Initial React rewrite

This commit is contained in:
Roman Hotsiy 2017-10-12 00:01:37 +03:00
parent af81c33c99
commit 4c3b4aa589
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
297 changed files with 37697 additions and 16661 deletions

View File

@ -1,30 +1,10 @@
root = true
[*]
charset=utf-8
end_of_line = lf
insert_final_newline=false
indent_style=space
indent_size=2
[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.scss]
indent_style=space
indent_size=2
[*.styl]
indent_style=space
indent_size=2
[*.coffee]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2
[tslint.json]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

14
.gitignore vendored
View File

@ -20,20 +20,8 @@ npm-debug.log*
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules
# compiled css
lib/**/*.css
lib/
# files produced by ngc
lib/**/*.ngfactory.ts
lib/**/*.css.shim.ts
**/*.ngsummary.json
lib/**/*.shim.ngstyle.ts
# other
/dist
/demo/build
.tmp
compiled
/coverage
.ghpages-tmp
stats.json

View File

@ -1,12 +1,8 @@
.DS_Store
**/.*
compiled
node_modules
jspm_packages
node_modules/
tests
lib
demo
build
coverage
perf/
demo/
coverage/

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"editor.formatOnSave": false
}

View File

@ -1,12 +1,3 @@
<a name="1.19.1"></a>
# [1.19.1](https://github.com/Rebilly/ReDoc/compare/v1.19.0...v1.19.1) (2017-10-02)
### Bug Fixes
* snapshot crashing on `constructor` prop ([04e8606](https://github.com/Rebilly/ReDoc/commit/04e8606)), closes [#341](https://github.com/Rebilly/ReDoc/issues/341)
<a name="1.19.0"></a>
# [1.19.0](https://github.com/Rebilly/ReDoc/compare/v1.18.1...v1.19.0) (2017-09-21)

View File

@ -10,6 +10,15 @@
[![Browser Compatibility](https://saucelabs.com/browser-matrix/redoc.svg)](https://saucelabs.com/u/redoc)
**REACT-REWRITE: WORK IN PROGRESS**
To try locally run:
```shell
yarn install
npm start
```
![ReDoc demo](demo/redoc-demo.png)
## [Live demo](http://rebilly.github.io/ReDoc/)

View File

@ -1,38 +0,0 @@
{
"name": "redoc",
"description": "Swagger-generated API Reference Documentation",
"main": "dist/redoc.min.js",
"authors": [
"Roman Hotsiy"
],
"repository": {
"type": "git",
"url": "git://github.com/Rebilly/ReDoc"
},
"license": "MIT",
"keywords": [
"OpenAPI",
"OpenAPI Specification",
"Swagger",
"JSON-Schema",
"API",
"REST",
"documentation",
"Angular 2"
],
"homepage": "https://github.com/Rebilly/ReDoc",
"moduleType": ["globals", "amd"],
"ignore": [
"**/.*",
"node_modules",
"tests",
"compiled",
"lib",
"demo",
"build",
"docs",
"gulpfile.js",
"*.conf.js",
"*.config.js"
]
}

View File

@ -1,9 +0,0 @@
const path = require('path');
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [__dirname, '..'].concat(args));
}
module.exports = {
root: root
}

View File

@ -1,15 +0,0 @@
#!/usr/bin/env node
'use strict';
require('shelljs/global');
set('-e');
set('-v');
cat([
'lib/components/Redoc/redoc-initial-styles.css',
'node_modules/perfect-scrollbar/dist/css/perfect-scrollbar.css',
'node_modules/dropkickjs/build/css/dropkick.css',
'node_modules/prismjs/themes/prism-dark.css',
'node_modules/hint.css/hint.base.css'
]).to('dist/redoc.css')

View File

@ -1,12 +0,0 @@
var path = require('path');
var paths = {
outputName: 'redoc.min.js',
output: 'dist/',
demo: 'demo/**/*',
releases: 'demo/releases/'
}
paths.redocBuilt = path.join(paths.output, paths.outputName);
module.exports = paths;

View File

@ -1,28 +0,0 @@
#!/usr/bin/env node
'use strict';
require('shelljs/global');
var paths = require('./paths');
var path = require('path');
set('-e');
set('-v');
// build
exec('npm run build-dist');
cd('demo');
mv('index-gh.html', 'index.html');
mkdir('-p', 'dist');
cp('-R', '../dist/*', './dist/');
cd('..');
var version = require(path.join(__dirname, '../package.json')).version;
var versionDir = path.join(paths.releases, 'v' + version + '/');
var latestDir = path.join(paths.releases, 'latest/');
var v1Dir = path.join(paths.releases, 'v' + version.split('.')[0] + '.x.x/');
mkdir('-p', versionDir)
mkdir('-p', latestDir);
mkdir('-p', v1Dir);
cp(paths.redocBuilt, versionDir);
cp(paths.redocBuilt, latestDir);
cp(paths.redocBuilt, v1Dir);

View File

@ -1,33 +0,0 @@
#!/usr/bin/env node
'use strict';
require('shelljs/global');
set('-e');
function isPR() {
return process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false';
}
function isCI() {
return !!process.env.CI;
}
if (process.env.JOB === 'e2e-guru') {
if (isPR()) {
console.log('Skiping E2E tests on PR');
return;
}
exec('npm run e2e');
} else {
exec('npm run unit');
if (isPR()) {
console.log('Skiping E2E tests on PR');
return;
}
if (!isCI()) {
console.log('Skiping E2E tests locally. Use `npm run e2e` to run');
return;
}
console.log('Starting Basic E2E');
exec('npm run e2e');
}

View File

@ -1,136 +0,0 @@
const webpack = require('webpack');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const StringReplacePlugin = require("string-replace-webpack-plugin");
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const VERSION = JSON.stringify(require('../package.json').version);
const root = require('./helpers').root;
module.exports = function (options) {
const conf = {
performance: { hints: false },
output: {
path: root('dist'),
filename: '[name].js',
sourceMapFilename: '[name].[id].map',
chunkFilename: '[id].chunk.js'
},
resolve: {
extensions: ['.ts', '.js', '.json', '.css'],
alias: {
http: 'stream-http',
https: 'https-browserify'
}
},
externals: {
'jquery': 'jquery',
'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc
},
module: {
exprContextCritical: false,
rules: [
{
enforce: 'pre',
test: /\.ts$/,
exclude: [
/node_modules/
],
loader: StringReplacePlugin.replace({
replacements: [
{
pattern: /styleUrls:\s*\[\s*'([\w\.\/-]*)\.css'\s*\][\s,]*$/gm,
replacement: function (match, p1, offset, string) {
return `styleUrls: ['${p1}.scss'],`;
}
},
{
pattern: /(\.\/components\/Redoc\/redoc-initial-styles\.css)/gm,
replacement: function (match, p1, offset, string) {
return p1.replace('.css', '.scss');
}
}
]
})
},
{
enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
exclude: [
/node_modules/
]
},
{
test: /\.json$/,
use: 'json-loader'
},
{
test: /lib[\\\/].*\.css$/,
loaders: ['raw-loader'],
exclude: [/redoc-initial-styles\.css$/]
}, {
test: /\.css$/,
loaders: ['style-loader', 'css-loader?-import'],
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.css$/]
},
{
test: /lib[\\\/].*\.scss$/,
loaders: ['raw-loader', 'sass-loader'],
exclude: [/redoc-initial-styles\.scss$/]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader?-import',
'sass-loader'
],
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/]
},
{
test: /\.html$/,
loader: 'raw-loader'
}
],
},
plugins: [
new CheckerPlugin(),
new webpack.DefinePlugin({
'IS_PRODUCTION': options.IS_PRODUCTION,
'LIB_VERSION': VERSION,
'AOT': options.AOT
}),
new StringReplacePlugin()
],
node: {
global: true,
crypto: 'empty',
fs: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
};
// if (options.AOT) {
// conf.plugins.push(
// new ngcWebpack.NgcWebpackPlugin({
// disable: !options.AOT,
// tsConfig: root('tsconfig.webpack.json'),
// resourceOverride: root('build/resource-override.js')
// })
// );
// }
return conf;
}

View File

@ -1,50 +0,0 @@
const webpack = require('webpack');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const StringReplacePlugin = require("string-replace-webpack-plugin");
const root = require('./helpers').root;
const VERSION = JSON.stringify(require('../package.json').version);
const IS_PRODUCTION = process.env.NODE_ENV === "production";
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig({
IS_PRODUCTION: IS_PRODUCTION,
AOT: IS_PRODUCTION
}), {
devtool: '#inline-source-map',
entry: {
'polyfills': './lib/polyfills.ts',
'redoc': './lib/index.ts',
},
devServer: {
contentBase: root('demo'),
watchContentBase: true,
compress: true,
watchOptions: {
poll: true
},
port: 9000,
hot: false,
stats: 'errors-only'
},
module: {
rules: [
{
test: /\.ts$/,
use: [
'awesome-typescript-loader',
'angular2-template-loader',
],
exclude: [/\.(spec|e2e)\.ts$/]
},
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'polyfills'],
minChunks: Infinity
})
]
})

View File

@ -1,83 +0,0 @@
const webpack = require('webpack');
const VERSION = JSON.stringify(require('../package.json').version);
const root = require('./helpers').root;
const BANNER =
`ReDoc - OpenAPI/Swagger-generated API Reference Documentation
-------------------------------------------------------------
Version: ${VERSION}
Repo: https://github.com/Rebilly/ReDoc`;
const IS_MODULE = process.env.IS_MODULE != null;
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js');
const config = webpackMerge(commonConfig({
IS_PRODUCTION: true,
AOT: true
}), {
devtool: 'source-map',
entry: {
'redoc': IS_MODULE ? ['./lib/redoc.module.ts'] : ['./lib/polyfills.ts', './lib/index.ts']
},
output: {
path: root('dist'),
filename: IS_MODULE ? '[name]-module.js' : '[name].min.js',
sourceMapFilename: IS_MODULE ? '[name]-module.map' : '[name].min.map',
library: 'Redoc',
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
rules: [
{
test: /\.ts$/,
use: [
'awesome-typescript-loader',
'angular2-template-loader',
],
exclude: [/\.(spec|e2e)\.ts$/]
}
]
},
plugins: [
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true
},
mangle: { screw_ie8 : true },
output: {
comments: false
},
sourceMap: true
}),
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.BannerPlugin(BANNER)
]
})
if (IS_MODULE) {
config.externals = {
'jquery': 'jquery',
'esprima': 'esprima', // optional dep of ys-yaml not needed for redoc
'@angular/platform-browser-dynamic': '@angular/platform-browser-dynamic',
'@angular/platform-browser': '@angular/platform-browser',
'@angular/core': '@angular/core',
'@angular/common': '@angular/common',
'@angular/forms': '@angular/forms',
'core-js': 'core-js',
'rxjs': 'rxjs',
'zone.js/dist/zone': 'zone.js/dist/zone'
};
}
module.exports = config;

View File

@ -1,61 +0,0 @@
const webpack = require('webpack');
const root = require('./helpers').root;
const path = require('path');
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig({
IS_PRODUCTION: true,
AOT: false
}), {
devtool: 'inline-source-map',
module: {
exprContextCritical: false,
rules: [
{
test: /\.ts$/,
use: 'awesome-typescript-loader'
},
{
test: /\.ts$/,
use: [
'angular2-template-loader',
],
exclude: [/\.(spec|e2e)\.ts$/]
},
{
enforce: 'post',
test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader',
include: root('lib'),
exclude: [
/\.(e2e|spec)\.ts$/,
/node_modules/
]
}]
},
plugins: [
new webpack.LoaderOptionsPlugin({
test: /\.ts$/,
sourceMap: false,
inlineSourceMap: true,
removeComments: true,
module: "commonjs"
}),
// ignore changes during tests
new webpack.WatchIgnorePlugin([
/[\\\/]ReDoc$/i, // ignore change of ReDoc folder itself
/node_modules[\\\/].*$/,
/\.tmp[\\\/].*$/,
/dist[\\\/].*$/,
/(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.css$/ // ignore css files
]),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
path.resolve(__dirname, '../src')
)
],
})

27
custom.d.ts vendored
View File

@ -1,25 +1,20 @@
declare module "*.css" {
declare module '*.json' {
const content: any;
export = content;
}
declare module '*.svg' {
const content: string;
export default content;
}
declare module "*.json" {
declare module '*.css' {
const content: string;
export default content;
}
declare var LIB_VERSION: any;
declare var IS_PRODUCTION: any;
declare var AOT: any;
declare var __DEV__: boolean;
interface ErrorStackTraceLimit {
stackTraceLimit: number;
}
interface History {
scrollRestoration: "auto"|"manual";
}
interface Window {
HTMLElement: any
}
declare var safari: any;
interface ErrorConstructor extends ErrorStackTraceLimit {}
declare type Dict<T> = {
[key: string]: T;
};

27362
demo/big-swagger.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ReDoc Demo: Multiple apis</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
margin: 0;
padding-top: 40px;
}
nav {
position: fixed;
top: 0;
width: 100%;
z-index: 100;
}
#links_container {
margin: 0;
padding: 0;
background-color: #0033a0;
}
#links_container li {
display: inline-block;
padding: 10px;
color: white;
cursor: pointer;
}
</style>
</head>
<body>
<!-- Top navigation placeholder -->
<nav>
<ul id="links_container">
</ul>
</nav>
<redoc scroll-y-offset="body > nav"></redoc>
<script src="https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js"> </script>
<script>
// list of APIS
var apis = [
{
name: 'PetStore',
url: 'https://rebilly.github.io/ReDoc/swagger.yaml'
},
{
name: 'Instagram',
url: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml'
},
{
name: 'Google Calendar',
url: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml'
}
];
// initially render first API
Redoc.init(apis[0].url);
function onClick() {
var url = this.getAttribute('data-link');
Redoc.init(url);
}
// dynamically building navigation items
var $list = document.getElementById('links_container');
apis.forEach(function(api) {
var $listitem = document.createElement('li');
$listitem.setAttribute('data-link', api.url);
$listitem.innerText = api.name;
$listitem.addEventListener('click', onClick);
$list.appendChild($listitem);
});
</script>
</body>
</html>

View File

@ -1,39 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ReDoc</title>
<link rel="stylesheet" href="main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/vaadin-combo-box/vaadin-combo-box-light.html">
</head>
<body>
<nav>
<header> <a href="/ReDoc"> ReDoc </a> </header>
<template is="dom-bind" id="specs">
<form id="schema-url-form" is="iron-form">
<vaadin-combo-box-light id="spec-input" items="[[specs]]" 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="150px" height="30px"></iframe>
</nav>
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering untrusted-spec></redoc>
<script src="main.js"> </script>
<script src="./dist/redoc.min.js"> </script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-81703547-1', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

View File

@ -1,35 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ReDoc</title>
<link rel="stylesheet" href="main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/vaadin-combo-box/vaadin-combo-box-light.html">
<style>
body {
margin: 0;
padding: 0;
}
redoc {
display: block;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
<nav>
<header> <a href="/ReDoc"> ReDoc </a> </header>
<template is="dom-bind" id="specs">
<form id="schema-url-form" is="iron-form">
<vaadin-combo-box-light id="spec-input" items="[[specs]]" 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="150px" height="30px"></iframe>
</nav>
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering untrusted-spec></redoc>
<script>
window.__REDOC_DEV__ = true;
</script>
<script src="main.js"> </script>
<script src="/webpack-dev-server.js"></script>
<script src="/polyfills.js"></script>
<script src="/redoc.js"></script>
<redoc id="example"></redoc>
</body>
</html>

View File

@ -1,133 +0,0 @@
body {
margin: 0;
padding-top: 50px;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-moz-tap-highlight-color: rgba(0,0,0,0);
-ms-tap-highlight-color: rgba(0,0,0,0);
-o-tap-highlight-color: rgba(0,0,0,0);
tap-highlight-color: rgba(0,0,0,0);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-smoothing: antialiased;
-webkit-osx-font-smoothing: grayscale;
-moz-osx-font-smoothing: grayscale;
osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
-ms-text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
text-rendering: optimizeSpeed!important;
font-smooth: always;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
font-family: Monserrat, sans-serif;
}
nav input, nav button {
font-size: 16px;
height: 28px;
box-sizing: border-box;
vertical-align: middle;
line-height: 1;
outline: none;
}
nav header {
font-family: Monserrat, sans-serif;
float: left;
margin-left: 20px;
font-size: 25px;
color: #00329F;
font-weight: bold;
}
nav input {
width: 50%;
box-sizing: border-box;
max-width: 500px;
padding: 0 10px;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
}
nav input:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
}
nav button {
background-color: #fff;
color: #333;
padding: 2px 10px;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border: 1px solid #ccc;
}
nav button:hover {
background-color: #e6e6e6;
border-color: #adadad;
}
nav button:active {
background-color: #d4d4d4;
border-color: #8c8c8c;
}
nav {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
background-color: white;
border-bottom: 1px solid #ccc;
position: fixed;
top: 0;
z-index: 1;
box-sizing: border-box;
}
nav iframe {
margin: 10px 0;
position: absolute;
right: 0;
top: 0;
}
header a {
color: inherit;
text-decoration: none;
}
@media (min-width: 1000px) {
nav header {
position: absolute;
}
}
@media (max-width: 500px) {
nav input {
width: 70%;
}
nav header {
display: none;
}
nav iframe {
display: none;
}
}

View File

@ -1,74 +0,0 @@
;(function() {
'use strict';
var schemaUrlForm = document.getElementById('schema-url-form');
var schemaUrlInput;
var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
url = window.__REDOC_DEV__ ? url : '\\\\cors.apis.guru/' + url;
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
}
function updateQueryStringParameter(uri, key, value) {
var re = new RegExp("([?|&])" + key + "=.*?(&|#|$)", "i");
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
} else {
var hash = '';
if( uri.indexOf('#') !== -1 ){
hash = uri.replace(/.*#/, '#');
uri = uri.replace(/#.*/, '');
}
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
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) {
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);
});
function selectItem() {
let value = this.innerText.trim();
schemaUrlInput.value = value;
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
}
// for some reason events are not triggered so have to dirty fix this
$specInput.addEventListener('click', function(event) {
let $elems = document.querySelectorAll('.item.vaadin-combo-box-overlay');
$elems.forEach(function($el) {
$el.addEventListener('mousedown', selectItem);
$el.addEventListener('mousedown', selectItem);
});
});
});
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

View File

@ -1,43 +1,79 @@
swagger: '2.0'
schemes:
- http
- https
host: petstore.swagger.io
basePath: /v2
openapi: 3.0.0
servers:
- url: //petstore.swagger.io/v2
description: Default server
- url: //petstore.swagger.io/sandbox
description: Sandbox server
info:
description: |
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.
[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
[Petstore 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).
[Petstore 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
[Petstore 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).
[Petstore 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/).
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.
All responses have a wildcard same-origin which makes them completely public
and accessible to everyone, including any code on any site.
# Authentication
Petstore offers two forms of authentication:
- API Key
- OAuth2
OAuth2 - an open protocol to allow secure authorization in a simple
and standard method from web, mobile and desktop applications.
<!-- ReDoc-Inject: <security-definitions> -->
version: 1.0.0
title: Swagger Petstore
termsOfService: 'http://swagger.io/terms/'
contact:
name: API Support
email: apiteam@swagger.io
url: https://github.com/Rebilly/ReDoc
x-logo:
@ -63,49 +99,22 @@ x-tagGroups:
- name: User Management
tags:
- user
securityDefinitions:
petstore_auth:
description: |
Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.
type: oauth2
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
flow: implicit
scopes:
'write:pets': modify pets in your account
'read:pets': read your pets
api_key:
description: |
For this sample, you can use the api key `special-key` to test the authorization filters.
type: apiKey
name: api_key
in: header
x-servers:
- url: //petstore.swagger.io/v2
description: Default server
- url: //petstore.swagger.io/sandbox
description: Sandbox server
paths:
/pet:
parameters:
- name: pathParam
in: cookie
description: Some cookie
required: true
schema:
type: integer
format: int64
post:
tags:
- pet
summary: Add a new pet to the store
description: Add new pet to the store inventory.
operationId: addPet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'405':
description: Invalid input
@ -132,26 +141,24 @@ paths:
Console.WriteLine(response.getRawResponse());
}
- lang: PHP
source: "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->create($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n"
source: |
$form = new \PetStore\Entities\Pet();
$form->setPetType("Dog");
$form->setName("Rex");
// set other fields
try {
$pet = $client->pets()->create($form);
} catch (UnprocessableEntityException $e) {
var_dump($e->getErrors());
}
requestBody:
$ref: '#/components/requestBodies/Pet'
put:
tags:
- pet
summary: Update an existing pet
description: ''
operationId: updatePet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'400':
description: Invalid ID supplied
@ -165,7 +172,19 @@ paths:
- 'read:pets'
x-code-samples:
- lang: PHP
source: "$form = new \\PetStore\\Entities\\Pet();\n$form->setPetId(1);\n$form->setPetType(\"Dog\");\n$form->setName(\"Rex\");\n// set other fields\ntry {\n $pet = $client->pets()->update($form);\n} catch (UnprocessableEntityException $e) {\n var_dump($e->getErrors());\n}\n"
source: |
$form = new \PetStore\Entities\Pet();
$form->setPetId(1);
$form->setPetType("Dog");
$form->setName("Rex");
// set other fields
try {
$pet = $client->pets()->update($form);
} catch (UnprocessableEntityException $e) {
var_dump($e->getErrors());
}
requestBody:
$ref: '#/components/requestBodies/Pet'
'/pet/{petId}':
get:
tags:
@ -173,21 +192,25 @@ paths:
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
deprecated: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/definitions/Pet'
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
@ -200,28 +223,14 @@ paths:
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
consumes:
- application/x-www-form-urlencoded
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
schema:
type: integer
format: int64
- name: name
in: formData
description: Updated name of the pet
required: false
type: string
- name: status
in: formData
description: Updated status of the pet
required: false
type: string
responses:
'405':
description: Invalid input
@ -229,25 +238,36 @@ paths:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
name:
description: Updated name of the pet
type: string
status:
description: Updated status of the pet
type: string
delete:
tags:
- pet
summary: Deletes a pet
description: ''
operationId: deletePet
produces:
- application/xml
- application/json
parameters:
- name: api_key
in: header
required: false
schema:
type: string
x-example: Bearer <TOKEN>
example: "Bearer <TOKEN>"
- name: petId
in: path
description: Pet id to delete
required: true
schema:
type: integer
format: int64
responses:
@ -264,36 +284,31 @@ paths:
summary: uploads an image
description: ''
operationId: uploadFile
consumes:
- multipart/form-data
produces:
- application/json
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
schema:
type: integer
format: int64
- name: additionalMetadata
in: formData
description: Additional data to pass to server
required: false
type: string
- name: file
in: formData
description: file to upload
required: false
type: file
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/definitions/ApiResponse'
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
content:
application/octet-stream:
schema:
type: string
format: binary
/pet/findByStatus:
get:
tags:
@ -301,14 +316,13 @@ paths:
summary: Finds Pets by status
description: Multiple status values can be provided with comma seperated strings
operationId: findPetsByStatus
produces:
- application/xml
- application/json
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: true
style: form
schema:
type: array
items:
type: string
@ -317,14 +331,20 @@ paths:
- pending
- sold
default: available
collectionFormat: csv
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/definitions/Pet'
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid status value
security:
@ -336,28 +356,35 @@ paths:
tags:
- pet
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
deprecated: true
produces:
- application/xml
- application/json
parameters:
- name: tags
in: query
description: Tags to filter by
required: true
type: array
items:
type: string
collectionFormat: csv
responses:
'200':
description: successful operation
style: form
schema:
type: array
items:
$ref: '#/definitions/Pet'
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid tag value
security:
@ -371,12 +398,11 @@ paths:
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
produces:
- application/json
parameters: []
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
@ -391,47 +417,59 @@ paths:
summary: Place an order for a pet
description: ''
operationId: placeOrder
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: order placed for purchasing the pet
required: true
schema:
$ref: '#/definitions/Order'
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/definitions/Order'
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid Order
content:
application/json:
example:
status: 400
message: "Invalid Order"
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
description: order placed for purchasing the pet
required: true
'/store/order/{orderId}':
get:
tags:
- store
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
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of pet that needs to be fetched
required: true
schema:
type: integer
maximum: 5
minimum: 1
format: int64
minimum: 1
maximum: 5
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/definitions/Order'
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'404':
@ -440,16 +478,16 @@ paths:
tags:
- store
summary: Delete purchase order by ID
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
description: >-
For valid response try integer IDs with value < 1000. Anything above
1000 or nonintegers will generate API errors
operationId: deleteOrder
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: string
minimum: 1
responses:
@ -464,19 +502,16 @@ paths:
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Created user object
required: true
schema:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
description: Created user object
required: true
'/user/{username}':
get:
tags:
@ -484,20 +519,23 @@ paths:
summary: Get user by user name
description: ''
operationId: getUserByName
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing. '
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/definitions/User'
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid username supplied
'404':
@ -508,40 +546,37 @@ paths:
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: name that need to be deleted
required: true
type: string
- in: body
name: body
description: Updated user object
required: true
schema:
$ref: '#/definitions/User'
type: string
responses:
'400':
description: Invalid user supplied
'404':
description: User not found
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
description: Updated user object
required: true
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
schema:
type: string
responses:
'400':
@ -555,21 +590,11 @@ paths:
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithArrayInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
requestBody:
$ref: '#/components/requestBodies/UserArray'
/user/createWithList:
post:
tags:
@ -577,21 +602,11 @@ paths:
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithListInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
requestBody:
$ref: '#/components/requestBodies/UserArray'
/user/login:
get:
tags:
@ -599,38 +614,50 @@ paths:
summary: Logs user into the system
description: ''
operationId: loginUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: query
description: The user name for login
required: true
schema:
type: string
- name: password
in: query
description: The password for login in clear text
required: true
schema:
type: string
responses:
'200':
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when toekn expires
schema:
type: string
format: date-time
content:
application/json:
schema:
type: string
examples:
application/json: OK
application/xml: <message> OK </message>
text/plain: OK
headers:
X-Rate-Limit:
type: integer
format: int32
description: calls per hour allowed by the user
X-Expires-After:
response:
value: OK
application/xml:
schema:
type: string
format: date-time
description: date in UTC when toekn expires
examples:
response:
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
@ -640,14 +667,11 @@ paths:
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
produces:
- application/xml
- application/json
parameters: []
responses:
default:
description: successful operation
definitions:
components:
schemas:
ApiResponse:
type: object
properties:
@ -661,7 +685,7 @@ definitions:
Cat:
description: A representation of a cat
allOf:
- $ref: '#/definitions/Pet'
- $ref: '#/components/schemas/Pet'
- type: object
properties:
huntingSkill:
@ -681,7 +705,7 @@ definitions:
id:
description: Category ID
allOf:
- $ref: '#/definitions/Id'
- $ref: '#/components/schemas/Id'
name:
description: Category name
type: string
@ -698,7 +722,7 @@ definitions:
Dog:
description: A representation of a dog
allOf:
- $ref: '#/definitions/Pet'
- $ref: '#/components/schemas/Pet'
- type: object
properties:
packSize:
@ -712,7 +736,7 @@ definitions:
HoneyBee:
description: A representation of a honey bee
allOf:
- $ref: '#/definitions/Pet'
- $ref: '#/components/schemas/Pet'
- type: object
properties:
honeyPerDay:
@ -730,11 +754,11 @@ definitions:
id:
description: Order ID
allOf:
- $ref: '#/definitions/Id'
- $ref: '#/components/schemas/Id'
petId:
description: Pet ID
allOf:
- $ref: '#/definitions/Id'
- $ref: '#/components/schemas/Id'
quantity:
type: integer
format: int32
@ -762,16 +786,21 @@ definitions:
required:
- name
- photoUrls
discriminator: petType
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'
bee: '#/components/schemas/HoneyBee'
properties:
id:
description: Pet ID
allOf:
- $ref: '#/definitions/Id'
- $ref: '#/components/schemas/Id'
category:
description: Categories this pet belongs to
allOf:
- $ref: '#/definitions/Category'
- $ref: '#/components/schemas/Category'
name:
description: The name given to a pet
type: string
@ -785,6 +814,9 @@ definitions:
items:
type: string
format: url
friend:
allOf:
- $ref: '#/components/schemas/Pet'
tags:
description: Tags attached to the pet
type: array
@ -792,7 +824,7 @@ definitions:
name: tag
wrapped: true
items:
$ref: '#/definitions/Tag'
$ref: '#/components/schemas/Tag'
status:
type: string
description: Pet status in the store
@ -811,7 +843,7 @@ definitions:
id:
description: Tag ID
allOf:
- $ref: '#/definitions/Id'
- $ref: '#/components/schemas/Id'
name:
description: Tag name
type: string
@ -822,8 +854,11 @@ definitions:
type: object
properties:
id:
description: User ID
$ref: '#/definitions/Id'
$ref: '#/components/schemas/Id'
pet:
oneOf:
- $ref: '#/components/schemas/Pet'
- $ref: '#/components/schemas/Tag'
username:
description: User supplied username
type: string
@ -846,7 +881,9 @@ definitions:
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'
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])'
@ -854,12 +891,58 @@ definitions:
phone:
description: User phone number in international format
type: string
pattern: "^\\+(?:[0-9]-?){6,14}[0-9]$"
pattern: '^\+(?:[0-9]-?){6,14}[0-9]$'
example: +1-202-555-0192
x-nullable: true
nullable: true
userStatus:
description: User status
type: integer
format: int32
xml:
name: User
requestBodies:
Pet:
content:
application/json:
schema:
allOf:
- description: My Pet
title: Pettie
- $ref: '#/components/schemas/Pet'
application/xml:
schema:
type: 'object'
properties:
name:
type: string
description: hooray
description: Pet object that needs to be added to the store
required: true
UserArray:
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
description: List of user object
required: true
securitySchemes:
petstore_auth:
description: |
Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access.
type: oauth2
flows:
implicit:
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
scopes:
'write:pets': modify pets in your account
'read:pets': read your pets
api_key:
description: >
For this sample, you can use the api key `special-key` to test the
authorization filters.
type: apiKey
name: api_key
in: header

1
empty.js Normal file
View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -1,53 +0,0 @@
module.exports = function(config) {
const testWebpackConfig = require('./build/webpack.test.js');
const travis = process.env.TRAVIS;
config.set({
frameworks: ['jasmine', 'sinon', 'should'],
preprocessors: {
'./tests/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'],
},
coverageReporter: {
type: 'in-memory',
},
remapCoverageReporter: {
'text-summary': null,
'text-lcov': './coverage/lcov.info',
html: './coverage/html',
},
webpack: testWebpackConfig,
webpackMiddleware: {
stats: 'errors-only',
state: true,
},
client: {
chai: {
truncateThreshold: 0,
},
},
files: [
{ pattern: './tests/spec-bundle.js', watched: false },
{ pattern: 'tests/schemas/**/*.json', included: false },
{ pattern: 'tests/schemas/**/*.yml', included: false },
{ pattern: 'lib/**/*.html', included: false },
{ pattern: 'lib/**/*.css', included: false },
],
proxies: {
'/tests/schemas': '/base/tests/schemas',
'/lib/': '/base/lib/',
'/node_modules/': '/base/node_modules/',
},
colors: true,
singleRun: true,
reporters: travis
? ['mocha', 'coverage', 'remap-coverage', 'coveralls']
: ['mocha', 'coverage', 'remap-coverage'],
browsers: ['ChromeHeadless'],
browserNoActivityTimeout: 60000,
});
};

View File

@ -1,13 +0,0 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RedocModule } from './redoc.module';
import { Redoc } from './components/index';
@NgModule({
imports: [ BrowserModule, RedocModule ],
bootstrap: [ Redoc ],
exports: [ Redoc ]
})
export class AppModule {
}

View File

@ -1,7 +0,0 @@
import { NgModuleRef } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
export function bootstrapRedoc(): Promise<NgModuleRef<AppModule>> {
return platformBrowserDynamic().bootstrapModule(AppModule);
}

View File

@ -1,8 +0,0 @@
import { NgModuleRef } from '@angular/core';
import { platformBrowser } from '@angular/platform-browser';
import { AppModule } from './app.module';
import { AppModuleNgFactory } from '../compiled/lib/app.module.ngfactory';
export function bootstrapRedoc():Promise<NgModuleRef<AppModule>> {
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
}

View File

@ -1,24 +0,0 @@
<div class="api-info-wrapper">
<h1>{{info.title}} <span class="api-info-version">({{info.version}})</span></h1>
<p class="download-openapi" *ngIf="specUrl">
Download OpenAPI specification:
<a class="openapi-button" [attr.download]="downloadFilename" target="_blank" [attr.href]="specUrl"> Download </a>
</p>
<p>
<!-- TODO: create separate components for contact and license ? -->
<span *ngIf="info?.contact?.url || info?.contact?.email"> 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>
<redoc-externalDocs [docs]="componentSchema.externalDocs"></redoc-externalDocs>
</p>
<span class="redoc-markdown-block">
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
</span>
</div>

View File

@ -1,33 +0,0 @@
@import '../../shared/styles/variables';
:host > .api-info-wrapper {
box-sizing: border-box;
padding: $section-spacing;
width: 60%;
@media (max-width: $right-panel-squash-breakpoint) {
width: 100%;
}
@media (max-width: $side-menu-mobile-breakpoint) {
padding-top: $section-spacing + 20px;
}
}
.openapi-button {
border: 1px solid $primary-color;
color: $primary-color;
font-weight: normal;
margin-left: 0.5em;
padding: 3px 8px 4px;
display: inline-block;
}
:host /deep/ [section] {
padding-top: 2 * $section-spacing;
}
:host /deep/ h2[section] {
padding-top: $section-spacing;
}

View File

@ -1,66 +0,0 @@
'use strict';
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import { OptionsService } from '../../services/index';
import {
inject,
async,
TestBed
} from '@angular/core/testing';
import { SpecManager } from '../../utils/spec-manager';
describe('Redoc components', () => {
describe('ApiInfo Component', () => {
let component;
let fixture;
let opts;
let specMgr;
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
opts = _opts;
opts.options = {
scrollYOffset: () => 0,
$scrollParent: window
};
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load('/tests/schemas/api-info-test.json').then(done, done.fail);
});
beforeEach(async(() => {
fixture = TestBed.createComponent(TestAppComponent);
component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance;
fixture.detectChanges();
}));
it('should init component data', () => {
expect(component).not.toBeNull();
expect(component.info).not.toBeNull();
component.info.title.should.be.equal('Swagger Petstore');
});
it('should render api name and version', () => {
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-info').nativeElement;
let headerElement = nativeElement.querySelector('h1');
expect(headerElement.innerText).toContain('Swagger Petstore (v1.0.0)');
});
});
});
/** Test component that contains an ApiInfo. */
@Component({
selector: 'test-app',
template:
`<api-info></api-info>`
})
class TestAppComponent {
}

View File

@ -1,44 +0,0 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { SpecManager, BaseComponent } from '../base';
import { OptionsService, Marker } from '../../services/index';
@Component({
selector: 'api-info',
styleUrls: ['./api-info.css'],
templateUrl: './api-info.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ApiInfo extends BaseComponent implements OnInit {
info: any = {};
specUrl: String | SafeResourceUrl;
downloadFilename = '';
constructor(specMgr: SpecManager,
private optionsService: OptionsService,
elRef: ElementRef,
marker: Marker,
private sanitizer: DomSanitizer
) {
super(specMgr);
marker.addElement(elRef.nativeElement);
}
init() {
this.info = this.componentSchema.info;
this.specUrl = this.specMgr.specUrl;
if (!this.specUrl && window.Blob && window.URL) {
const blob = new Blob([JSON.stringify(this.specMgr.rawSpec, null, 2)], {type : 'application/json'});
this.specUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
this.downloadFilename = 'swagger.json';
}
if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) {
this.info.version = 'v' + this.info.version;
}
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,4 +0,0 @@
<a *ngIf="logo.url" href="{{logo.url}}">
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
</a>
<img *ngIf="logo.imgUrl && !logo.url" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">

View File

@ -1,19 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
text-align: center;
@media (max-width: $side-menu-mobile-breakpoint) {
display: none;
}
}
img {
max-height: 150px;
width: auto;
display: inline-block;
max-width: 100%;
//padding: 0 5px;
box-sizing: border-box;
}

View File

@ -1,73 +0,0 @@
'use strict';
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import {
inject,
async,
TestBed
} from '@angular/core/testing';
import { SpecManager } from '../../utils/spec-manager';
describe('Redoc components', () => {
describe('ApiLogo Component', () => {
let builder;
let component;
let fixture;
let specMgr;
let schemaUrl = '/tests/schemas/api-info-test.json';
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
beforeEach(async(inject([SpecManager], ( _specMgr) => {
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load(schemaUrl).then(done, done.fail);
});
beforeEach(() => {
fixture = TestBed.createComponent(TestAppComponent);
component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance;
fixture.detectChanges();
});
it('should init component data', () => {
if (specMgr.a) return;
expect(component).not.toBeNull();
expect(component.logo).not.toBeNull();
});
it('should not display image when no x-logo', () => {
component.logo.should.be.empty();
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement;
let imgElement = nativeElement.querySelector('img');
expect(imgElement).toBeNull();
// update schemaUrl to load other schema in the next test
schemaUrl = '/tests/schemas/extended-petstore.yml';
});
it('should load values from spec and use transparent bgColor by default', () => {
component.logo.imgUrl.should.endWith('petstore-logo.png');
component.logo.bgColor.should.be.equal('transparent');
});
});
});
/** Test component that contains an ApiInfo. */
@Component({
selector: 'test-app',
template:
`<api-logo></api-logo>`
})
class TestAppComponent {
}

View File

@ -1,30 +0,0 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { BaseComponent, SpecManager } from '../base';
@Component({
selector: 'api-logo',
styleUrls: ['./api-logo.css'],
templateUrl: './api-logo.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ApiLogo extends BaseComponent implements OnInit {
logo:any = {};
constructor(specMgr:SpecManager) {
super(specMgr);
}
init() {
const info = this.componentSchema.info;
const logoInfo = info['x-logo'];
if (!logoInfo) return;
this.logo.imgUrl = logoInfo.url;
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
this.logo.url = info.contact && info.contact.url || null;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,17 +0,0 @@
<div class="operation-endpoint" (click)="handleClick()">
<h5 class="http-verb" [ngClass]="verb">{{verb}}</h5>
<span><!--
--><span class="operation-api-url-path">{{path}}</span><!--
--></span>
<svg class="expand-icon" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
<polygon fill="white" points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
</svg>
</div>
<div class="servers-overlay">
<div *ngFor="let server of servers" class="server-item">
<div class="description" [innerHtml]="server.description | marked"></div>
<div select-on-click class="url">
<span class="operation-api-url"> {{server.url}}</span>{{path}}
</div>
</div>
</div>

View File

@ -1,153 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
position: relative;
cursor: pointer;
}
.operation-endpoint {
padding: 10px 30px 10px 20px;
border-radius: $border-radius*2;
background-color: darken($black, 2%);
display: block;
font-weight: $light;
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
border: 1px solid transparent;
}
.operation-endpoint > .operation-params-subheader {
padding-top: 1px;
padding-bottom: 0;
margin: 0;
font-size: 12/14em;
color: $black;
vertical-align: middle;
display: inline-block;
border-radius: $border-radius;
}
.operation-api-url {
color: rgba($black, 0.8);
&-path {
font-family: $headers-font, $headers-font-family;
position: relative;
top: 1px;
color: #ffffff;
margin-left: 10px;
}
}
.http-verb {
color: $black;
background: #ffffff;
padding: 3px 10px;
text-transform: uppercase;
display: inline-block;
margin: 0;
}
.servers-overlay {
position: absolute;
width: 100%;
z-index: 100;
background: $side-bar-bg-color;
color: $black;
box-sizing: border-box;
box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.33);
overflow: hidden;
border-bottom-left-radius: $border-radius*2;
border-bottom-right-radius: $border-radius*2;
}
.server-item {
padding: 10px;
//margin-bottom: 10px;
& > .url {
padding: 5px;
border: 1px solid $border-color;
background: $background-color;
word-break: break-all;
color: $primary-color;
}
&:last-child {
margin-bottom: 0;
}
}
.expand-icon {
height: 20px;
width: 20px;
display: inline-block;
float: right;
background: darken($black, 2%);
transform: rotateZ(0);
transition: all 0.2s ease;
top: 15px;
right: 5px;
position: absolute;
}
.servers-overlay {
transform: translateY(-50%) scaleY(0);
transition: all 0.25s ease;
}
:host.expanded {
> .operation-endpoint {
border-color: $side-bar-bg-color;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.expand-icon {
transform: rotateZ(180deg);
}
.servers-overlay {
transform: translateY(0%) scaleY(1);
}
}
.http-verb {
color: white;
&.get {
background-color: $get-color;
}
&.post {
background-color: $post-color;
}
&.put {
background-color: $put-color;
}
&.options {
background-color: $options-color;
}
&.patch {
background-color: $patch-color;
}
&.delete {
background-color: $delete-color;
}
&.basic {
background-color: $basic-color;
}
&.link {
background-color: $link-color;
}
&.head {
background-color: $head-color;
}
}

View File

@ -1,82 +0,0 @@
'use strict';
import { Component } from '@angular/core';
import {
inject,
async,
TestBed
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { EndpointLink } from './endpoint-link';
import { SpecManager } from '../../utils/spec-manager';
import { OptionsService } from '../../services/';
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('EndpointLink Component', () => {
let builder;
let component: EndpointLink;
let specMgr: SpecManager;
let opts: OptionsService;
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
specMgr = _specMgr;
opts = _opts;
})));
beforeEach(() => {
specMgr.apiUrl = 'http://test.com/v1';
specMgr._schema = {
info: {},
host: 'petstore.swagger.io',
baseName: '/v2',
schemes: ['https', 'http'],
'x-servers': [
{
url: '//test.com/v2'
},
{
url: 'ws://test.com/v3',
description: 'test'
}
]
};
specMgr.init();
component = new EndpointLink(specMgr, opts);
});
it('should replace // with appropriate protocol', () => {
component.ngOnInit();
component.servers[0].url.should.be.equal('https://test.com/v2');
});
it('should preserve other protocols', () => {
component.ngOnInit();
component.servers[1].url.should.be.equal('ws://test.com/v3');
});
it('should fallback to host + basePath + schemas if no x-servers', () => {
specMgr._schema['x-servers'] = null;
specMgr.init();
component.ngOnInit();
component.servers.should.be.lengthOf(1);
component.servers[0].url.should.be.equal('https://petstore.swagger.io');
});
});
});
/** Test component that contains an Operation. */
@Component({
selector: 'test-app',
template:
`<operation pointer='#/paths/~1user~1{username}/put'></operation>`
})
class TestAppComponent {
}

View File

@ -1,63 +0,0 @@
'use strict';
import { Component, ChangeDetectionStrategy, Input, OnInit, HostListener, HostBinding} from '@angular/core';
import { BaseComponent, SpecManager } from '../base';
import { OptionsService } from '../../services/';
import { stripTrailingSlash } from '../../utils/';
export interface ServerInfo {
description: string;
url: string;
}
@Component({
selector: 'endpoint-link',
styleUrls: ['./endpoint-link.css'],
templateUrl: './endpoint-link.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EndpointLink implements OnInit {
@Input() path:string;
@Input() verb:string;
apiUrl: string;
servers: ServerInfo[];
@HostBinding('class.expanded') expanded: boolean = false;
// @HostListener('click')
handleClick() {
this.expanded = !this.expanded;
}
constructor(public specMgr:SpecManager, public optionsService: OptionsService) {
this.expanded = false;
}
init() {
let servers:ServerInfo[] = this.specMgr.schema['x-servers'];
if (servers) {
this.servers = servers.map(({url, description}) => ({
description,
url: stripTrailingSlash(url.startsWith('//') ? `${this.specMgr.apiProtocol}:${url}` : url)
}));
} else {
this.servers = [
{
description: 'Server URL',
url: this.getBaseUrl()
}
];
}
}
getBaseUrl():string {
if (this.optionsService.options.hideHostname) {
return '';
} else {
return this.specMgr.apiUrl;
}
}
ngOnInit() {
this.init();
}
}

View File

@ -1,18 +0,0 @@
'use strict';
import { Component, Input, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { BaseComponent, SpecManager } from '../base';
@Component({
selector: 'redoc-externalDocs',
template: `<a *ngIf="docs" [href]="docs.url" [innerHtml]="docs.description | marked"></a>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExternalDocs implements OnInit {
@Input() docs;
ngOnInit() {
if (this.docs && !this.docs.description) {
this.docs.description = 'External Docs';
}
}
}

View File

@ -1,260 +0,0 @@
@import '../../shared/styles/variables';
$lines-width: 1px;
$bullet-size: 1px;
$cell-spacing: 25px;
$cell-padding: 10px;
$bullet-margin: 10px;
$line-border: $lines-width solid $tree-lines-color;
$line-border-erase: ($lines-width + 1px) solid $background-color;
$border-color: lighten($secondary-color, 50%);
$nullable-color: #3195a6;
$hint-border: 1px dotted rgba(38, 50, 56, 0.4);
$param-name-height: 20px;
$sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
.param-name-wrap {
display: inline-block;
padding-right: $cell-spacing;
font-family: $headers-font, $headers-font-family;
}
.param-info {
border-bottom: 1px solid $border-color;
padding: $cell-padding 0;
width: 75%;
box-sizing: border-box;
> div {
line-height: 1;
}
}
.param-range {
position: relative;
top: 1px;
margin-right: 6px;
margin-left: 6px;
border-radius: $border-radius;
background-color: rgba($primary-color, 0.1);
padding: 0 4px;
color: rgba($primary-color, 0.7);
}
.param-description {
//font-size: 14px;
}
.param-required {
vertical-align: middle;
line-height: $param-name-height;
color: $red;
font-size: 12px;
font-weight: bold;
}
.param-nullable {
vertical-align: middle;
line-height: $param-name-height;
color: $nullable-color;
font-size: 12px;
font-weight: bold;
}
.param-type,
.param-array-format {
vertical-align: middle;
line-height: $param-name-height;
color: rgba($black, 0.4);
font-size: 0.929em;
}
.param-type {
font-weight: normal;
word-break: break-all;
&.array::before,
&.tuple::before {
color: $black;
font-weight: $base-font-weight;
.param-collection-format-multi + & {
content: none;
}
}
&.array::before {
content: $array-text;
}
&.tuple::before {
content: $tuple-text;
}
&.with-hint {
display: inline-block;
margin-bottom: 0.4em;
border-bottom: $hint-border;
padding: 0;
cursor: help;
}
&-trivial {
display: inline-block;
}
&-file {
font-weight: bold;
text-transform: capitalize;
}
}
// tree
// Bullet
.param-name {
border-left: $line-border;
box-sizing: border-box;
position: relative;
padding: $cell-padding 0;
vertical-align: top;
line-height: $param-name-height;
white-space: nowrap;
font-size: 0.929em;
font-weight: $regular;
> span::before {
content: '';
display: inline-block;
width: $bullet-size;
height: $bullet-size + 6;
background-color: $primary-color;
margin: 0 $bullet-margin;
vertical-align: middle;
}
> span::after {
content: '';
position: absolute;
border-top: $line-border;
width: $bullet-margin;
left: 0;
top: ($param-name-height / 2) + $cell-padding + 1;
}
}
.param:first-of-type {
> .param-name::before {
content: '';
display: block;
position: absolute;
left: -$lines-width;
top: 0;
border-left: $line-border-erase;
height: ($param-name-height / 2) + $cell-padding + 1;
}
}
.param:last-of-type,
.param.last {
> .param-name {
position: relative;
&::after {
content: '';
display: block;
position: absolute;
left: -$lines-width - 1px;
border-left: $line-border-erase;
top: ($param-name-height / 2) + $cell-padding + $lines-width + 1;
background-color: $background-color;
bottom: 0;
}
}
}
.param-wrap:last-of-type > .param-schema {
border-left-color: transparent;
}
.param-schema {
.param-wrap:first-of-type {
.param-name::before {
display: none;
}
}
}
.param-schema.last {
> td {
border-left: 0;
}
}
.param-enum {
color: $text-color;
font-size: 0.95em;
&::before {
content: 'Valid values: ';
}
}
.param-enum {
color: $text-color;
font-size: 0.95em;
&::before {
content: 'Valid values: ';
}
.param-type.array ~ &::before {
content: 'Valid items values: ';
}
}
.param-pattern {
color: $nullable-color;
white-space: nowrap;
&::before,
&::after {
content: '/';
margin: 0 3px;
font-size: 1.2em;
font-weight: bold;
}
}
.param-default {
font-size: 0.95em;
&::before {
content: 'Default: ';
}
}
.param-example {
font-size: 0.95em;
&::before {
content: 'Example: ';
}
}
.param-enum-value,
.param-default-value,
.param-example-value {
font-family: Courier, monospace;
background-color: rgba($secondary-color, 0.02);
border: 1px solid rgba($secondary-color, 0.1);
margin: 2px 3px;
padding: 0.1em 0.2em 0.2em;
border-radius: $border-radius;
color: $text-color;
display: inline-block;
min-width: 20px;
text-align: center;
}

View File

@ -1,55 +0,0 @@
'use strict';
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import {
inject,
TestBed
} from '@angular/core/testing';
import { JsonSchemaLazy } from './json-schema-lazy';
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('JsonSchemaLazy Component', () => {
let builder;
let component;
let fixture;
beforeEach(() => {
fixture = TestBed.createComponent(TestAppComponent);
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema-lazy');
component = <JsonSchemaLazy>debugEl.componentInstance;
spyOn(component, '_loadAfterSelf').and.stub();
});
afterEach(() => {
component._loadAfterSelf.and.callThrough();
});
it('should init component', () => {
expect(component).not.toBeNull();
});
it('should run loadNextToLocation on load', () => {
component.pointer = '#/def';
fixture.detectChanges();
component.load();
expect(component._loadAfterSelf).toHaveBeenCalled();
});
});
});
/** Test component that contains a lazy schema. */
@Component({
selector: 'test-app',
template:
`<json-schema-lazy></json-schema-lazy>`
})
class TestAppComponent {
}

View File

@ -1,100 +0,0 @@
'use strict';
import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input,
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
import { JsonSchema } from './json-schema';
import { OptionsService } from '../../services/options.service';
import { SpecManager } from '../../utils/spec-manager';
var cache = {};
@Component({
selector: 'json-schema-lazy',
entryComponents: [ JsonSchema ],
template: '',
styles: [':host { display:none }']
})
export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit {
@Input() pointer: string;
@Input() absolutePointer: string;
@Input() auto: boolean;
@Input() isRequestSchema: boolean;
@Input() final: boolean = false;
@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:ComponentFactoryResolver, private optionsService:OptionsService, private _renderer: Renderer) {
this.disableLazy = this.optionsService.options.disableLazySchemas;
}
normalizePointer() {
let schema = this.specMgr.byPointer(this.pointer);
return schema && schema.$ref || this.pointer;
}
private _loadAfterSelf() {
var componentFactory = this.resolver.resolveComponentFactory(JsonSchema);
let contextInjector = this.location.parentInjector;
let compRef = this.location.createComponent(componentFactory, null, contextInjector, null);
this.projectComponentInputs(compRef.instance);
this._renderer.setElementAttribute(compRef.location.nativeElement, 'class', this.location.element.nativeElement.className);
compRef.changeDetectorRef.detectChanges();
this.loaded = true;
return compRef;
}
load() {
if (this.disableLazy) return;
if (this.loaded) return;
if (this.pointer) {
this._loadAfterSelf();
}
}
// cache JsonSchema view
loadCached() {
this.pointer = this.normalizePointer();
if (cache[this.pointer]) {
let compRef = cache[this.pointer];
let $element = compRef.location.nativeElement;
// skip caching view with descendant schemas
// as it needs attached controller
let hasDescendants = compRef.instance.descendants && compRef.instance.descendants.length;
if (!this.disableLazy && (hasDescendants || compRef.instance._hasSubSchemas)) {
this._loadAfterSelf();
return;
}
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
this.loaded = true;
} else {
cache[this.pointer] = this._loadAfterSelf();
}
}
projectComponentInputs(instance:JsonSchema) {
Object.assign(instance, this);
}
ngOnInit() {
if (!this.absolutePointer) this.absolutePointer = this.pointer;
}
ngAfterViewInit() {
if (!this.auto && !this.disableLazy) return;
this.loadCached();
}
ngOnDestroy() {
// clear cache
cache = {};
}
}
function insertAfter(newNode, referenceNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

View File

@ -1,107 +0,0 @@
<ng-container [ngSwitch]="schema._widgetType">
<ng-template ngSwitchCase="file">
<span class="param-wrap">
<span class="param-type-file">file</span>
<div *ngIf="schema._produces && !isRequestSchema" class="file produces">
<ul>
<li *ngFor="let type of schema._produces">{{type}}</li>
</ul>
</div>
<div *ngIf="schema._consumes && isRequestSchema" class="file consume">
<ul>
<li *ngFor="let type of schema._consumes">{{type}}</li>
</ul>
</div>
</span>
</ng-template>
<ng-template ngSwitchCase="trivial">
<span class="param-wrap">
<span class="param-type param-type-trivial {{schema.type}}"
[ngClass]="{'with-hint': schema._displayTypeHint, 'array': _isArray}"
title="{{schema._displayTypeHint}}">{{schema._displayType}} {{schema._displayFormat}}
<span class="param-range" *ngIf="schema._range"> {{schema._range}} </span>
</span>
<span *ngIf="schema['x-nullable']" class="param-nullable">Nullable</span>
<div *ngIf="schema.enum" class="param-enum">
<span *ngFor="let enumItem of schema.enum" class="param-enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
</div>
<span *ngIf="schema.pattern" class="param-pattern">{{schema.pattern}}</span>
</span>
</ng-template>
<ng-template ngSwitchCase="tuple">
<div class="params-wrap params-array array-tuple">
<ng-template ngFor [ngForOf]="schema.items" let-item="$implicit" let-idx="index" [ngForTrackBy]="trackByIdx">
<div class="tuple-item">
<span class="tuple-item-index"> [{{idx}}]: </span>
<json-schema class="nested-schema" [pointer]="item._pointer"
[absolutePointer]="item._pointer"
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
</json-schema>
</div>
</ng-template>
</div>
</ng-template>
<ng-template ngSwitchCase="array">
<json-schema class="nested-schema" [pointer]="schema._pointer"
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema"> </json-schema>
</ng-template>
<ng-template ngSwitchCase="object">
<table class="params-wrap" [ngClass]="{'params-array': _isArray}">
<!-- <caption> {{_displayType}} </caption> -->
<ng-template ngFor [ngForOf]="properties" let-prop="$implicit" let-last="last" [ngForTrackBy]="trackByName">
<tr class="param"
[class.last]="last"
[class.discriminator] = "prop.isDiscriminator"
[class.complex] = "prop._pointer"
[class.additional] = "prop._additional"
[class.expanded] = "subSchema.open">
<td class="param-name">
<span class="param-name-wrap" (click)="subSchema.toggle()">
<span class="param-name-content">
{{prop.name}}
<span class="param-name-enumvalue" [hidden]="!prop._enumItem"> {{prop._enumItem?.val | json}} </span>
</span>
<svg *ngIf="prop._pointer" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
</svg>
</span>
</td>
<td class="param-info">
<div>
<span class="param-type {{prop.type}}" [ngClass]="{'with-hint': prop._displayTypeHint, 'tuple': prop._isTuple, 'array': (prop._isArray || prop.type == 'array')}"
title="{{prop._displayTypeHint}}"> {{prop._displayType}} {{prop._displayFormat}}
<span class="param-range" *ngIf="prop._range"> {{prop._range}} </span>
</span>
<span *ngIf="prop._required" class="param-required">Required</span>
<span *ngIf="prop['x-nullable']" class="param-nullable">Nullable</span>
<div class="param-default" *ngIf="prop.default != null">
<span class="param-default-value">{{prop.default | json}}</span>
</div>
<div *ngIf="prop.enum && !prop.isDiscriminator" class="param-enum">
<span *ngFor="let enumItem of prop.enum" class="param-enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
</div>
<span *ngIf="prop.pattern" class="param-pattern">{{prop.pattern}}</span>
</div>
<div class="param-description" [innerHtml]="prop.description | marked"></div>
<div class="discriminator-info" *ngIf="prop.isDiscriminator && descendants.length">
<drop-down (change)="selectDescendantByIdx($event)" [active]="activeDescendant.idx">
<option *ngFor="let descendant of descendants; let i=index"
[value]="i" [attr.selected]="descendant.active ? '' : null" >{{descendant.name}}</option>
</drop-down>
</div>
</td>
</tr>
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
<td colspan="2">
<zippy [attr.disabled]="prop.name" #subSchema title="Expand" [headless]="true" (openChange)="lazySchema.load()" [(open)]="prop.expanded">
<json-schema-lazy #lazySchema [auto]="prop.expanded" class="nested-schema" [pointer]="prop._pointer"
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema" absolutePointer="{{absolutePointer}}/properties/{{prop.name}}">
</json-schema-lazy>
</zippy>
</td>
</tr>
</ng-template>
</table>
</ng-template>
</ng-container>

View File

@ -1,221 +0,0 @@
@import 'json-schema-common';
// styles for array-schema for array
$array-marker-font-sz: 13px;
$array-marker-line-height: 1.5;
:host {
display: block;
}
.param-schema > td {
border-left: $line-border;
padding: 0 10px;
}
.derived-schema {
display: none;
}
.derived-schema.active {
display: block;
}
:host.nested-schema {
background-color: white;
padding: 10px 20px;
position: relative;
border-radius: $border-radius;
&:before, &:after {
content: "";
width: 0;
height: 0;
position: absolute;
top: 0;
border-style: solid;
border-color: transparent;
border-width: 10px 15px 0;
margin-left: -7.5px;
border-top-color: $side-menu-active-bg-color;
}
&:before {
left: 10%;
}
&:after {
right: 10%;
}
.param:first-of-type > .param-name:before, .param:last-of-type > .param-name:after {
border-color: white;
}
}
:host[nestodd="true"] {
background-color: $side-menu-active-bg-color;
border-radius: $border-radius;
&:before, &:after {
border-top-color: white;
}
> .params-wrap > .param:first-of-type > .param-name:before,
> .params-wrap > .param:last-of-type > .param-name:after {
border-color: $side-menu-active-bg-color;
}
> .params-wrap > .param:last-of-type,
> .params-wrap > .param.last {
> .param-name:after {
border-color: $side-menu-active-bg-color;
}
}
}
zippy {
overflow: visible;
}
.zippy-content-wrap {
padding: 0;
}
.param.complex.expanded > .param-info {
border-bottom: 0;
}
.param.complex > .param-name .param-name-wrap {
font-weight: bold;
cursor: pointer;
color: $black;
}
.param.complex > .param-name svg {
height: 1.2em;
width: 1.2em;
vertical-align: middle;
transition: all 0.3s ease;
}
.param.complex.expanded > .param-name svg{
transform: rotateZ(-180deg);
}
.param.additional > .param-name {
color: rgba($black, 0.4);
}
.params-wrap {
width: 100%;
}
table {
border-spacing: 0;
}
.params-wrap.params-array:before, .params-wrap.params-array:after {
display: block;
font-weight: $base-font-weight;
color: $black;
font-size: $array-marker-font-sz;
line-height: $array-marker-line-height;
}
.params-wrap.params-array:after {
content: "]";
font-family: monospace;
}
.params-wrap.params-array:before {
content: "Array [";
padding-top: 1em;
font-family: monospace;
}
.params-wrap.params-array {
padding-left: $bullet-margin;
}
.param-schema.param-array:before {
bottom: ($array-marker-font-sz * $array-marker-line-height) / 2;
width: $bullet-margin;
border-left-style: dashed;
border-bottom: $lines-width dashed $tree-lines-color;
}
.params-wrap.params-array > .param-wrap:first-of-type > .param > .param-name:after {
content: "";
display: block;
position: absolute;
left: -$lines-width;
top: 0;
border-left: $line-border-erase;
height: ($param-name-height/2) + $cell-padding;
}
.params-wrap > .param > .param-schema.param-array {
border-left-color: transparent;
}
.discriminator-info {
margin-top: 5px;
}
.discriminator-wrap:not(.empty) > td {
//border-left: $line-border;
padding: 0;
position: relative;
&:before {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;
border-left: $line-border;
height: ($param-name-height/2) + $cell-padding + 1;
z-index: 1;
}
}
ul, li {
margin: 0;
}
ul {
list-style: none;
padding-left: 1em;
}
li:before {
content: "- ";
font-weight: bold;
}
.array-tuple > .tuple-item {
margin-top: 1.5em;
display: flex;
> span {
flex: 0;
padding: 10px 15px 10px 0;
font-family: monospace;
}
> json-schema {
flex: 1;
&:before, &:after {
display: none;
}
}
}
.param-name-enumvalue {
padding: 2px;
background-color: #e6ebf6;
&:before {
content: " = ";
}
}

View File

@ -1,67 +0,0 @@
'use strict';
import { Component } from '@angular/core';
import {
inject,
TestBed
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { SpecManager } from '../../utils/spec-manager';;
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('JsonSchema Component', () => {
let builder;
let component;
let fixture;
let specMgr;
beforeEach(inject([SpecManager], ( _spec) => {
specMgr = _spec;
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestAppComponent);
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
component = debugEl.componentInstance;
});
it('should init component', () => {
component.pointer = '';
(<SpecManager>specMgr)._schema = {type: 'object'};
fixture.detectChanges();
expect(component).not.toBeNull();
});
it('should set isTrivial for non-object/array types', () => {
component.pointer = '#';
(<any>specMgr)._schema = {type: 'string'};
fixture.detectChanges();
component.schema.isTrivial.should.be.true();
});
it('should use < anything > notation for prop without type', () => {
component.pointer = '#';
(<any>specMgr)._schema = {type: 'object', properties: {
test: {}
}};
fixture.detectChanges();
component.schema._properties[0]._displayType.should.be.equal('< anything >');
});
});
});
/** Test component that contains a json schema. */
@Component({
selector: 'test-app',
template:
`<json-schema></json-schema>`
})
class TestAppComponent {
}

View File

@ -1,203 +0,0 @@
'use strict';
import { Component,
Input,
Renderer,
ElementRef,
OnInit,
ChangeDetectionStrategy,
ChangeDetectorRef
} from '@angular/core';
import { BaseSearchableComponent, SpecManager } from '../base';
import { SchemaNormalizer, SchemaHelper, AppStateService, OptionsService } from '../../services/';
import { JsonPointer, DescendantInfo } from '../../utils/';
import { Zippy } from '../../shared/components';
import { JsonSchemaLazy } from './json-schema-lazy';
@Component({
selector: 'json-schema',
templateUrl: './json-schema.html',
styleUrls: ['./json-schema.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class JsonSchema extends BaseSearchableComponent implements OnInit {
@Input() pointer: string;
@Input() absolutePointer: string;
@Input() final: boolean = false;
@Input() nestOdd: boolean;
@Input() childFor: string;
@Input() isRequestSchema: boolean;
schema: any = {};
activeDescendant:any = {};
discriminator: string = null;
_hasSubSchemas: boolean = false;
properties: any;
_isArray: boolean;
normalizer: SchemaNormalizer;
descendants: DescendantInfo[];
constructor(
specMgr: SpecManager,
app: AppStateService,
private _renderer: Renderer,
private cdr: ChangeDetectorRef,
private _elementRef: ElementRef,
private optionsService: OptionsService) {
super(specMgr, app);
this.normalizer = new SchemaNormalizer(specMgr);
}
get normPointer() {
return this.schema._pointer || this.pointer;
}
selectDescendantByIdx(idx) {
this.selectDescendant(this.descendants[idx]);
}
selectDescendant(activeDescendant: DescendantInfo) {
if (!activeDescendant || activeDescendant.active) return;
this.descendants.forEach(d => {
d.active = false;
});
activeDescendant.active = true;
this.schema = this.specMgr.getDescendant(activeDescendant, this.componentSchema);
this.pointer = this.schema._pointer || activeDescendant.$ref;
this.normalizer.reset();
this.schema = this.normalizer.normalize(this.schema, this.normPointer,
{resolved: true});
this.preprocessSchema();
this.activeDescendant = activeDescendant;
}
initDescendants() {
this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema);
if (!this.descendants.length) return;
let discriminator = this.discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
let discrProperty = this.schema.properties &&
this.schema.properties[discriminator];
if (discrProperty && discrProperty.enum) {
let enumOrder = {};
discrProperty.enum.forEach((enumItem, idx) => {
enumOrder[enumItem] = idx;
});
this.descendants = this.descendants
.filter(a => {
return enumOrder[a.name] != undefined;
}).sort((a, b) => {
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
});
}
this.descendants.forEach((d, idx) => d.idx = idx);
this.selectDescendantByIdx(0);
}
init() {
if (!this.pointer) return;
if (!this.absolutePointer) this.absolutePointer = this.pointer;
this.schema = this.componentSchema;
if (!this.schema) {
throw new Error(`Can't load component schema at ${this.pointer}`);
}
this.applyStyling();
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
this._isArray = this.schema._isArray;
this.absolutePointer += (this._isArray ? '/items' : '');
this.initDescendants();
this.preprocessSchema();
}
preprocessSchema() {
SchemaHelper.preprocess(this.schema, this.normPointer, this.pointer);
if (!this.schema.isTrivial) {
SchemaHelper.preprocessProperties(this.schema, this.normPointer, {
childFor: this.childFor,
discriminator: this.discriminator
});
}
this.properties = this.schema._properties || [];
if (this.isRequestSchema) {
this.properties = this.properties.filter(prop => !prop.readOnly);
}
if (this.optionsService.options.requiredPropsFirst) {
SchemaHelper.moveRequiredPropsFirst(this.properties, this.schema.required);
}
this._hasSubSchemas = this.properties && this.properties.some(
propSchema => {
if (propSchema.type === 'array') {
propSchema = propSchema.items;
}
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
});
if (this.properties.length === 1) {
this.properties[0].expanded = true;
}
}
applyStyling() {
if (this.nestOdd) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
}
}
trackByName(_: number, item: any): number {
return item.name + (item._pointer || '');
}
trackByIdx(idx: number, _: any): number {
return idx;
}
findDescendantWithField(fieldName: string): DescendantInfo {
let res: DescendantInfo;
for (let descendantInfo of this.descendants) {
let schema = this.specMgr.getDescendant(descendantInfo, this.schema);
this.normalizer.reset();
schema = this.normalizer.normalize(schema, this.normPointer,
{resolved: true});
if (schema.properties && schema.properties[fieldName]) {
res = descendantInfo;
break;
};
};
return res;
}
ensureSearchIsShown(ptr: string) {
if (ptr.startsWith(this.absolutePointer)) {
let props = this.properties;
if (!props) return;
let relative = JsonPointer.relative(this.absolutePointer, ptr);
let propName;
if (relative.length > 1 && relative[0] === 'properties') {
propName = relative[1];
}
let prop = props.find(p => p.name === propName);
if (!prop) {
let d = this.findDescendantWithField(propName);
this.selectDescendant(d);
prop = this.properties.find(p => p.name === propName);
}
if (prop && !prop.isTrivial) prop.expanded = true;
this.cdr.markForCheck();
this.cdr.detectChanges();
}
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,21 +0,0 @@
:host {
position: fixed;
top: 0;
left: 0;
right: 0;
display: block;
height: 5px;
z-index: 100;
}
span {
display: block;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: attr(progress percentage);
background-color: #5f7fc3;
transition: right 0.2s linear;
}

View File

@ -1,58 +0,0 @@
'use strict';
import {
Component
} from '@angular/core';
import {
ComponentFixture,
inject,
fakeAsync,
tick,
TestBed,
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { LoadingBar } from './loading-bar';
describe('Redoc components', () => {
describe('Loading Bar', () => {
let component: LoadingBar;
it('should init component', () => {
let fixture = TestBed.createComponent(LoadingBar);
component = fixture.componentInstance;
fixture.detectChanges();
should.exist(component);
component.progress.should.be.equal(0);
component.display.should.be.equal('block');
});
it('should hide itself in 500ms if progress is 100', fakeAsync(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
let fixture = TestBed.createComponent(TestAppComponent);
let parentComp = fixture.componentInstance;
component = getChildDebugElement(fixture.debugElement, 'loading-bar').componentInstance;
// need to pass update through parent component as ngOnChanges is run only for view changes
parentComp.progress = 50;
fixture.detectChanges();
parentComp.progress = 100;
fixture.detectChanges();
component.display.should.be.equal('block');
tick(500);
component.display.should.be.equal('none');
}));
});
});
/** Test component that contains an ApiInfo. */
@Component({
selector: 'test-app',
template:
`<loading-bar [progress]="progress"></loading-bar>`
})
class TestAppComponent {
progress = 0;
}

View File

@ -1,22 +0,0 @@
'use strict';
import { Input, HostBinding, Component, OnChanges } from '@angular/core';
@Component({
selector: 'loading-bar',
template: `
<span [style.width]='progress + "%"'> </span>
`,
styleUrls: ['loading-bar.css']
})
export class LoadingBar implements OnChanges {
@Input() progress:number = 0;
@HostBinding('style.display') display = 'block';
ngOnChanges(ch) {
if (ch.progress.currentValue === 100) {
setTimeout(() => {
this.display = 'none';
}, 500);
}
}
}

View File

@ -1,32 +0,0 @@
<div class="operation" *ngIf="operation">
<div class="operation-content">
<h2 class="operation-header sharable-header" [class.deprecated]="operation.deprecated">
<a class="share-link" href="#{{operation.anchor}}"></a>{{operation.summary}}
</h2>
<endpoint-link *ngIf="pathInMiddlePanel"
[verb]="operation.verb" [path]="operation.path"> </endpoint-link>
<div class="operation-tags" *ngIf="operation.info.tags.length">
<a *ngFor="let tag of operation.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
</div>
<p *ngIf="operation.info.description" class="operation-description"
[innerHtml]="operation.info.description | marked">
</p>
<redoc-externalDocs [docs]="operation.externalDocs"></redoc-externalDocs>
<params-list pointer="{{pointer}}/parameters"> </params-list>
<responses-list pointer="{{pointer}}/responses"> </responses-list>
</div>
<div class="operation-samples">
<endpoint-link *ngIf="!pathInMiddlePanel"
[verb]="operation.verb" [path]="operation.path"> </endpoint-link>
<div>
<request-samples [pointer]="pointer" [schemaPointer]="operation.bodyParam?._pointer">
</request-samples>
</div>
<div>
<br>
<responses-samples pointer="{{pointer}}/responses"> </responses-samples>
</div>
</div>
</div>

View File

@ -1,148 +0,0 @@
@import '../../shared/styles/variables';
:host {
padding-bottom: 100px;
display: block;
border-bottom: 1px solid rgba(127, 127, 127, 0.25);
margin-top: 1em;
transform: translateZ(0);
z-index: 2;
}
// :host:last-of-type {
// border-bottom: 0;
// }
.operation-header {
margin-bottom: calc(1em - 6px);
&.deprecated {
&:after {
content: 'Deprecated';
display: inline-block;
padding: 0 5px;
margin: 0;
background-color: $yellow;
color: white;
font-weight: bold;
font-size: 13px;
vertical-align: text-top;
}
}
}
.operation-tags {
margin-top: 20px;
> a {
font-size: 16px;
color: #999;
display: inline-block;
padding: 0 0.5em;
text-decoration: none;
&:before {
content: '#';
margin-right: -0.4em;
}
&:first-of-type {
padding: 0;
}
}
}
.operation-content, .operation-samples {
display: block;
box-sizing: border-box;
float: left;
}
.operation-content {
width: 100% - $samples-panel-width;
padding: $section-spacing;
}
.operation-samples {
color: $sample-panel-color;
width: 40%;
padding: $section-spacing;
background: $samples-panel-bg-color;
}
.operation-samples pre {
color: $sample-panel-color;
}
.operation-samples header,
.operation-samples > h5 {
color: $sample-panel-headers-color;
text-transform: uppercase;
}
.operation-samples > h5 {
margin-bottom: 8px;
}
.operation-samples schema-sample {
display: block;
}
.operation:after {
content: "";
display: table;
clear:both;
}
.operation-description {
padding: 6px 0 10px 0;
margin: 0;
}
[select-on-click] {
cursor: pointer;
}
@media (max-width: $right-panel-squash-breakpoint) {
.operations:before {
display: none;
}
.operation-samples, .operation-content {
width: 100%;
}
.operation-samples {
margin-top: 2em;
}
:host {
padding-bottom: 0;
}
}
.operation-content /deep/ endpoint-link {
margin-bottom: 16px;
.operation-endpoint[class] {
padding: 5px 30px 5px 5px;
border: 0;
border-bottom: 1px solid $border-color;
border-radius: 0;
background-color: transparent;
}
.operation-api-url-path {
color: $black;
}
.expand-icon {
top: 8px;
background-color: $border-color;
}
.servers-overlay {
border: 1px solid $border-color;
border-top: 0;
}
}

View File

@ -1,65 +0,0 @@
'use strict';
import { Component } from '@angular/core';
import {
inject,
async,
TestBed
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { Operation } from './operation';
import { SpecManager } from '../../utils/spec-manager';;
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';;
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('Operation Component', () => {
let builder;
let component: Operation;
let specMgr;
beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => {
lazyTasks.sync = true;
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
});
beforeEach(() => {
let fixture = TestBed.createComponent(TestAppComponent);
component = getChildDebugElement(fixture.debugElement, 'operation').componentInstance;
fixture.detectChanges();
});
it('should init component', () => {
expect(component).not.toBeNull();
});
it('should init basic component data', () => {
component.operation.verb.should.be.equal('put');
component.operation.path.should.be.equal('/user/{username}');
});
it('should main tag', () => {
component.operation.info.tags.should.be.empty();
});
});
});
/** Test component that contains a Operation. */
@Component({
selector: 'test-app',
template:
`<operation pointer='#/paths/~1user~1{username}/put'></operation>`
})
class TestAppComponent {
}

View File

@ -1,89 +0,0 @@
'use strict';
import { Input, HostBinding, Component, OnInit, ChangeDetectionStrategy, ElementRef } from '@angular/core';
import JsonPointer from '../../utils/JsonPointer';
import { BaseComponent, SpecManager } from '../base';
import { SchemaHelper } from '../../services/schema-helper.service';
import { OptionsService, MenuService } from '../../services/';
import { SwaggerBodyParameter } from '../../utils/swagger-typings';
export interface OperationInfo {
deprecated: boolean;
verb: string;
path: string;
info: {
tags: string[];
description: string;
};
bodyParam: any;
summary: string;
anchor: string;
externalDocs?: {
url: string;
description?: string;
}
}
@Component({
selector: 'operation',
templateUrl: './operation.html',
styleUrls: ['./operation.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Operation extends BaseComponent implements OnInit {
@Input() pointer :string;
@Input() parentTagId :string;
@HostBinding('attr.operation-id') operationId;
operation: OperationInfo;
pathInMiddlePanel: boolean;
constructor(
specMgr:SpecManager,
private optionsService: OptionsService,
private menu: MenuService) {
super(specMgr);
this.pathInMiddlePanel = optionsService.options.pathInMiddlePanel;
}
init() {
this.operationId = this.componentSchema.operationId;
this.operation = {
deprecated: this.componentSchema.deprecated,
verb: JsonPointer.baseName(this.pointer),
path: JsonPointer.baseName(this.pointer, 2),
info: {
description: this.componentSchema.description,
tags: this.filterMainTags(this.componentSchema.tags)
},
bodyParam: this.findBodyParam(),
summary: SchemaHelper.operationSummary(this.componentSchema),
anchor: this.buildAnchor(),
externalDocs: this.componentSchema.externalDocs
};
}
buildAnchor():string {
return this.menu.hashFor(this.pointer,
{ type: 'operation', operationId: this.operationId, pointer: this.pointer },
this.parentTagId );
}
filterMainTags(tags) {
var tagsMap = this.specMgr.getTagsMap();
if (!tags) return [];
return tags.filter(tag => tagsMap[tag] && tagsMap[tag]['x-traitTag']);
}
findBodyParam():SwaggerBodyParameter {
let params = this.specMgr.getOperationParams(this.pointer);
let bodyParam = params.find(param => param.in === 'body');
return bodyParam;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,12 +0,0 @@
<div class="operations">
<div class="tag" *ngFor="let tag of tags; trackBy:trackByTagName" [attr.section]="tag.id">
<div class="tag-info" *ngIf="tag.name">
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.anchor}}"></a>{{tag.name}} </h1>
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
<redoc-externalDocs [docs]="tag.metadata.externalDocs"></redoc-externalDocs>
</div>
<operation *lazyFor="let operation of tag.items; let ready = ready;"
[hidden]="!ready" [pointer]="operation.metadata.pointer"
[parentTagId]="tag.id" [attr.section]="operation.id"></operation>
</div>
</div>

View File

@ -1,37 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
overflow: hidden;
}
:host [hidden] {
display: none;
}
.tag-info {
padding: $section-spacing;
box-sizing: border-box;
width: 60%;
@media (max-width: $right-panel-squash-breakpoint) {
width: 100%;
}
}
.tag-info:after, .tag-info:before {
content: "";
display: table;
}
.tag-info h1 {
color: $headers-color;
text-transform: capitalize;
font-weight: normal;
margin-top: 0;
}
.operations {
display: block;
position: relative;;
}

View File

@ -1,62 +0,0 @@
'use strict';
import { Component } from '@angular/core';
import {
inject,
async,
TestBed
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { OperationsList } from './operations-list';
import { SpecManager } from '../../utils/spec-manager';
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('OperationsList Component', () => {
let builder;
let component;
let fixture;
let specMgr;
beforeEach(async(inject([SpecManager], (_specMgr) => {
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load('/tests/schemas/operations-list-component.json').then(done, done.fail);
});
beforeEach(() => {
fixture = TestBed.createComponent(TestAppComponent);
component = getChildDebugElement(fixture.debugElement, 'operations-list').componentInstance;
fixture.detectChanges();
});
it('should init component and component data', () => {
expect(component).not.toBeNull();
});
it('should build correct tags list', () => {
expect(component.tags).not.toBeNull();
component.tags.should.have.lengthOf(2);
component.tags[0].name.should.be.equal('traitTag');
should.not.exist(component.tags[0].items);
component.tags[1].name.should.be.equal('tag1');
component.tags[1].items.should.have.lengthOf(2);
});
});
});
@Component({
selector: 'test-app',
template:
`<operations-list></operations-list>`
})
class TestAppComponent {
}

View File

@ -1,57 +0,0 @@
'use strict';
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { BaseComponent, SpecManager } from '../base';
import { MenuService } from '../../services/index';
@Component({
selector: 'operations-list',
templateUrl: './operations-list.html',
styleUrls: ['./operations-list.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OperationsList extends BaseComponent implements OnInit {
@Input() pointer:string;
tags:Array<any> = [];
constructor(specMgr:SpecManager, private menu: MenuService) {
super(specMgr);
}
init() {
let flatMenuItems = this.menu.flatItems;
this.tags = [];
let emptyTag = {
name: '',
items: []
};
flatMenuItems.forEach(menuItem => {
// skip items that are not bound to swagger tags/operations
if (!menuItem.metadata) return;
if (menuItem.metadata.type === 'tag') {
this.tags.push({
...menuItem,
anchor: this.buildAnchor(menuItem.id)
});
}
if (menuItem.metadata.type === 'operation' && !menuItem.parent) {
emptyTag.items.push(menuItem);
}
});
if (emptyTag.items.length) this.tags.push(emptyTag);
}
buildAnchor(tagId):string {
return this.menu.hashFor(tagId,
{ type: 'tag'});
}
trackByTagName(_, el) {
return el.name;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,53 +0,0 @@
<h5 class="param-list-header" *ngIf="params.length"> Parameters </h5>
<ng-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>
</header>
<div class="params-wrap">
<div *ngFor="let param of paramType.params" class="param">
<div class="param-name">
<span class="param-name-wrap"> {{param.name}} </span>
</div>
<div class="param-info">
<div>
<span *ngIf='param.type === "array"'
class="param-array-format param-collection-format-{{param.collectionFormat}}">
{{param | collectionFormat}}
</span>
<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="param-default" *ngIf="param.default != null">
<span class="param-default-value">{{param.default | json}}</span>
</div>
<div class="param-example" *ngIf="param.example != null">
<span class="param-example-value">{{param.example | json}}</span>
</div>
<div *ngIf="param.enum || param._enumItem" class="param-enum">
<span *ngFor="let enumItem of param.enum" class="param-enum-value {{enumItem.type}}">
{{enumItem.val | json}}
</span>
<span *ngIf="param._enumItem" class="param-enum-value {{param._enumItem.type}}">
{{param._enumItem.val | json}}
</span>
</div>
<span *ngIf="param.pattern" class="param-pattern">{{param.pattern}}</span>
</div>
<div class="param-description" [innerHtml]="param.description | marked"></div>
</div>
</div>
</div>
</ng-template>
<div *ngIf="bodyParam">
<h5 class="param-list-header" *ngIf="bodyParam"> Request Body </h5>
<div class="body-param-description" [innerHtml]="bodyParam.description | marked"></div>
<div>
<br>
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{bodyParam._pointer}}/schema">
</json-schema-lazy>
</div>
</div>

View File

@ -1,99 +0,0 @@
@import '../../shared/styles/variables';
$hint-color: #999999;
:host {
display: block;
}
.param-list-header {
border-bottom: 1px solid rgba($text-color, .3);
// padding: 0.2em 0;
margin: 3em 0 1em 0;
color: rgba($text-color, .5);
font-weight: normal;
text-transform: uppercase;
}
@import '../JsonSchema/json-schema-common';
header.paramType {
margin: 25px 0 5px 0;
text-transform: capitalize;
}
.param-array-format {
color: black;
font-weight: 300;
}
// paramters can't be multilevel so table representation works for it without javascript
.params-wrap {
display: table;
width: 100%;
}
.param-name {
display: table-cell;
vertical-align: top;
}
.param-info {
display: table-cell;
width: 100%;
}
.param {
display: table-row;
}
.param:last-of-type > .param-name {
border-left: 0;
&:after {
content: "";
display: block;
position: absolute;
left: 0;
border-left: $line-border;
height: ($param-name-height/2) + $cell-padding + $lines-width;
background-color: white;
top: 0;
}
}
.param:first-of-type .param-name:after {
content: "";
display: block;
position: absolute;
left: -$lines-width;
border-left: $line-border-erase;
height: ($param-name-height/2) + $cell-padding;
background-color: white;
top: 0;
}
[data-hint] {
width: 1.2em;
text-align: center;
border-radius: 50%;
vertical-align: middle;
color: $hint-color;
line-height: 1.2;
text-transform: none;
cursor: help;
border: 1px solid $hint-color;
margin-left: 0.5em;
}
@media (max-width: 520px) {
[data-hint] {
float: right;
}
[data-hint]:after {
margin-left: 12px;
transform: translateX(-100%) translateY(-8px);
-moz-transform: translateX(-100%) translateY(-8px);
-webkit-transform: translateX(-100%) translateY(-8px);
}
}

View File

@ -1,88 +0,0 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { OptionsService } from '../../services/options.service';
import { SchemaHelper } from '../../services/schema-helper.service';
import { BaseComponent, SpecManager } from '../base';
function safePush(obj, prop, item) {
if (!obj[prop]) obj[prop] = [];
obj[prop].push(item);
}
@Component({
selector: 'params-list',
templateUrl: './params-list.html',
styleUrls: ['./params-list.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ParamsList extends BaseComponent implements OnInit {
@Input() pointer: string;
params: Array<any>;
empty: boolean;
bodyParam: any;
constructor(specMgr: SpecManager, private options: OptionsService) {
super(specMgr);
}
init() {
this.params = [];
let paramsList = this.specMgr.getOperationParams(this.pointer);
const igrnoredHeaders =
this.specMgr.schema['x-ignoredHeaderParameters'] ||
this.options.options.ignoredHeaderParameters ||
[];
paramsList = paramsList
.map(paramSchema => {
let propPointer = paramSchema._pointer;
if (paramSchema.in === 'body') return paramSchema;
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
})
.filter(param => {
return param.in !== 'header' || igrnoredHeaders.indexOf(param.name) < 0;
});
let paramsMap = this.orderParams(paramsList);
if (paramsMap.body && paramsMap.body.length) {
let bodyParam = paramsMap.body[0];
this.bodyParam = bodyParam;
paramsMap.body = undefined;
}
this.empty = !(Object.keys(paramsMap).length || this.bodyParam);
let paramsPlaces = ['path', 'query', 'formData', 'header', 'body'];
let placeHint = {
path: `Used together with Path Templating, where the parameter value is actually part
of the operation's URL. This does not include the host or base path of the API.
For example, in /items/{itemId}, the path parameter is itemId`,
query: `Parameters that are appended to the URL.
For example, in /items?id=###, the query parameter is id`,
formData: `Parameters that are submitted through a form.
application/x-www-form-urlencoded, multipart/form-data or both are usually
used as the content type of the request`,
header: 'Custom headers that are expected as part of the request'
};
let params = [];
paramsPlaces.forEach(place => {
if (paramsMap[place] && paramsMap[place].length) {
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
}
});
this.params = params;
}
orderParams(params):any {
let res = {};
params.forEach((param) => safePush(res, param.in, param));
return res;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,56 +0,0 @@
@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;
min-height: 350px;
}
@keyframes rotate {
0% {
transform: rotate(0deg); }
100% {
transform: rotate(360deg);
}
}
redoc.loading:before {
font-family: Helvetica;
content: "Loading";
font-size: 24px;
text-align: center;
padding-top: 40px;
color: #0033a0;
font-weight: normal;
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: white;
z-index: 9999;
opacity: 1;
transition: all 0.6s ease-out;
}
redoc.loading:after {
z-index: 10000;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="512" height="512" viewBox="0 0 512 512"><g></g><path d="M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z" fill="#0033a0"/><path d="M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z" fill="#0033a0"/><path d="M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z" fill="#0033a0"/><path d="M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z" fill="#0033a0"/><path d="M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z" fill="#0033a0"/><path d="M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z" fill="#0033a0"/><path d="M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z" fill="#0033a0"/><path d="M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z" fill="#0033a0"/></svg>');
animation: 2s rotate linear infinite;
width: 50px;
height: 50px;
position: absolute;
content: "";
left: 50%;
margin-left: -25px;
background-size: cover;
top: 75px;
transition: all 0.6s ease-out;
opacity: 1;
}
redoc.loading-remove:before, redoc.loading-remove:after {
opacity: 0;
}

View File

@ -1,30 +0,0 @@
<div class="redoc-error" *ngIf="error">
<h1>Oops... ReDoc failed to render this spec</h1>
<div class='redoc-error-details'>{{error.message}}</div>
</div>
<loading-bar *ngIf="options.lazyRendering" [progress]="loadingProgress"> </loading-bar>
<div class="redoc-wrap" *ngIf="specLoaded && !error">
<div class="background">
<div class="background-actual"> </div>
</div>
<div class="menu-content" sticky-sidebar [disable]="specLoading"
[scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
<div class="menu-header">
<api-logo> </api-logo>
<redoc-search> </redoc-search>
</div>
<side-menu> </side-menu>
</div>
<div class="api-content">
<warnings></warnings>
<api-info></api-info>
<operations-list> </operations-list>
<footer>
<div class="powered-by-badge">
<a href="https://github.com/Rebilly/ReDoc" title="Swagger-generated API Reference Documentation" target="_blank">
Powered by <strong>ReDoc</strong>
</a>
</div>
</footer>
</div>
</div>

View File

@ -1,362 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-moz-tap-highlight-color: rgba(0, 0, 0, 0);
-ms-tap-highlight-color: rgba(0, 0, 0, 0);
-o-tap-highlight-color: rgba(0, 0, 0, 0);
tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-smoothing: antialiased;
-webkit-osx-font-smoothing: grayscale;
-moz-osx-font-smoothing: grayscale;
osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
-moz-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
-ms-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
text-rendering: optimizeSpeed !important;
font-smooth: always;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
}
.redoc-wrap {
z-index: 0;
position: relative;
overflow: hidden;
font-family: $base-font, $base-font-family;
font-size: $em-size;
line-height: $base-line-height;
color: $text-color;
}
.menu-content {
overflow: hidden;
display: flex;
flex-direction: column;
}
side-menu {
overflow: hidden;
}
[sticky-sidebar] {
width: $side-bar-width;
background-color: $side-bar-bg-color;
overflow-x: hidden;
transform: translateZ(0);
z-index: 75;
@media (max-width: $side-menu-mobile-breakpoint) {
width: 100%;
bottom: auto !important;
}
}
.api-content {
margin-left: $side-bar-width;
z-index: 50;
position: relative;
// height: 100vh;
// overflow: scroll;
top: 0;
@media (max-width: $side-menu-mobile-breakpoint) {
padding-top: 3em;
margin-left: 0;
}
}
.background {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: $side-bar-width;
z-index: 1;
&-actual {
background: $samples-panel-bg-color;
left: 100% - $samples-panel-width;
right: 0;
top: 0;
bottom: 0;
position: absolute;
}
@media (max-width: $right-panel-squash-breakpoint) {
display: none;
}
}
.redoc-error {
padding: 20px;
text-align: center;
color: $red;
> h2 {
color: $red;
font-size: 40px;
}
}
.redoc-error-details {
max-width: 750px;
margin: 0 auto;
font-size: 18px;
}
/* global menu items styles (search results + menu) */
:host /deep/ {
.menu-item-header > span {
display: inline-block;
vertical-align: middle;
}
.menu-item-header > .operation-type + .menu-item-title {
width: calc(100% - 32px); // 32 = 26px image width + 6px margin left
}
.menu-item-header > .operation-type {
width: 26px;
display: inline-block;
height: 13px;
background-color: #333;
border-radius: 3px;
vertical-align: top;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAACgCAMAAADZ0KclAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAA80lEQVR42uSWSwLCIAxEX+5/aa2QZBJw5UIt9QMdRqSPEAAw/TyvqzZf150NzdXL49qreXwXjeqz9bqN1tgJl/KLyaVrrL7K7gx+1vlNMqy+helOO4rfBGYZiEkq1ubQ3DeKvc97Et+d+e01vIZlLZZqb1WNJFd8ZKYsmv4Hh3H2fDgjMUI5WSExjiEZs7rEZ5T+/jQn9lhgsw53j/e9MQtxqPsbZY54M5fNl/MY/f1s7NbRSkYlYjc0KPsWMrmhIU9933ywxDiSE+upYNH8TdusUotllNvcAUzfnE/NC4OSYyklQhpdl9E4Tw0Cm4/G9xBgAO7VCkjWLOMfAAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-position: 6px 4px;
text-indent: -9000px;
margin-right: 6px;
margin-top: 2px;
&.get {
background-position: 8px -12px;
background-color: $get-color;
}
&.post {
background-position: 6px 4px;
background-color: $post-color;
}
&.put {
background-position: 8px -28px;
background-color: $put-color;
}
&.options {
background-position: 4px -148px;
background-color: $options-color;
}
&.patch {
background-position: 4px -114px;
background-color: $patch-color;
}
&.delete {
background-position: 4px -44px;
background-color: $delete-color;
}
&.basic {
background-position: 5px -79px;
background-color: $basic-color;
}
&.link {
background-position: 4px -131px;
background-color: $link-color;
}
&.head {
background-position: 6px -102px;
background-color: $head-color;
}
}
}
/* global redoc styles */
@for $index from 1 through 5 {
:host /deep/ h#{$index} {
margin-top: 0;
font-family: $headers-font, $headers-font-family;
color: $secondary-color;
font-weight: $headers-font-weight;
line-height: 1.5;
margin-bottom: 0.5em;
}
}
:host /deep/ {
h1 {
font-size: $h1;
color: $headers-color;
}
h2 {
font-size: $h2;
}
h3 {
font-size: $h3;
}
h4 {
font-size: $h4;
}
h5 {
font-size: $h5;
line-height: 20px;
}
p {
font-family: $base-font, $base-font-family;
font-weight: $base-font-weight;
margin: 0;
margin-bottom: 1em;
line-height: $base-line-height;
}
a {
text-decoration: none;
color: $primary-color;
}
p > code {
color: $red;
border: 1px solid rgba(38, 50, 56, 0.1);
}
.hint--inversed {
&:before {
border-top-color: #fff;
}
&:after {
background: #fff;
color: #383838;
}
}
@import '../../shared/styles/share-link';
}
footer {
position: relative;
text-align: right;
padding: 10px $section-spacing;
font-size: 15px;
margin-top: -35px;
color: white;
a {
color: white;
}
strong {
font-size: 18px;
}
}
/* markdown elements */
:host /deep/ .redoc-markdown-block {
pre {
font-family: Courier, monospace;
white-space: pre-wrap;
background-color: #263238;
color: white;
padding: 12px 14px 15px 14px;
overflow-x: auto;
line-height: normal;
border-radius: $border-radius;
border: 1px solid rgba(38, 50, 56, 0.1);
code {
background-color: transparent;
color: white;
&:before,
&:after {
content: none;
}
}
}
code {
font-family: Courier, monospace;
background-color: rgba(38, 50, 56, 0.04);
padding: 0.1em 0.2em 0.2em;
font-size: 1em;
border-radius: $border-radius;
color: $red;
border: 1px solid rgba(38, 50, 56, 0.1);
}
p:last-of-type {
margin-bottom: 0;
}
blockquote {
margin: 0;
margin-bottom: 1em;
padding: 0 15px;
color: #777;
border-left: 4px solid #ddd;
}
img {
max-width: 100%;
box-sizing: content-box;
}
ul,
ol {
padding-left: 2em;
margin: 0;
margin-bottom: 1em;
font-family: $base-font, $base-font-family;
font-weight: $base-font-weight;
line-height: $base-line-height;
> li {
margin: 1em 0;
}
}
table {
display: block;
width: 100%;
overflow: auto;
word-break: normal;
word-break: keep-all;
border-collapse: collapse;
border-spacing: 0;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
table tr {
background-color: #fff;
border-top: 1px solid #ccc;
&:nth-child(2n) {
background-color: #f8f8f8;
}
}
table th,
table td {
padding: 6px 13px;
border: 1px solid #ddd;
}
table th {
text-align: left;
font-weight: bold;
}
}

View File

@ -1,60 +0,0 @@
'use strict';
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import {
inject,
async
} from '@angular/core/testing';
import { TestBed } from '@angular/core/testing';
import { Redoc } from './redoc';
import { SpecManager } from '../../utils/spec-manager';
import { OptionsService } from '../../services/index';
let optsMgr:OptionsService;
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
});
describe('Redoc Component', () => {
let builder;
let specMgr;
beforeEach(async(inject([SpecManager, OptionsService],
( _specMgr, _optsMgr) => {
optsMgr = _optsMgr;
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
})
it('should init component', () => {
let fixture = TestBed.createComponent(TestAppComponent);
let component = getChildDebugElement(fixture.debugElement, 'redoc').componentInstance;
expect(component).not.toBeNull();
fixture.destroy();
});
it('should init components tree without errors', () => {
let fixture = TestBed.createComponent(TestAppComponent);
(() => fixture.detectChanges()).should.not.throw();
fixture.destroy();
});
});
});
/** Test component that contains a Redoc. */
@Component({
selector: 'test-app',
template:
`<redoc disable-lazy-schemas></redoc>`
})
class TestAppComponent {
}

View File

@ -1,162 +0,0 @@
'use strict';
import { ElementRef,
ChangeDetectorRef,
Input,
Component,
OnInit,
OnDestroy,
HostBinding
} from '@angular/core';
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
import { BaseComponent } from '../base';
import * as detectScollParent from 'scrollparent';
import { SpecManager } from '../../utils/spec-manager';
import {
SearchService,
OptionsService,
Options,
Hash,
AppStateService,
SchemaHelper,
MenuService,
Marker
} from '../../services/';
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
function getPreOptions() {
return Redoc._preOptions || {};
}
@Component({
selector: 'redoc',
templateUrl: './redoc.html',
styleUrls: ['./redoc.css'],
providers: [
SpecManager,
MenuService,
SearchService,
LazyTasksService,
Marker
]
//changeDetection: ChangeDetectionStrategy.OnPush
})
export class Redoc extends BaseComponent implements OnInit {
static _preOptions: any = {};
error: any;
specLoaded: boolean;
options: Options;
loadingProgress: number;
@Input() specUrl: string;
@HostBinding('class.loading') specLoading: boolean = false;
@HostBinding('class.loading-remove') specLoadingRemove: boolean = false;
private element: HTMLElement;
private $parent: Element;
private $refElem: Element;
constructor(
specMgr: SpecManager,
optionsMgr: OptionsService,
elementRef: ElementRef,
private changeDetector: ChangeDetectorRef,
private appState: AppStateService,
private lazyTasksService: LazyTasksService,
private hash: Hash
) {
super(specMgr);
SchemaHelper.setSpecManager(specMgr);
// merge options passed before init
optionsMgr.options = getPreOptions();
this.element = elementRef.nativeElement;
this.$parent = this.element.parentElement;
this.$refElem = this.element.nextElementSibling;
//parse options (top level component doesn't support inputs)
optionsMgr.parseOptions( this.element );
let scrollParent = detectScollParent( this.element );
if (scrollParent === (document.scrollingElement || document.documentElement)) scrollParent = window;
optionsMgr.options.$scrollParent = scrollParent;
this.options = optionsMgr.options;
this.lazyTasksService.allSync = !this.options.lazyRendering;
}
hideLoadingAnimation() {
if (this.options.hideLoading) {
return
}
requestAnimationFrame(() => {
this.specLoadingRemove = true;
setTimeout(() => {
this.specLoadingRemove = false;
this.specLoading = false;
}, 400);
});
}
showLoadingAnimation() {
if (this.options.hideLoading) {
return
}
this.specLoading = true;
this.specLoadingRemove = false;
}
load() {
// bunlde spec directly if passsed or load by URL
this.specMgr.load(this.options.spec || this.options.specUrl).catch(err => {
throw err;
});
this.appState.loading.subscribe(loading => {
if (loading) {
this.showLoadingAnimation();
} else {
this.hideLoadingAnimation();
}
});
this.specMgr.spec.subscribe((spec) => {
if (!spec) {
this.appState.startLoading();
} else {
this.specLoaded = true;
this.changeDetector.markForCheck();
this.changeDetector.detectChanges();
setTimeout(() => {
this.hash.start();
});
}
});
}
ngOnInit() {
this.lazyTasksService.loadProgress.subscribe(progress => this.loadingProgress = progress)
this.appState.error.subscribe(_err => {
if (!_err) return;
this.appState.stopLoading();
if (this.loadingProgress === 100) return;
this.error = _err;
this.changeDetector.markForCheck();
});
if (this.specUrl) {
this.options.specUrl = this.specUrl;
}
this.load();
}
ngOnDestroy() {
let $clone = this.element.cloneNode();
this.$parent.insertBefore($clone, this.$refElem);
}
}

View File

@ -1,15 +0,0 @@
<header *ngIf="schemaPointer || samples.length"> Request samples </header>
<schema-sample *ngIf="schemaPointer && !samples.length" [skipReadOnly]="true" [pointer]="schemaPointer"> </schema-sample>
<tabs *ngIf="samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
<tab *ngIf="schemaPointer" tabTitle="JSON">
<schema-sample [pointer]="schemaPointer" [skipReadOnly]="true"> </schema-sample>
</tab>
<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>
</div>
<pre [innerHtml]="sample.source | prism:sample.lang"></pre>
</div>
</tab>
</tabs>

View File

@ -1,81 +0,0 @@
@import '../../shared/styles/variables';
:host {
overflow: hidden;
display: block;
}
.action-buttons {
opacity: 0;
transition: opacity 0.3s ease;
transform: translateY(100%);
z-index: 3;
position: relative;
height: 2em;
line-height: 2em;
padding-right: 10px;
text-align: right;
margin-top: -1em;
> span > a {
padding: 2px 10px;
color: #ffffff;
cursor: pointer;
&:hover {
background-color: lighten($black, 15%);
}
}
}
.code-sample:hover > .action-buttons {
opacity: 1;
}
header {
font-family: $headers-font;
font-size: $h5;
text-transform: uppercase;
margin: 0;
color: $sample-panel-headers-color;
text-transform: uppercase;
font-weight: normal;
margin-top: 20px;
}
:host /deep/ > tabs > ul li {
font-family: $headers-font;
font-size: .9em;
border-radius: $border-radius;
margin: 2px 0;
padding: 3px 10px 2px 10px;
line-height: 16px;
color: $sample-panel-headers-color;
&:hover {
background-color: rgba(white, .1);
color: #ffffff;
}
&.active {
background-color: #ffffff;
color: $text-color;
}
}
:host /deep/ tabs ul {
padding-top: 10px;
}
.code-sample pre {
overflow-x: auto;
word-break: break-all;
word-wrap: break-word;
white-space: pre-wrap;
margin-top: 0;
overflow-x: auto;
padding: 20px;
border-radius: 4px;
background-color: #222d32;
margin-bottom: 36px;
}

View File

@ -1,60 +0,0 @@
'use strict';
import { Component, ViewChildren, QueryList, Input,
ChangeDetectionStrategy, OnInit, HostBinding, ElementRef, NgZone } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { BaseComponent, SpecManager } from '../base';
import JsonPointer from '../../utils/JsonPointer';
import { Tabs } from '../../shared/components/index';
import { AppStateService, ScrollService } from '../../services/index';
@Component({
selector: 'request-samples',
templateUrl: './request-samples.html',
styleUrls: ['./request-samples.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RequestSamples extends BaseComponent implements OnInit {
@Input() pointer:string;
@Input() schemaPointer:string;
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
@HostBinding('attr.hidden') hidden;
childTabs: Tabs;
selectedLang: Subject<any>;
samples: Array<any>;
constructor(
specMgr:SpecManager,
public appState:AppStateService,
private scrollService: ScrollService,
private el: ElementRef,
private zone: NgZone
) {
super(specMgr);
this.selectedLang = this.appState.samplesLanguage;
}
changeLangNotify(lang) {
let relativeScrollPos = this.scrollService.relativeScrollPos(this.el.nativeElement);
this.selectedLang.next(lang);
// do scroll in the end of VM turn to have it seamless
let subscription = this.zone.onMicrotaskEmpty.subscribe(() => {
this.scrollService.scrollTo(this.el.nativeElement, relativeScrollPos);
subscription.unsubscribe();
});
}
init() {
this.schemaPointer = this.schemaPointer ? JsonPointer.join(this.schemaPointer, 'schema') : null;
this.samples = this.componentSchema['x-code-samples'] || [];
if (!this.schemaPointer && !this.samples.length) this.hidden = true;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,26 +0,0 @@
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
[type]="response.type" [(open)]="response.expanded" [empty]="response.empty" (openChange)="lazySchema.load()">
<div *ngIf="response.headers" class="response-headers">
<header>
Headers
</header>
<div class="header" *ngFor="let header of response.headers">
<div class="header-name"> {{header.name}} </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>
<header *ngIf="response.schema">
Response Schema
</header>
<json-schema-lazy [auto]="response.expanded" #lazySchema
pointer="{{response.schema ? response.pointer + '/schema' : null}}">
</json-schema-lazy>
</zippy>

View File

@ -1,61 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
}
.responses-list-header {
font-size: 18px;
padding: 0.2em 0;
margin: 3em 0 1.1em;
color: #253137;
font-weight: normal;
}
:host .zippy-title {
font-family: $headers-font, $headers-font-family;
}
.header-name {
font-weight: bold;
display: inline-block;
}
.header-type {
display: inline-block;
font-weight: bold;
color: #999;
}
header {
font-size: 14px;
font-weight: bold;
text-transform: uppercase;
margin-bottom: 15px;
&:not(:first-child) {
margin-top: 15px;
margin-bottom: 0;
}
}
.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, 0.1);
padding: 0 4px;
color: rgba($primary-color, 0.7);
}
.header-type.array::before {
content: $array-text;
color: $black;
font-weight: $base-font-weight;
}

View File

@ -1,64 +0,0 @@
'use strict';
import { Component } from '@angular/core';
import {
inject,
async,
TestBed,
ComponentFixture
} from '@angular/core/testing';
import { getChildDebugElement } from '../../../tests/helpers';
import { ResponsesList } from './responses-list';
import { SpecManager } from '../../utils/spec-manager';
describe('Redoc components', () => {
describe('ResponsesList Component', () => {
let builder;
let component: ResponsesList;
let fixture: ComponentFixture<ResponsesList>
let specMgr;
beforeEach(async(inject([SpecManager], (_specMgr) => {
specMgr = _specMgr;
})));
beforeEach(done => {
specMgr.load('/tests/schemas/responses-list-component.json').then(done, done.fail);
});
beforeEach(() => {
fixture = TestBed.createComponent(ResponsesList);
component = fixture.componentInstance;
});
it('should instantiate without errors', () => {
should.exist(component);
});
it('should init repsonses list', () => {
component.pointer = '#/paths/~1test1/get/responses';
fixture.detectChanges();
should.exist(component.responses);
component.responses.should.be.lengthOf(2);
});
it('should not overwrite codes for shared schemas', () => {
component.pointer = '#/paths/~1test1/get/responses';
fixture.detectChanges();
let resp1 = component.responses[0];
let resp2 = component.responses[1];
resp1.code.should.not.be.equal(resp2.code);
});
it('should set type of default as error if other 200-399 response is defined', () => {
component.pointer = '#/paths/~1test2/get/responses';
fixture.detectChanges();
let resp1 = component.responses[1];
resp1.type.should.be.equal('error');
});
});
});

View File

@ -1,108 +0,0 @@
'use strict';
import { Component,
Input,
OnInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef
} from '@angular/core';
import { BaseSearchableComponent, SpecManager } from '../base';
import JsonPointer from '../../utils/JsonPointer';
import { statusCodeType } from '../../utils/helpers';
import { OptionsService, AppStateService } from '../../services/index';
import { SchemaHelper } from '../../services/schema-helper.service';
function isNumeric(n) {
return (!isNaN(parseFloat(n)) && isFinite(n));
}
@Component({
selector: 'responses-list',
templateUrl: './responses-list.html',
styleUrls: ['./responses-list.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ResponsesList extends BaseSearchableComponent implements OnInit {
@Input() pointer:string;
responses: Array<any>;
options: any;
constructor(specMgr:SpecManager,
optionsMgr:OptionsService,
app: AppStateService,
private cdr: ChangeDetectorRef
) {
super(specMgr, app);
this.options = optionsMgr.options;
}
init() {
this.responses = [];
let responses = this.componentSchema;
if (!responses) return;
let hasSuccessResponses = false;
let respCodes = Object.keys(responses).filter(respCode => {
if ((parseInt(respCode) >= 100) && (parseInt(respCode) <=399)) {
hasSuccessResponses = true;
}
// only response-codes and "default"
return ( isNumeric(respCode) || (respCode === 'default'));
});
responses = respCodes.map(respCode => {
let resp = responses[respCode];
resp.pointer = JsonPointer.join(this.pointer, respCode);
if (resp.$ref) {
let ref = resp.$ref;
resp = Object.assign({}, this.specMgr.byPointer(resp.$ref));
resp.pointer = ref;
}
resp.empty = !resp.schema;
resp.code = respCode;
resp.type = statusCodeType(resp.code, hasSuccessResponses);
resp.expanded = false;
if (this.options.expandResponses) {
if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) {
resp.expanded = true;
}
}
if (resp.headers && !(resp.headers instanceof Array)) {
resp.headers = Object.keys(resp.headers).map((k) => {
let respInfo = resp.headers[k];
respInfo.name = k;
return SchemaHelper.preprocess(respInfo, this.pointer, this.pointer);
});
resp.empty = false;
}
resp.extendable = resp.headers || resp.length;
return resp;
});
this.responses = responses;
}
trackByCode(_, el) {
return el.code;
}
ensureSearchIsShown(ptr: string) {
if (ptr.startsWith(this.pointer)) {
let code = JsonPointer.relative(this.pointer, ptr)[0];
if (code && this.componentSchema[code]) {
this.componentSchema[code].expanded = true;
this.cdr.markForCheck();
this.cdr.detectChanges();
}
}
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,7 +0,0 @@
<header *ngIf="data.responses.length"> Response samples </header>
<tabs *ngIf="data.responses.length">
<tab *ngFor="let response of data.responses" [tabTitle]="response.code + ' ' + response.description | marked"
[tabStatus]="response.type">
<schema-sample [pointer]="response.pointer"></schema-sample>
</tab>
</tabs>

View File

@ -1,40 +0,0 @@
@import '../../shared/styles/variables';
:host {
overflow: hidden;
display: block;
}
header {
font-family: $headers-font;
font-size: 0.929em;
text-transform: uppercase;
margin: 0;
color: $sample-panel-headers-color;
text-transform: uppercase;
font-weight: normal;
}
:host /deep/ > tabs > ul li {
font-family: $headers-font;
font-size: 0.929em;
border-radius: $border-radius;
margin: 2px 0;
padding: 2px 8px 3px 8px;
color: $sample-panel-headers-color;
line-height: 16px;
&:hover {
color: #ffffff;
background-color: rgba(white, .1);
}
&.active {
background-color: white;
color: $black;
}
}
:host /deep/ tabs ul {
padding-top: 10px;
}

View File

@ -1,66 +0,0 @@
'use strict';
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { BaseComponent, SpecManager } from '../base';
import JsonPointer from '../../utils/JsonPointer';
import { statusCodeType, getJsonLikeSample, getXmlLikeSample } from '../../utils/helpers';
function isNumeric(n) {
return (!isNaN(parseFloat(n)) && isFinite(n));
}
function hasExample(response) {
return response.schema || getXmlLikeSample(response.examples) || getJsonLikeSample(response.examples)
}
@Component({
selector: 'responses-samples',
templateUrl: './responses-samples.html',
styleUrls: ['./responses-samples.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ResponsesSamples extends BaseComponent implements OnInit {
@Input() pointer:string;
data: any;
constructor(specMgr:SpecManager) {
super(specMgr);
}
init() {
this.data = {};
this.data.responses = [];
let responses = this.componentSchema;
if (!responses) return;
let hasSuccessResponses = false;
responses = Object.keys(responses).filter(respCode => {
if ((parseInt(respCode) >= 100) && (parseInt(respCode) <=399)) {
hasSuccessResponses = true;
}
// only response-codes and "default"
return ( isNumeric(respCode) || (respCode === 'default'));
}).map(respCode => {
let resp = responses[respCode];
resp.pointer = JsonPointer.join(this.pointer, respCode);
if (resp.$ref) {
let ref = resp.$ref;
resp = this.specMgr.byPointer(resp.$ref);
resp.pointer = ref;
}
resp.code = respCode;
resp.type = statusCodeType(resp.code, hasSuccessResponses);
return resp;
})
.filter(response => hasExample(response));
this.data.responses = responses;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,34 +0,0 @@
<ng-template #jsonSnippet>
<div class="snippet">
<!-- in case sample is not available for some reason -->
<pre *ngIf="sample == undefined"> Sample unavailable </pre>
<div class="action-buttons">
<span copy-button [copyText]="sample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand all</a> </span>
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
</div>
<pre [innerHtml]="sample | jsonFormatter"></pre>
</div>
</ng-template>
<tabs *ngIf="xmlSample; else jsonSnippet">
<tab tabTitle="JSON" *ngIf="sample">
<ng-container *ngTemplateOutlet="jsonSnippet"></ng-container>
</tab>
<tab tabTitle="XML" *ngIf="xmlSample">
<div class="snippet">
<div class="action-buttons">
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
</div>
<pre class="response-sample" [innerHtml]="xmlSample | prism:'xml'"></pre>
</div>
</tab>
<tab tabTitle="text/plain" *ngIf="textSample">
<div class="snippet">
<div class="action-buttons">
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
</div>
<pre class="response-sample">{{textSample}}</pre>
</div>
</tab>
</tabs>

View File

@ -1,195 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
}
/* tabs */
:host /deep/ tabs {
margin-top: 1em;
> ul {
margin: 0;
padding: 0;
> li {
padding: 2px 10px;
display: inline-block;
background: #131a1d;
border-bottom: 1px solid trasparent;
color: $sample-panel-headers-color;
&.active {
color: white;
border-bottom: 1px solid $sample-panel-headers-color;
}
}
}
.action-buttons {
margin-top: -2em;
}
}
pre {
background-color: transparent;
padding: 0;
margin: 0;
clear: both;
position: relative;
}
.action-buttons {
opacity: 0;
transition: opacity 0.3s ease;
transform: translateY(100%);
z-index: 3;
position: relative;
height: 2em;
line-height: 2em;
padding-right: 10px;
text-align: right;
margin-top: -1em;
> span > a {
padding: 2px 10px;
color: #ffffff;
cursor: pointer;
&:hover {
background-color: lighten($black, 15%);
}
}
}
.snippet:hover .action-buttons {
opacity: 1;
}
:host /deep/ {
.property {
//font-weight: bold;
}
.type-null {
color: gray;
}
.type-boolean {
color: firebrick;
}
.type-number {
color: #4A8BB3;
}
.type-string {
color: #66B16E;
& + a {
color: #66B16E;
text-decoration: underline;
}
}
.callback-function {
color: gray;
}
.collapser:after {
content: "-";
cursor: pointer;
}
.collapsed > .collapser:after {
content: "+";
cursor: pointer;
}
.ellipsis:after {
content: "";
}
.collapsible {
margin-left: 2em;
}
.hoverable {
padding-top: 1px;
padding-bottom: 1px;
padding-left: 2px;
padding-right: 2px;
border-radius: 2px;
}
.hovered {
background-color: rgba(235, 238, 249, 1);
}
.collapser {
padding-right: 6px;
padding-left: 6px;
}
.redoc-json, .response-sample {
overflow-x: auto;
padding: 20px;
border-radius: $border-radius*2;
background-color: darken($black, 2%);
margin-bottom: 36px;
}
ul, .redoc-json ul {
list-style-type: none;
padding: 0px;
margin: 0px 0px 0px 26px;
}
li {
position: relative;
display: block;
}
.hoverable {
transition: background-color .2s ease-out 0s;
-webkit-transition: background-color .2s ease-out 0s;
display: inline-block;
}
.hovered {
transition-delay: .2s;
-webkit-transition-delay: .2s;
}
.selected {
outline-style: solid;
outline-width: 1px;
outline-style: dotted;
}
.collapsed>.collapsible {
display: none;
}
.ellipsis {
display: none;
}
.collapsed > .ellipsis {
display: inherit;
}
.collapser {
position: absolute;
top: 1px;
left: -1.5em;
cursor: default;
user-select: none;
-webkit-user-select: none;
}
// hide top-level collapser
.redoc-json > .collapser {
display: none;
}
}

View File

@ -1,156 +0,0 @@
'use strict';
import { Component, ElementRef, Input, ChangeDetectionStrategy, OnInit } from '@angular/core';
import * as OpenAPISampler from 'openapi-sampler';
import JsonPointer from '../../utils/JsonPointer';
import { BaseComponent, SpecManager } from '../base';
import { SchemaNormalizer } from '../../services/schema-normalizer.service';
import { getJsonLikeSample, getXmlLikeSample, getTextLikeSample } from '../../utils/helpers';
@Component({
selector: 'schema-sample',
templateUrl: './schema-sample.html',
styleUrls: ['./schema-sample.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SchemaSample extends BaseComponent implements OnInit {
@Input() pointer:string;
@Input() skipReadOnly:boolean;
element: any;
sample: any;
xmlSample: string;
textSample: string;
enableButtons: boolean = false;
private _normalizer:SchemaNormalizer;
constructor(specMgr:SpecManager, elementRef:ElementRef) {
super(specMgr);
this.element = elementRef.nativeElement;
this._normalizer = new SchemaNormalizer(specMgr);
}
init() {
this.bindEvents();
let base:any = this.componentSchema;
let sample, xmlSample;
// got pointer not directly to the schema but e.g. to the response obj
if (this.componentSchema.schema) {
base = this.componentSchema;
this.componentSchema = this.componentSchema.schema;
this.pointer += '/schema';
}
// Support x-examples, allowing requests to specify an example.
let examplePointer:string = JsonPointer.join(JsonPointer.dirName(this.pointer), 'x-examples');
let requestExamples:any = this.specMgr.byPointer(examplePointer);
if (requestExamples) {
base.examples = requestExamples;
}
this.xmlSample = base.examples && getXmlLikeSample(base.examples);
this.textSample = base.examples && getTextLikeSample(base.examples);
let jsonLikeSample = base.examples && getJsonLikeSample(base.examples);
if (jsonLikeSample) {
sample = jsonLikeSample;
} else {
let selectedDescendant;
this.componentSchema = this._normalizer.normalize(this.componentSchema, this.pointer);
let discriminator = this.componentSchema.discriminator || this.componentSchema['x-discriminatorBasePointer'];
if (discriminator) {
let descendants = this.specMgr.findDerivedDefinitions(this.componentSchema._pointer || this.pointer, this.componentSchema);
if (descendants.length) {
// TODO: sync up with dropdown
selectedDescendant = descendants[0];
let descSchema = this.specMgr.getDescendant(selectedDescendant, this.componentSchema);
this.componentSchema = this._normalizer.normalize(Object.assign({}, descSchema), selectedDescendant.$ref,
{omitParent: false});
}
}
if (this.fromCache()) {
this.initButtons();
return;
}
try {
sample = OpenAPISampler.sample(this.componentSchema, {
skipReadOnly: this.skipReadOnly
});
} catch(e) {
// no sample available
}
if (selectedDescendant) {
sample[discriminator] = selectedDescendant.name;
}
}
this.cache(sample);
this.sample = sample;
this.initButtons();
}
initButtons() {
if (typeof this.sample === 'object') {
this.enableButtons = true;
}
}
cache(sample) {
if (this.skipReadOnly) {
this.componentSchema['x-redoc-ro-sample'] = sample;
} else {
this.componentSchema['x-redoc-rw-sample'] = sample;
}
}
fromCache() {
if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) {
this.sample = this.componentSchema['x-redoc-ro-sample'];
return true;
} else if (!this.skipReadOnly && this.componentSchema['x-redoc-rw-sample']) {
this.sample = this.componentSchema['x-redoc-rw-sample'];
return true;
}
return false;
}
bindEvents() {
this.element.addEventListener('click', (event) => {
var collapsed, target = event.target;
if (event.target.className === 'collapser') {
collapsed = target.parentNode.getElementsByClassName('collapsible')[0];
if (collapsed.parentNode.classList.contains('collapsed')) {
collapsed.parentNode.classList.remove('collapsed');
} else {
collapsed.parentNode.classList.add('collapsed');
}
}
});
}
expandAll() {
let elements = this.element.getElementsByClassName('collapsible');
for (let i = 0; i < elements.length; i++) {
let collapsed = elements[i];
collapsed.parentNode.classList.remove('collapsed');
}
}
collapseAll() {
let elements = this.element.getElementsByClassName('collapsible');
for (let i = 0; i < elements.length; i++) {
let expanded = elements[i];
if (expanded.parentNode.classList.contains('redoc-json')) continue;
expanded.parentNode.classList.add('collapsed');
}
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,15 +0,0 @@
<div class="search-input-wrap">
<div class="clear-button" *ngIf="searchTerm" (click)="clearSearch()">×</div>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
<path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"/>
</svg>
<input #search (keyup)="update($event, search.value)" [value]="searchTerm" placeholder="Search">
</div>
<ul class="search-results" [hidden]="!items.length">
<li class="result menu-item-header" *ngFor="let item of items"
ngClass="menu-item-depth-{{item.menuItem.depth}} {{item.menuItem.ready ? '' : 'disabled'}}"
(click)="clickSearch(item)">
<span class="operation-type" [ngClass]="item.menuItem?.metadata?.operation" *ngIf="item.menuItem?.metadata?.operation"> {{item.menuItem?.metadata?.operation}} </span><!--
--><span class="menu-item-title">{{item.menuItem.name}}</span>
</li>
</ul>

View File

@ -1,85 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
margin: 10px 0;
}
.search-input-wrap {
padding: 0 20px;
> svg {
width: 13px;
height: 27px;
display: inline-block;
position: absolute;
path {
fill: lighten($text-color, 20%);
}
}
.clear-button {
position: absolute;
display: inline-block;
width: 13px;
text-align: center;
right: 20px;
height: 28px;
line-height: 28px;
vertical-align: middle;
cursor: pointer;
}
}
input {
width: 100%;
box-sizing: border-box;
padding: 5px 20px 5px 20px;
border: 0;
border-bottom: 1px solid darken($side-bar-bg-color, 10%);
font-weight: bold;
font-size: 13px;
color: $text-color;
background-color: transparent;
outline: none;
}
.search-results {
margin: 10px 0 0;
list-style: none;
padding: 10px 0;
background-color: darken($side-bar-bg-color, 5%);
max-height: 100px;
overflow-y: auto;
border-bottom: 1px solid darken($side-bar-bg-color, 10%);
border-top: 1px solid darken($side-bar-bg-color, 10%);
line-height: 1.2;
min-height: 150px;
max-height: 250px;
> li {
display: block;
cursor: pointer;
font-family: Montserrat, sans-serif;
font-size: 13px;
padding: 5px 20px;
&:hover {
background-color: darken($side-bar-bg-color, 10%);
}
}
li.menu-item-depth-1 {
color: $primary-color;
text-transform: uppercase;
}
> li.disabled {
cursor: default;
color: lighten($text-color, 60%);
}
}

View File

@ -1,93 +0,0 @@
'use strict';
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, OnInit, HostBinding } from '@angular/core';
import { Marker, SearchService, MenuService, MenuItem } from '../../services/';
import { throttle } from '../../utils/';
@Component({
selector: 'redoc-search',
styleUrls: ['./redoc-search.css'],
templateUrl: './redoc-search.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RedocSearch implements OnInit {
logo:any = {};
items: { menuItem: MenuItem, pointers: string[] }[] = [];
searchTerm = '';
throttledSearch: Function;
_subscription;
constructor(
cdr: ChangeDetectorRef,
private marker: Marker,
public search: SearchService,
public menu: MenuService) {
this._subscription = menu.changed.subscribe(() => {
cdr.markForCheck();
cdr.detectChanges();
});
this.throttledSearch = throttle(() => {
this.updateSearch();
cdr.markForCheck();
cdr.detectChanges();
}, 300, this);
}
init() {
this.search.indexAll();
}
clearSearch() {
this.searchTerm = '';
this.updateSearch();
}
update(event:KeyboardEvent, val) {
if (event && event.keyCode === 27) { // escape
this.searchTerm = '';
} else {
this.searchTerm = val;
}
this.throttledSearch();
}
updateSearch() {
if (!this.searchTerm || this.searchTerm.length < 2) {
this.items = [];
this.marker.unmark();
return;
}
let searchRes = this.search.search(this.searchTerm);
this.items = Object.keys(searchRes).map(id => ({
menuItem: this.menu.getItemById(id),
pointers: searchRes[id].map(el => el.pointer)
})).filter(res => !!res.menuItem);
this.items.sort((a, b) => {
if (a.menuItem.depth > b.menuItem.depth) return 1;
else if (a.menuItem.depth < b.menuItem.depth) return -1;
else return 0;
});
this.marker.mark(this.searchTerm);
}
clickSearch(item) {
this.search.ensureSearchVisible(
item.pointers
);
this.marker.remark();
this.menu.activate(item.menuItem);
this.menu.scrollToActive();
}
ngOnInit() {
this.init();
}
destroy() {
this._subscription.unsubscribe();
}
}

View File

@ -1,38 +0,0 @@
<div class="security-definition" *ngFor="let def of defs">
<h2 class="sharable-header" attr.section="section/Authentication/{{def.name}}">
<a class="share-link" href="#section/Authentication/{{def.name}}"></a>{{def.name}}</h2>
<div [innerHTML]="def.details.description | marked"></div>
<table class="security-details">
<tr>
<th> Security scheme type: </th>
<td> {{def.details._displayType}} </td>
</tr>
<tr *ngIf="def.details.type === 'apiKey'">
<th> {{def.details.in}} parameter name:</th>
<td> {{def.details.name}} </td>
</tr>
<ng-template [ngIf]="def.details.type === 'oauth2'">
<tr>
<th> OAuth2 Flow</th>
<td> {{def.details.flow}} </td>
</tr>
<tr *ngIf="def.details.flow === 'implicit' || def.details.flow === 'accessCode'">
<th> Authorization URL </th>
<td> {{def.details.authorizationUrl}} </td>
</tr>
<tr *ngIf="def.details.flow !== 'implicit'">
<th> Token URL </th>
<td> {{def.details.tokenUrl}} </td>
</tr>
</ng-template>
</table>
<ng-template [ngIf]="def.details.type === 'oauth2'">
<h3> OAuth2 Scopes </h3>
<table class="security-scopes-details">
<tr *ngFor="let scopeName of def.details.scopes | keys">
<th> {{scopeName}} </th>
<td> {{def.details.scopes[scopeName]}} </td>
</tr>
</table>
</ng-template>
</div>

View File

@ -1,36 +0,0 @@
@import '../../shared/styles/variables';
:host {
display: block;
}
.security-definition:not(:last-of-type) {
border-bottom: 1px solid rgba($text-color, .3);
padding-bottom: 20px;
}
:host h2 {
padding-top: $section-spacing;
}
h3 {
margin: 1em 0;
font-size: 1em;
}
:host .security-scopes-details, :host .security-details {
margin-top: 20px;
}
table.details th, table.details td {
font-weight: bold;
width: 200px;
max-width: 50%;
}
table.details th {
text-align: left;
padding: 6px;
text-transform: capitalize;
font-weight: normal;
}

View File

@ -1,50 +0,0 @@
'use strict';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { SpecManager, BaseComponent } from '../base';
import { ComponentParser } from '../../services/component-parser.service';
const AUTH_TYPES = {
'oauth2': 'OAuth2',
'apiKey': 'API Key',
'basic': 'Basic Authorization'
}
@Component({
selector: 'security-definitions',
styleUrls: ['./security-definitions.css'],
templateUrl: './security-definitions.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SecurityDefinitions extends BaseComponent implements OnInit {
info: any = {};
specUrl: String;
defs: any[];
static insertTagIntoDescription(md:string) {
if (ComponentParser.contains(md, 'security-definitions')) return md;
if (/^#\s?Authentication\s*$/mi.test(md)) return md;
return md + '\n# Authentication \n' + ComponentParser.build('security-definitions');
}
constructor(specMgr:SpecManager) {
super(specMgr);
}
init() {
this.componentSchema = this.componentSchema.securityDefinitions;
this.defs = Object.keys(this.componentSchema).map(name => {
let details = this.componentSchema[name];
details._displayType = AUTH_TYPES[details.type];
return {
name,
details
}
});
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,13 +0,0 @@
<li *ngFor="let item of items; let idx = index" class="menu-item"
ngClass="menu-item-depth-{{item.depth}} {{item.active ? 'active' : ''}} menu-item-for-{{item.metadata?.type}}">
<label class="menu-item-header" [ngClass]="{disabled: !item.ready, deprecated: item?.metadata?.deprecated}" (click)="activateItem(item)">
<span class="operation-type" [ngClass]="item?.metadata?.operation" *ngIf="item?.metadata?.operation"> {{item?.metadata?.operation}} </span><!--
--><span class="menu-item-title">{{item.name}}</span>
<svg *ngIf="item.items?.length" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
</svg>
</label>
<ul *ngIf="item.items" class="menu-subitems">
<side-menu-items [items]="item.items" (activate)="activateItem($event)"> </side-menu-items>
</ul>
</li>

View File

@ -1,134 +0,0 @@
@import '../../shared/styles/variables';
.menu-item-header {
cursor: pointer;
color: rgba($text-color, .9);
-webkit-transition: all .15s ease-in-out;
-moz-transition: all .15s ease-in-out;
-ms-transition: all .15s ease-in-out;
-o-transition: all .15s ease-in-out;
transition: all .15s ease-in-out;
display: block;
margin: 0;
padding: $side-menu-item-vpadding*2.5 $side-menu-item-hpadding;
&[hidden] {
display: none;
}
&.disabled, &.disabled:hover {
cursor: default;
color: lighten($text-color, 60%);
}
&.deprecated {
text-decoration: line-through;
color: lighten($text-color, 60%);
}
display: flex;
justify-content: space-between;
> svg {
height: 18px;
vertical-align: middle;
float: right;
transform: rotateZ(-90deg);
//transition: transform 0.3s ease-out;
polygon {
fill: $border-color;
}
.active > & {
transform: rotateZ(0);
}
}
}
.menu-item {
-webkit-transition: all .15s ease-in-out;
-moz-transition: all .15s ease-in-out;
-ms-transition: all .15s ease-in-out;
-o-transition: all .15s ease-in-out;
transition: all .15s ease-in-out;
list-style: none inside none;
overflow: hidden;
text-overflow: ellipsis;
padding: 0;
}
.menu-subitems {
margin: 0;
font-size: 0.929em;
line-height: 1.2em;
font-weight: $light;
color: rgba($text-color, .9);
padding: 0;
overflow: hidden;
height: 0;
.active > & {
height: auto;
}
}
.menu-item-depth-1 {
> .menu-item-header {
font-family: $headers-font, $headers-font-family;
font-weight: $light;
font-size: $h5;
text-transform: uppercase;
}
// do not capitalize method summuary in level-1 menu
&.menu-item-for-operation > .menu-item-header {
text-transform: none;
}
> .menu-item-header:not(.disabled):hover,
&.active > .menu-item-header {
color: $primary-color;
background: $side-menu-active-bg-color;
}
&.active {
//background: $side-menu-active-bg-color;
}
}
.menu-item-depth-2 {
> .menu-item-header {
padding-left: $side-menu-item-hpadding;
}
> .menu-item-header:hover,
&.active > .menu-item-header {
background: darken($side-menu-active-bg-color, 6%);
}
}
// group items
.menu-item-depth-0 {
margin-top: 15px;
> .menu-subitems {
height: auto;
}
> .menu-item-header {
font-family: $headers-font, $headers-font-family;
color: rgba($text-color, .4);
text-transform: uppercase;
font-size: 0.8em;
padding-bottom: 0;
cursor: default;
> svg {
display: none;
}
}
&:hover,
&.active {
//background: none;
}
}

View File

@ -1,17 +0,0 @@
<div #mobile class="mobile-nav" (click)="toggleMobileNav()">
<span class="selected-item-info">
<span class="selected-tag"> {{activeCatCaption}} </span>
<span class="selected-endpoint">{{activeItemCaption}}</span>
</span>
</div>
<ng-template #default>
<side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items>
</ng-template>
<div #desktop id="resources-nav" perfect-scrollbar>
<ul class="menu-root">
<div *ngIf="itemsTemplate; else default">
<ng-container *ngTemplateOutlet="itemsTemplate; context: this"></ng-container>
</div>
</ul>
</div>

View File

@ -1,90 +0,0 @@
@import '../../shared/styles/variables';
$mobile-menu-compact-breakpoint: 550px;
:host {
display: flex;
box-sizing: border-box;
}
#resources-nav {
position: relative;
width: 100%;
overflow: scroll;
}
ul.menu-root {
margin: 0;
padding: 0;
}
.mobile-nav {
display: none;
height: 3em;
line-height: 3em;
box-sizing: border-box;
border-bottom: 1px solid #ccc;
cursor: pointer;
&:after {
content: "";
display: inline-block;
width: 3em;
height: 3em;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve"><polygon fill="#010101" points="23.1 34.1 51.5 61.7 80 34.1 81.5 35 51.5 64.1 21.5 35 23.1 34.1 "/></svg>');
background-size: 70%;
background-repeat: no-repeat;
background-position: center;
float: right;
vertical-align: middle;
}
}
@media (max-width: $side-menu-mobile-breakpoint) {
:host {
display: block;
}
.mobile-nav {
display: block;
}
#resources-nav {
height: 0;
overflow-y: auto;
transition: all 0.3s ease;
}
.menu-subitems {
height: auto;
}
}
.selected-tag {
text-transform: capitalize;
}
.selected-endpoint:before {
content: "/";
padding: 0 2px;
color: #ccc;
}
.selected-endpoint:empty:before {
display: none;
}
.selected-item-info {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
box-sizing: border-box;
max-width: 350px;
@media (max-width: $mobile-menu-compact-breakpoint) {
display: inline-block;
padding: 0 20px;
max-width: 80%;
max-width: calc(100% - 4em);
}
}

View File

@ -1,96 +0,0 @@
'use strict';
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import { OptionsService, MenuItem } from '../../services/index';
import {
inject,
async
} from '@angular/core/testing';
import { TestBed, ComponentFixture } from '@angular/core/testing';
import { OperationsList, SideMenu } from '../index';
import { SpecManager } from '../../utils/spec-manager';
let testOptions;
describe('Redoc components', () => {
beforeEach(() => {
TestBed.configureTestingModule({ declarations: [ TestAppComponent, OperationsList ] });
});
describe('SideMenu Component', () => {
let builder;
let component: SideMenu;
let fixture: ComponentFixture<TestAppComponent>;
let specMgr;
beforeEach(inject([SpecManager, OptionsService],
(_specMgr, opts) => {
testOptions = opts;
testOptions.options = {
scrollYOffset: () => 0,
$scrollParent: window
};
specMgr = _specMgr;
}));
beforeEach(done => {
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
});
beforeEach(() => {
fixture = TestBed.createComponent(TestAppComponent);
component = getChildDebugElement(fixture.debugElement, 'side-menu').componentInstance;
fixture.detectChanges();
});
afterEach(() => {
if (fixture) fixture.destroy();
});
it('should init component and component data', () => {
should.exist(component);
});
it('should clear active item and cat captions on change to null', () => {
component.activeCatCaption = 'test';
component.activeItemCaption = 'test';
component.changed(null);
component.activeCatCaption.should.be.equal('');
component.activeItemCaption.should.be.equal('');
});
it('should set active item and cat captions on change event', () => {
let parentItem: MenuItem = {
id: 'id',
name: 'Item'
};
component.changed(parentItem);
component.activeCatCaption.should.be.equal(parentItem.name);
component.activeItemCaption.should.be.equal('');
let childItem: MenuItem = {
id: 'id2',
name: 'Child',
parent: parentItem
};
component.changed(childItem);
component.activeCatCaption.should.be.equal(parentItem.name);
component.activeItemCaption.should.be.equal(childItem.name);
});
});
});
/** Test component that contains an ApiInfo. */
@Component({
selector: 'test-app',
template:
`<side-menu></side-menu>
<operations-list></operations-list>`
})
class TestAppComponent {
}

View File

@ -1,163 +0,0 @@
'use strict';
import { Component,
EventEmitter,
Input,
Output,
ElementRef,
ChangeDetectorRef,
ViewChild,
OnInit,
OnDestroy
} from '@angular/core';
import { trigger, state, animate, transition, style } from '@angular/core';
import { ScrollService, MenuService, OptionsService, MenuItem } from '../../services/';
import { PerfectScrollbar } from '../../shared/components';
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
const global = window;
@Component({
selector: 'side-menu-items',
templateUrl: './side-menu-items.html',
styleUrls: ['./side-menu-items.css'],
})
export class SideMenuItems {
@Input() items: MenuItem[];
@Output() activate = new EventEmitter<MenuItem>();
activateItem(item) {
this.activate.next(item);
}
}
@Component({
selector: 'side-menu',
templateUrl: './side-menu.html',
styleUrls: ['./side-menu.css']
})
export class SideMenu implements OnInit, OnDestroy {
activeCatCaption: string;
activeItemCaption: string;
menuItems: Array<MenuItem>;
@Input() itemsTemplate;
@ViewChild(PerfectScrollbar) PS:PerfectScrollbar;
private options: any;
private $element: any;
private $mobileNav: any;
private $resourcesNav: any;
private $scrollParent: any;
private changedActiveSubscription;
private changedSubscription;
constructor(
elementRef:ElementRef,
private scrollService:ScrollService,
private menuService:MenuService,
optionsService:OptionsService,
private detectorRef:ChangeDetectorRef,
) {
this.$element = elementRef.nativeElement;
this.activeCatCaption = '';
this.activeItemCaption = '';
this.options = optionsService.options;
this.changedActiveSubscription = this.menuService.changedActiveItem.subscribe((evt) => this.changed(evt));
this.changedSubscription = this.menuService.changed.subscribe((evt) => {
this.update();
});
}
changed(item) {
if (!item) {
this.activeCatCaption = '';
this.activeItemCaption = '';
return;
}
if (item.parent) {
this.activeItemCaption = item.name;
this.activeCatCaption = item.parent.name;
} else {
this.activeCatCaption = item.name;
this.activeItemCaption = '';
}
// safari doesn't update bindings if not run changeDetector manually :(
this.update();
this.scrollActiveIntoView();
}
update() {
this.detectorRef.detectChanges();
this.PS && this.PS.update();
}
scrollActiveIntoView() {
let $item = this.$element.querySelector('li.active, label.active');
if ($item) $item.scrollIntoViewIfNeeded();
}
activateAndScroll(item) {
if (this.mobileMode) {
this.toggleMobileNav();
}
this.menuService.activate(item);
this.menuService.scrollToActive();
}
init() {
this.menuItems = this.menuService.items;
this.$mobileNav = DOM.querySelector(this.$element, '.mobile-nav');
this.$resourcesNav = DOM.querySelector(this.$element, '#resources-nav');
//decorate scrollYOffset to account mobile nav
this.scrollService.scrollYOffset = () => {
let mobileNavOffset = this.$mobileNav.clientHeight;
return this.options.scrollYOffset() + mobileNavOffset;
};
}
get mobileMode() {
return this.$mobileNav.clientHeight > 0;
}
toggleMobileNav() {
let $overflowParent = (this.options.$scrollParent === global) ? DOM.defaultDoc().body
: this.$scrollParent;
if (DOM.hasStyle(this.$resourcesNav, 'height')) {
DOM.removeStyle(this.$resourcesNav, 'height');
DOM.removeStyle($overflowParent, 'overflow-y');
} else {
let viewportHeight = this.options.$scrollParent.innerHeight
|| this.options.$scrollParent.clientHeight;
let height = viewportHeight - this.$mobileNav.getBoundingClientRect().bottom;
DOM.setStyle($overflowParent, 'overflow-y', 'hidden');
DOM.setStyle(this.$resourcesNav, 'height', height + 'px');
}
}
destroy() {
this.changedActiveSubscription.unsubscribe();
this.changedSubscription.unsubscribe();
this.scrollService.unbind();
this.menuService.destroy();
}
ngOnDestroy() {
this.destroy();
}
ngOnInit() {
this.init();
}
ngAfterViewInit() {
}
}

View File

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

View File

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

View File

@ -1,36 +0,0 @@
'use strict';
import { Component, OnInit } from '@angular/core';
import { SpecManager, BaseComponent } from '../base';
import { WarningsService, OptionsService } from '../../services/index';
@Component({
selector: 'warnings',
styleUrls: ['./warnings.css'],
templateUrl: './warnings.html'
})
export class Warnings extends BaseComponent implements OnInit {
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;
}
ngOnInit() {
this.preinit();
}
}

View File

@ -1,41 +0,0 @@
'use strict';
import { SpecManager } from '../utils/spec-manager';
import { BaseComponent } from '../components/base';
import { OptionsService } from '../services/options.service';
describe('Redoc components', () => {
describe('BaseComponent', () => {
let specMgr;
let component;
beforeAll(() => {
specMgr = new SpecManager(new OptionsService());
specMgr._schema = {tags: []};
});
beforeEach(() => {
component = new BaseComponent(specMgr);
});
it('should set instance properties', () => {
component.specMgr.should.be.equal(specMgr);
//component.schema.should.be.of.type('object');
expect(component.componentSchema).toBeNull();
});
it('should set componentSchema based on pointer on ngOnInit', () => {
component.pointer = '/tags';
component.ngOnInit();
component.componentSchema.should.be.deepEqual(specMgr._schema.tags);
});
it('should call init virtual methods after init', () => {
spyOn(component, 'init');
component.ngOnInit();
component.init.calls.count().should.be.equal(1);
component.init.and.callThrough();
});
});
});

Some files were not shown because too many files have changed in this diff Show More