mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-24 09:33:44 +03:00
Merge commit '8df06dcc19700e954febcd6b5373051e182c6ab2' into releases
This commit is contained in:
commit
45d16b8173
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -33,6 +33,7 @@ lib/**/*.shim.ngstyle.ts
|
||||||
/dist
|
/dist
|
||||||
/demo/build
|
/demo/build
|
||||||
.tmp
|
.tmp
|
||||||
|
compiled
|
||||||
/coverage
|
/coverage
|
||||||
.ghpages-tmp
|
.ghpages-tmp
|
||||||
stats.json
|
stats.json
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.*
|
**/.*
|
||||||
.tmp
|
compiled
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
jspm_packages
|
jspm_packages
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '4.0'
|
- '6'
|
||||||
branches:
|
branches:
|
||||||
except:
|
except:
|
||||||
- releases
|
- releases
|
||||||
|
@ -22,9 +22,7 @@ env:
|
||||||
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
||||||
addons:
|
addons:
|
||||||
sauce_connect: true
|
sauce_connect: true
|
||||||
cache:
|
cache: yarn
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
|
before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
|
||||||
before_script:
|
before_script:
|
||||||
- npm run e2e-server > /dev/null & # kill e2e server
|
- npm run e2e-server > /dev/null & # kill e2e server
|
||||||
|
@ -32,7 +30,7 @@ before_script:
|
||||||
after_script:
|
after_script:
|
||||||
- kill %1 # kill e2e server
|
- kill %1 # kill e2e server
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- if [[ ! -z "$TRAVIS_TAG" ]]; then npm run build:prod-module; fi
|
- if [[ ! -z "$TRAVIS_TAG" ]]; then npm run build:prod; fi
|
||||||
deploy:
|
deploy:
|
||||||
- provider: npm
|
- provider: npm
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
|
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,3 +1,18 @@
|
||||||
|
# 1.7.0 (2017-01-06)
|
||||||
|
### Features/Improvements
|
||||||
|
* Add support for grouping items in menu via [`x-tagGroups`](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-taggroups)
|
||||||
|
* Support inherited discriminator (only one at the moment)
|
||||||
|
* Add support for second-level headings from Markdown docs (by [@jaingaurav](https://github.com/jaingaurav))
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
* Fix response list for shared schemas (fixes [#177](https://github.com/Rebilly/ReDoc/issues/177))
|
||||||
|
* Fix right panel overlaps site-footer
|
||||||
|
|
||||||
|
# 1.6.4 (2016-12-28)
|
||||||
|
### Bug fixes
|
||||||
|
* Fix crash on MS Edge (fixes [#166](https://github.com/Rebilly/ReDoc/issues/166))
|
||||||
|
* Uncomment animation after upgrade to the latest ng2 (resolves [#162](https://github.com/Rebilly/ReDoc/issues/162))
|
||||||
|
|
||||||
# 1.6.3 (2016-12-19)
|
# 1.6.3 (2016-12-19)
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* Disable side-menu animation (workaround for [#162](https://github.com/Rebilly/ReDoc/issues/162))
|
* Disable side-menu animation (workaround for [#162](https://github.com/Rebilly/ReDoc/issues/162))
|
||||||
|
|
23
README.md
23
README.md
|
@ -1,7 +1,7 @@
|
||||||
# ReDoc
|
# ReDoc
|
||||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)
|
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![Dependency Status](https://gemnasium.com/badges/github.com/Rebilly/ReDoc.svg)](https://gemnasium.com/github.com/Rebilly/ReDoc) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)
|
||||||
|
|
||||||
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Rebilly/redoc.svg)](http://isitmaintained.com/project/Rebilly/redoc "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/REBILLY/REDOC.svg)](http://isitmaintained.com/project/REBILLY/REDOC "Percentage of issues still open")
|
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Rebilly/redoc.svg)](http://isitmaintained.com/project/Rebilly/redoc "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/REBILLY/REDOC.svg)](http://isitmaintained.com/project/REBILLY/REDOC "Percentage of issues still open")
|
||||||
|
|
||||||
|
@ -17,15 +17,18 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Extremely easy deployment
|
- Extremely easy deployment
|
||||||
- It’s free and open-source project under MIT license
|
- The widest OpenAPI features support (yes, it supports even `discriminator`) <br>
|
||||||
- The widest OpenAPI features support (yes, it supports even `discriminator`)
|
![](docs/images/discriminator-demo.gif)
|
||||||
- Neat **interactive** documentation for nested objects
|
- Neat **interactive** documentation for nested objects <br>
|
||||||
|
![](docs/images/nested-demo.gif)
|
||||||
<img src="http://i.imgur.com/260gaV4.png" width="500">
|
- Code samples support (via vendor extension) <br>
|
||||||
|
![](docs/images/code-samples-demo.gif)
|
||||||
- Code samples support (via vendor extension)
|
- Progressive loading with `lazy-rendering` options <br>
|
||||||
|
![](docs/images/progressive-loading-demo.gif)
|
||||||
- Responsive three-panel design with menu/scrolling synchronization
|
- Responsive three-panel design with menu/scrolling synchronization
|
||||||
- Integrate API introduction into side menu - ReDoc takes advantage of markdown headings from OpenAPI description field. It pulls them into side menu and also supports deep linking.
|
- Integrate API Introduction into side menu - ReDoc takes advantage of markdown headings from OpenAPI description field. It pulls them into side menu and also supports deep linking.
|
||||||
|
- High-level grouping in side-menu via [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) vendor extension
|
||||||
|
- Multiple ReDoc instances on single page ([example](demo/examples/multiple-apis/index.html))
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [x] performance optimizations
|
- [x] performance optimizations
|
||||||
|
@ -38,7 +41,7 @@
|
||||||
We host the latest and all the previous ReDoc releases on GitHub Pages-based **CDN**:
|
We host the latest and all the previous ReDoc releases on GitHub Pages-based **CDN**:
|
||||||
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
||||||
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
|
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
|
||||||
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js **[not for production]**
|
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js this file is updated with each release of ReDoc and may introduce breaking changes. **Not recommended to use in production.** Use particular release or `v1.x.x`.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"**/.*",
|
"**/.*",
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"tests",
|
"tests",
|
||||||
|
"compiled",
|
||||||
"lib",
|
"lib",
|
||||||
"demo",
|
"demo",
|
||||||
"build",
|
"build",
|
||||||
|
|
0
build/resource-override.js
Normal file
0
build/resource-override.js
Normal file
133
build/webpack.common.js
Normal file
133
build/webpack.common.js
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
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 ngcWebpack = require('ngc-webpack');
|
||||||
|
|
||||||
|
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: 'stream-http'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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$/,
|
||||||
|
loaders: ['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;
|
||||||
|
}
|
|
@ -1,41 +1,24 @@
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
|
||||||
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
||||||
|
|
||||||
const root = require('./helpers').root;
|
const root = require('./helpers').root;
|
||||||
const VERSION = JSON.stringify(require('../package.json').version);
|
const VERSION = JSON.stringify(require('../package.json').version);
|
||||||
const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
||||||
// TODO Refactor common parts of config
|
|
||||||
|
|
||||||
module.exports = {
|
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||||
|
const commonConfig = require('./webpack.common.js');
|
||||||
|
|
||||||
|
module.exports = webpackMerge(commonConfig({
|
||||||
|
IS_PRODUCTION: process.env.NODE_ENV === "production",
|
||||||
|
AOT: false
|
||||||
|
}), {
|
||||||
devtool: '#inline-source-map',
|
devtool: '#inline-source-map',
|
||||||
performance: { hints: false },
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.js', '.json', '.css'],
|
|
||||||
alias: {
|
|
||||||
http: 'stream-http',
|
|
||||||
https: 'stream-http'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
'jquery': 'jquery',
|
|
||||||
'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
fs: "empty",
|
|
||||||
crypto: "empty",
|
|
||||||
global: true,
|
|
||||||
process: true,
|
|
||||||
module: false,
|
|
||||||
clearImmediate: false,
|
|
||||||
setImmediate: false
|
|
||||||
},
|
|
||||||
entry: {
|
entry: {
|
||||||
'redoc': './lib/index.ts',
|
'polyfills': './lib/polyfills.ts',
|
||||||
'vendor': './lib/vendor.ts',
|
'vendor': './lib/vendor.ts',
|
||||||
'polyfills': './lib/polyfills.ts'
|
'redoc': './lib/index.ts',
|
||||||
},
|
},
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: root('demo'),
|
contentBase: root('demo'),
|
||||||
watchContentBase: true,
|
watchContentBase: true,
|
||||||
|
@ -47,87 +30,22 @@ module.exports = {
|
||||||
hot: false,
|
hot: false,
|
||||||
stats: 'errors-only'
|
stats: 'errors-only'
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
|
||||||
path: root('dist'),
|
|
||||||
filename: '[name].js',
|
|
||||||
sourceMapFilename: '[name].[id].map',
|
|
||||||
chunkFilename: '[id].chunk.js',
|
|
||||||
// devtoolModuleFilenameTemplate: "[resource-path]",
|
|
||||||
// devtoolFallbackModuleFilenameTemplate: "[resource-path]?[hash]",
|
|
||||||
},
|
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
exprContextCritical: false,
|
rules: [
|
||||||
rules: [{
|
|
||||||
enforce: 'pre',
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'source-map-loader',
|
|
||||||
exclude: [
|
|
||||||
/node_modules/
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}, {
|
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
loaders: [
|
use: [
|
||||||
'awesome-typescript-loader',
|
'awesome-typescript-loader?{configFileName: "tsconfig.webpack.json"}',
|
||||||
'angular2-template-loader'
|
'angular2-template-loader',
|
||||||
],
|
],
|
||||||
exclude: [/\.(spec|e2e)\.ts$/]
|
exclude: [/\.(spec|e2e)\.ts$/]
|
||||||
}, {
|
|
||||||
test: /lib[\\\/].*\.scss$/,
|
|
||||||
loaders: ['raw-loader', "sass-loader"],
|
|
||||||
exclude: [/redoc-initial-styles\.scss$/]
|
|
||||||
}, {
|
|
||||||
test: /\.scss$/,
|
|
||||||
loaders: ['style-loader', 'css-loader?-import', "sass-loader"],
|
|
||||||
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/]
|
|
||||||
}, {
|
|
||||||
test: /\.css$/,
|
|
||||||
loaders: ['style-loader', 'css-loader?-import'],
|
|
||||||
}, {
|
|
||||||
test: /\.html$/,
|
|
||||||
loader: 'raw-loader'
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
|
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
name: ['vendor', 'polyfills'],
|
name: ['vendor', 'polyfills'],
|
||||||
minChunks: Infinity
|
minChunks: Infinity
|
||||||
}),
|
})
|
||||||
|
]
|
||||||
new webpack.DefinePlugin({
|
})
|
||||||
'IS_PRODUCTION': IS_PRODUCTION,
|
|
||||||
'LIB_VERSION': VERSION,
|
|
||||||
'AOT': IS_PRODUCTION
|
|
||||||
}),
|
|
||||||
|
|
||||||
new ForkCheckerPlugin(),
|
|
||||||
|
|
||||||
new StringReplacePlugin()
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,31 +11,15 @@ const BANNER =
|
||||||
|
|
||||||
const IS_MODULE = process.env.IS_MODULE != null;
|
const IS_MODULE = process.env.IS_MODULE != null;
|
||||||
|
|
||||||
const config = {
|
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||||
context: root(),
|
const commonConfig = require('./webpack.common.js');
|
||||||
devtool: 'source-map',
|
|
||||||
performance: { hints: false },
|
const config = webpackMerge(commonConfig({
|
||||||
|
IS_PRODUCTION: true,
|
||||||
|
AOT: true
|
||||||
|
}), {
|
||||||
|
devtool: 'source-map',
|
||||||
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.js', '.json', '.css'],
|
|
||||||
alias: {
|
|
||||||
http: 'stream-http',
|
|
||||||
https: 'stream-http'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
'jquery': 'jquery',
|
|
||||||
'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
fs: "empty",
|
|
||||||
crypto: "empty",
|
|
||||||
global: true,
|
|
||||||
process: true,
|
|
||||||
module: false,
|
|
||||||
clearImmediate: false,
|
|
||||||
setImmediate: false
|
|
||||||
},
|
|
||||||
entry: {
|
entry: {
|
||||||
'redoc': IS_MODULE ? ['./lib/vendor.ts', './lib/redoc.module.ts'] : ['./lib/polyfills.ts', './lib/vendor.ts', './lib/index.ts']
|
'redoc': IS_MODULE ? ['./lib/vendor.ts', './lib/redoc.module.ts'] : ['./lib/polyfills.ts', './lib/vendor.ts', './lib/index.ts']
|
||||||
},
|
},
|
||||||
|
@ -48,34 +32,18 @@ const config = {
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
umdNamedDefine: true
|
umdNamedDefine: true
|
||||||
},
|
},
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
exprContextCritical: false,
|
rules: [
|
||||||
rules: [{
|
{
|
||||||
enforce: 'pre',
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'source-map-loader',
|
|
||||||
exclude: [
|
|
||||||
/node_modules/
|
|
||||||
]
|
|
||||||
}, {
|
|
||||||
test: /node_modules\/.*\.ngfactory\.ts$/,
|
|
||||||
loader: 'awesome-typescript-loader'
|
|
||||||
}, {
|
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
loader: 'awesome-typescript-loader',
|
use: [
|
||||||
exclude: /(node_modules)/,
|
'awesome-typescript-loader?{configFileName: "tsconfig.webpack.json"}',
|
||||||
}, {
|
'angular2-template-loader',
|
||||||
test: /lib[\\\/].*\.css$/,
|
],
|
||||||
loaders: ['raw-loader'],
|
exclude: [/\.(spec|e2e)\.ts$/]
|
||||||
exclude: [/redoc-initial-styles\.css$/]
|
}
|
||||||
}, {
|
]
|
||||||
test: /\.css$/,
|
|
||||||
loaders: ['style-loader', 'css-loader?-import'],
|
|
||||||
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.css$/]
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
minimize: true,
|
minimize: true,
|
||||||
|
@ -84,7 +52,8 @@ const config = {
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
compress: {
|
compress: {
|
||||||
warnings: false,
|
warnings: false,
|
||||||
screw_ie8: true
|
screw_ie8: true,
|
||||||
|
negate_iife: false // for lazy v8
|
||||||
},
|
},
|
||||||
mangle: { screw_ie8 : true },
|
mangle: { screw_ie8 : true },
|
||||||
output: {
|
output: {
|
||||||
|
@ -92,14 +61,9 @@ const config = {
|
||||||
},
|
},
|
||||||
sourceMap: true
|
sourceMap: true
|
||||||
}),
|
}),
|
||||||
new webpack.BannerPlugin(BANNER),
|
new webpack.BannerPlugin(BANNER)
|
||||||
new webpack.DefinePlugin({
|
]
|
||||||
'IS_PRODUCTION': true,
|
})
|
||||||
'LIB_VERSION': VERSION,
|
|
||||||
'AOT': true
|
|
||||||
})
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IS_MODULE) {
|
if (IS_MODULE) {
|
||||||
config.externals = {
|
config.externals = {
|
||||||
|
@ -114,17 +78,6 @@ if (IS_MODULE) {
|
||||||
'rxjs': 'rxjs',
|
'rxjs': 'rxjs',
|
||||||
'zone.js/dist/zone': 'zone.js/dist/zone'
|
'zone.js/dist/zone': 'zone.js/dist/zone'
|
||||||
};
|
};
|
||||||
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.ts$/,
|
|
||||||
loader: 'angular2-template-loader',
|
|
||||||
exclude: [/\.(spec|e2e)\.ts$/]
|
|
||||||
});
|
|
||||||
|
|
||||||
config.module.rules.push({
|
|
||||||
test: /\.html$/,
|
|
||||||
loader: 'raw-loader'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,73 +1,31 @@
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
const root = require('./helpers').root;
|
const root = require('./helpers').root;
|
||||||
const VERSION = JSON.stringify(require('../package.json').version);
|
|
||||||
|
|
||||||
|
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||||
|
const commonConfig = require('./webpack.common.js');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = webpackMerge(commonConfig({
|
||||||
|
IS_PRODUCTION: true,
|
||||||
|
AOT: false
|
||||||
|
}), {
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
performance: { hints: false },
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.js', '.json', '.css'],
|
|
||||||
alias: {
|
|
||||||
http: 'stream-http',
|
|
||||||
https: 'stream-http'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
'jquery': 'jquery',
|
|
||||||
'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
fs: "empty",
|
|
||||||
crypto: "empty",
|
|
||||||
global: true,
|
|
||||||
process: true,
|
|
||||||
module: false,
|
|
||||||
clearImmediate: false,
|
|
||||||
setImmediate: false
|
|
||||||
},
|
|
||||||
|
|
||||||
output: {
|
|
||||||
path: root('dist'),
|
|
||||||
filename: '[name].js',
|
|
||||||
sourceMapFilename: '[name].map',
|
|
||||||
chunkFilename: '[id].chunk.js'
|
|
||||||
},
|
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
exprContextCritical: false,
|
exprContextCritical: false,
|
||||||
rules: [{
|
rules: [
|
||||||
enforce: 'pre',
|
{
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'source-map-loader',
|
|
||||||
exclude: [
|
|
||||||
/node_modules/
|
|
||||||
]
|
|
||||||
},{
|
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
loaders: [
|
use: 'awesome-typescript-loader'
|
||||||
'awesome-typescript-loader'
|
},
|
||||||
]
|
{
|
||||||
}, {
|
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
loaders: [
|
use: [
|
||||||
'angular2-template-loader'
|
'angular2-template-loader',
|
||||||
],
|
],
|
||||||
exclude: [/\.(spec|e2e)\.ts$/]
|
exclude: [/\.(spec|e2e)\.ts$/]
|
||||||
}, {
|
},
|
||||||
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: /\.html$/,
|
|
||||||
loader: 'raw-loader'
|
|
||||||
}, {
|
|
||||||
/**
|
/**
|
||||||
* Instruments JS files with Istanbul for subsequent code coverage reporting.
|
* Instruments JS files with Istanbul for subsequent code coverage reporting.
|
||||||
* Instrument only testing sources.
|
* Instrument only testing sources.
|
||||||
|
@ -85,11 +43,6 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'IS_PRODUCTION': false,
|
|
||||||
'LIB_VERSION': VERSION,
|
|
||||||
'AOT': 'false'
|
|
||||||
}),
|
|
||||||
new webpack.LoaderOptionsPlugin({
|
new webpack.LoaderOptionsPlugin({
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
sourceMap: false,
|
sourceMap: false,
|
||||||
|
@ -106,4 +59,4 @@ module.exports = {
|
||||||
/(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.css$/ // ignore css files
|
/(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.css$/ // ignore css files
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
}
|
})
|
||||||
|
|
79
demo/examples/multiple-apis/index.html
Normal file
79
demo/examples/multiple-apis/index.html
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<!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;
|
||||||
|
}
|
||||||
|
ul#links_container {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #0033a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
|
@ -51,10 +51,10 @@
|
||||||
|
|
||||||
var $specInput = document.getElementById('spec-input');
|
var $specInput = document.getElementById('spec-input');
|
||||||
|
|
||||||
// $specInput.addEventListener('value-changed', function(e) {
|
$specInput.addEventListener('value-changed', function(e) {
|
||||||
// schemaUrlInput.value = e.detail.value;
|
schemaUrlInput.value = e.detail.value;
|
||||||
// location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||||
// });
|
});
|
||||||
|
|
||||||
function selectItem() {
|
function selectItem() {
|
||||||
let value = this.innerText.trim();
|
let value = this.innerText.trim();
|
||||||
|
|
|
@ -676,6 +676,13 @@ definitions:
|
||||||
description: Category name
|
description: Category name
|
||||||
type: string
|
type: string
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
sub:
|
||||||
|
description: Test Sub Category
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
prop1:
|
||||||
|
type: string
|
||||||
|
description: Dumb Property
|
||||||
xml:
|
xml:
|
||||||
name: Category
|
name: Category
|
||||||
Dog:
|
Dog:
|
||||||
|
@ -747,9 +754,6 @@ definitions:
|
||||||
- photoUrls
|
- photoUrls
|
||||||
discriminator: petType
|
discriminator: petType
|
||||||
properties:
|
properties:
|
||||||
petType:
|
|
||||||
description: Type of a pet
|
|
||||||
type: string
|
|
||||||
id:
|
id:
|
||||||
description: Pet ID
|
description: Pet ID
|
||||||
allOf:
|
allOf:
|
||||||
|
@ -786,6 +790,9 @@ definitions:
|
||||||
- available
|
- available
|
||||||
- pending
|
- pending
|
||||||
- sold
|
- sold
|
||||||
|
petType:
|
||||||
|
description: Type of a pet
|
||||||
|
type: string
|
||||||
xml:
|
xml:
|
||||||
name: Pet
|
name: Pet
|
||||||
Tag:
|
Tag:
|
||||||
|
|
BIN
docs/images/code-samples-demo.gif
Normal file
BIN
docs/images/code-samples-demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 310 KiB |
BIN
docs/images/discriminator-demo.gif
Normal file
BIN
docs/images/discriminator-demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 263 KiB |
BIN
docs/images/nested-demo.gif
Normal file
BIN
docs/images/nested-demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 469 KiB |
BIN
docs/images/progressive-loading-demo.gif
Normal file
BIN
docs/images/progressive-loading-demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 510 KiB |
|
@ -100,7 +100,7 @@ info:
|
||||||
|
|
||||||
### Tag Object vendor extensions
|
### Tag Object vendor extensions
|
||||||
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
||||||
#### x-traitTag [DEPRECATED]
|
#### x-traitTag
|
||||||
| Field Name | Type | Description |
|
| Field Name | Type | Description |
|
||||||
| :------------- | :------: | :---------- |
|
| :------------- | :------: | :---------- |
|
||||||
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
||||||
|
|
|
@ -9,13 +9,13 @@ module.exports = function (config) {
|
||||||
},
|
},
|
||||||
|
|
||||||
coverageReporter: {
|
coverageReporter: {
|
||||||
dir: 'coverage/',
|
type: 'in-memory'
|
||||||
reporters: [
|
},
|
||||||
{type: 'html'},
|
|
||||||
{type: 'lcov'},
|
remapCoverageReporter: {
|
||||||
{type: 'json'},
|
'text-summary': null,
|
||||||
{type: 'text-summary'}
|
'text-lcov': './coverage/lcov.info',
|
||||||
]
|
'html': './coverage/html'
|
||||||
},
|
},
|
||||||
webpack: testWebpackConfig,
|
webpack: testWebpackConfig,
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
|
@ -42,7 +42,7 @@ module.exports = function (config) {
|
||||||
},
|
},
|
||||||
colors: true,
|
colors: true,
|
||||||
singleRun: true,
|
singleRun: true,
|
||||||
reporters: travis ? ['mocha', 'coverage', 'coveralls'] : ['mocha', 'coverage'],
|
reporters: travis ? ['mocha', 'coverage', 'remap-coverage', 'coveralls'] : ['mocha', 'coverage', 'remap-coverage'],
|
||||||
|
|
||||||
browsers: ['PhantomJS'],
|
browsers: ['PhantomJS'],
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { platformBrowser } from '@angular/platform-browser';
|
import { platformBrowser } from '@angular/platform-browser';
|
||||||
import { AppModuleNgFactory } from './app.module.ngfactory';
|
import { AppModuleNgFactory } from '../compiled/lib/app.module.ngfactory';
|
||||||
|
|
||||||
export function bootstrapRedoc() {
|
export function bootstrapRedoc() {
|
||||||
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
|
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
|
||||||
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
||||||
</span>
|
</span>
|
||||||
|
<redoc-externalDocs [docs]="componentSchema.externalDocs"></redoc-externalDocs>
|
||||||
</p>
|
</p>
|
||||||
<span class="redoc-markdown-block">
|
<span class="redoc-markdown-block">
|
||||||
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
|
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
|
||||||
import { SpecManager, BaseComponent } from '../base';
|
import { SpecManager, BaseComponent } from '../base';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService, Marker } from '../../services/index';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'api-info',
|
selector: 'api-info',
|
||||||
|
@ -12,8 +12,13 @@ import { OptionsService } from '../../services/index';
|
||||||
export class ApiInfo extends BaseComponent implements OnInit {
|
export class ApiInfo extends BaseComponent implements OnInit {
|
||||||
info: any = {};
|
info: any = {};
|
||||||
specUrl: String;
|
specUrl: String;
|
||||||
constructor(specMgr: SpecManager, private optionsService: OptionsService) {
|
constructor(specMgr: SpecManager,
|
||||||
|
private optionsService: OptionsService,
|
||||||
|
elRef: ElementRef,
|
||||||
|
marker: Marker
|
||||||
|
) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
|
marker.addElement(elRef.nativeElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
18
lib/components/ExternalDocs/external-docs.ts
Normal file
18
lib/components/ExternalDocs/external-docs.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,6 +72,7 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
||||||
}
|
}
|
||||||
.param-type {
|
.param-type {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
word-break: break-all;
|
||||||
&.array::before, &.tuple::before {
|
&.array::before, &.tuple::before {
|
||||||
color: $black;
|
color: $black;
|
||||||
font-weight: $base-font-weight;
|
font-weight: $base-font-weight;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, Input,
|
import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input,
|
||||||
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
|
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
|
||||||
|
|
||||||
import { JsonSchema } from './json-schema';
|
import { JsonSchema } from './json-schema';
|
||||||
|
@ -15,8 +15,9 @@ var cache = {};
|
||||||
template: '',
|
template: '',
|
||||||
styles: [':host { display:none }']
|
styles: [':host { display:none }']
|
||||||
})
|
})
|
||||||
export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit {
|
||||||
@Input() pointer: string;
|
@Input() pointer: string;
|
||||||
|
@Input() absolutePointer: string;
|
||||||
@Input() auto: boolean;
|
@Input() auto: boolean;
|
||||||
@Input() isRequestSchema: boolean;
|
@Input() isRequestSchema: boolean;
|
||||||
@Input() final: boolean = false;
|
@Input() final: boolean = false;
|
||||||
|
@ -63,7 +64,8 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
// skip caching view with descendant schemas
|
// skip caching view with descendant schemas
|
||||||
// as it needs attached controller
|
// as it needs attached controller
|
||||||
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
|
let hasDescendants = compRef.instance.descendants && compRef.instance.descendants.length;
|
||||||
|
if (!this.disableLazy && (hasDescendants || compRef.instance._hasSubSchemas)) {
|
||||||
this._loadAfterSelf();
|
this._loadAfterSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +80,10 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
Object.assign(instance, this);
|
Object.assign(instance, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||||
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
if (!this.auto && !this.disableLazy) return;
|
if (!this.auto && !this.disableLazy) return;
|
||||||
this.loadCached();
|
this.loadCached();
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
<div class="tuple-item">
|
<div class="tuple-item">
|
||||||
<span class="tuple-item-index"> [{{idx}}]: </span>
|
<span class="tuple-item-index"> [{{idx}}]: </span>
|
||||||
<json-schema class="nested-schema" [pointer]="item._pointer"
|
<json-schema class="nested-schema" [pointer]="item._pointer"
|
||||||
|
[absolutePointer]="item._pointer"
|
||||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||||
</json-schema>
|
</json-schema>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,12 +53,12 @@
|
||||||
'discriminator': prop.isDiscriminator,
|
'discriminator': prop.isDiscriminator,
|
||||||
'complex': prop._pointer,
|
'complex': prop._pointer,
|
||||||
'additional': prop._additional,
|
'additional': prop._additional,
|
||||||
'expanded': subSchema.visible
|
'expanded': subSchema.open
|
||||||
}">
|
}">
|
||||||
<td class="param-name">
|
<td class="param-name">
|
||||||
<span class="param-name-wrap" (click)="subSchema.toggle()">
|
<span class="param-name-wrap" (click)="subSchema.toggle()">
|
||||||
<span class="param-name-content">
|
<span class="param-name-content">
|
||||||
{{prop._name}}
|
{{prop.name}}
|
||||||
<span class="param-name-enumvalue" [hidden]="!prop._enumItem"> {{prop._enumItem?.val | json}} </span>
|
<span class="param-name-enumvalue" [hidden]="!prop._enumItem"> {{prop._enumItem?.val | json}} </span>
|
||||||
</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">
|
<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">
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="param-description" [innerHtml]="prop.description | marked"></div>
|
<div class="param-description" [innerHtml]="prop.description | marked"></div>
|
||||||
<div class="discriminator-info" *ngIf="prop.isDiscriminator">
|
<div class="discriminator-info" *ngIf="prop.isDiscriminator">
|
||||||
<drop-down (change)="selectDescendant($event)">
|
<drop-down (change)="selectDescendantByIdx($event)" [active]="activeDescendant.idx">
|
||||||
<option *ngFor="let descendant of descendants; let i=index"
|
<option *ngFor="let descendant of descendants; let i=index"
|
||||||
[value]="i" [attr.selected]="descendant.active ? '' : null" >{{descendant.name}}</option>
|
[value]="i" [attr.selected]="descendant.active ? '' : null" >{{descendant.name}}</option>
|
||||||
</drop-down>
|
</drop-down>
|
||||||
|
@ -92,9 +93,9 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
|
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<zippy #subSchema title="Expand" [headless]="true" (open)="lazySchema.load()" [visible]="autoExpand">
|
<zippy [attr.disabled]="prop.name" #subSchema title="Expand" [headless]="true" (openChange)="lazySchema.load()" [(open)]="prop.expanded">
|
||||||
<json-schema-lazy #lazySchema [auto]="autoExpand" class="nested-schema" [pointer]="prop._pointer"
|
<json-schema-lazy #lazySchema [auto]="prop.expanded" class="nested-schema" [pointer]="prop._pointer"
|
||||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema" absolutePointer="{{absolutePointer}}/properties/{{prop.name}}">
|
||||||
</json-schema-lazy>
|
</json-schema-lazy>
|
||||||
</zippy>
|
</zippy>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, Input, Renderer, ElementRef, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component,
|
||||||
|
Input,
|
||||||
|
Renderer,
|
||||||
|
ElementRef,
|
||||||
|
OnInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
import { BaseComponent, SpecManager } from '../base';
|
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||||
import { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
import { SchemaNormalizer, SchemaHelper, AppStateService } from '../../services/';
|
||||||
|
import { JsonPointer, DescendantInfo } from '../../utils/';
|
||||||
|
import { Zippy } from '../../shared/components';
|
||||||
|
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'json-schema',
|
selector: 'json-schema',
|
||||||
|
@ -11,8 +21,9 @@ import { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
||||||
styleUrls: ['./json-schema.css'],
|
styleUrls: ['./json-schema.css'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class JsonSchema extends BaseComponent implements OnInit {
|
export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
||||||
@Input() pointer: string;
|
@Input() pointer: string;
|
||||||
|
@Input() absolutePointer: string;
|
||||||
@Input() final: boolean = false;
|
@Input() final: boolean = false;
|
||||||
@Input() nestOdd: boolean;
|
@Input() nestOdd: boolean;
|
||||||
@Input() childFor: string;
|
@Input() childFor: string;
|
||||||
|
@ -20,16 +31,20 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
|
|
||||||
schema: any = {};
|
schema: any = {};
|
||||||
activeDescendant:any = {};
|
activeDescendant:any = {};
|
||||||
hasDescendants: boolean = false;
|
discriminator: string = null;
|
||||||
_hasSubSchemas: boolean = false;
|
_hasSubSchemas: boolean = false;
|
||||||
properties: any;
|
properties: any;
|
||||||
_isArray: boolean;
|
_isArray: boolean;
|
||||||
normalizer: SchemaNormalizer;
|
normalizer: SchemaNormalizer;
|
||||||
autoExpand = false;
|
descendants: DescendantInfo[];
|
||||||
descendants: any;
|
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, private _renderer: Renderer, private _elementRef: ElementRef) {
|
constructor(
|
||||||
super(specMgr);
|
specMgr:SpecManager,
|
||||||
|
app: AppStateService,
|
||||||
|
private _renderer: Renderer,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private _elementRef: ElementRef) {
|
||||||
|
super(specMgr, app);
|
||||||
this.normalizer = new SchemaNormalizer(specMgr);
|
this.normalizer = new SchemaNormalizer(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +52,11 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
return this.schema._pointer || this.pointer;
|
return this.schema._pointer || this.pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectDescendant(idx) {
|
selectDescendantByIdx(idx) {
|
||||||
let activeDescendant = this.descendants[idx];
|
this.selectDescendant(this.descendants[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDescendant(activeDescendant: DescendantInfo) {
|
||||||
if (!activeDescendant || activeDescendant.active) return;
|
if (!activeDescendant || activeDescendant.active) return;
|
||||||
this.descendants.forEach(d => {
|
this.descendants.forEach(d => {
|
||||||
d.active = false;
|
d.active = false;
|
||||||
|
@ -51,13 +69,13 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
this.schema = this.normalizer.normalize(this.schema, this.normPointer,
|
this.schema = this.normalizer.normalize(this.schema, this.normPointer,
|
||||||
{resolved: true});
|
{resolved: true});
|
||||||
this.preprocessSchema();
|
this.preprocessSchema();
|
||||||
|
this.activeDescendant = activeDescendant;
|
||||||
}
|
}
|
||||||
|
|
||||||
initDescendants() {
|
initDescendants() {
|
||||||
this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema);
|
this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema);
|
||||||
if (!this.descendants.length) return;
|
if (!this.descendants.length) return;
|
||||||
this.hasDescendants = true;
|
let discriminator = this.discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
|
||||||
let discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
|
|
||||||
let discrProperty = this.schema.properties &&
|
let discrProperty = this.schema.properties &&
|
||||||
this.schema.properties[discriminator];
|
this.schema.properties[discriminator];
|
||||||
if (discrProperty && discrProperty.enum) {
|
if (discrProperty && discrProperty.enum) {
|
||||||
|
@ -72,12 +90,15 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
}).sort((a, b) => {
|
}).sort((a, b) => {
|
||||||
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
||||||
});
|
});
|
||||||
|
this.descendants.forEach((d, idx) => d.idx = idx);
|
||||||
}
|
}
|
||||||
this.selectDescendant(0);
|
this.selectDescendantByIdx(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (!this.pointer) return;
|
if (!this.pointer) return;
|
||||||
|
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||||
|
|
||||||
this.schema = this.componentSchema;
|
this.schema = this.componentSchema;
|
||||||
if (!this.schema) {
|
if (!this.schema) {
|
||||||
throw new Error(`Can't load component schema at ${this.pointer}`);
|
throw new Error(`Can't load component schema at ${this.pointer}`);
|
||||||
|
@ -88,6 +109,7 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
|
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
|
||||||
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
|
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
|
||||||
this._isArray = this.schema._isArray;
|
this._isArray = this.schema._isArray;
|
||||||
|
this.absolutePointer += (this._isArray ? '/items' : '');
|
||||||
this.initDescendants();
|
this.initDescendants();
|
||||||
this.preprocessSchema();
|
this.preprocessSchema();
|
||||||
}
|
}
|
||||||
|
@ -97,11 +119,12 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
|
|
||||||
if (!this.schema.isTrivial) {
|
if (!this.schema.isTrivial) {
|
||||||
SchemaHelper.preprocessProperties(this.schema, this.normPointer, {
|
SchemaHelper.preprocessProperties(this.schema, this.normPointer, {
|
||||||
childFor: this.childFor
|
childFor: this.childFor,
|
||||||
|
discriminator: this.discriminator
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.properties = this.schema._properties;
|
this.properties = this.schema._properties || [];
|
||||||
if (this.isRequestSchema) {
|
if (this.isRequestSchema) {
|
||||||
this.properties = this.properties && this.properties.filter(prop => !prop.readOnly);
|
this.properties = this.properties && this.properties.filter(prop => !prop.readOnly);
|
||||||
}
|
}
|
||||||
|
@ -114,7 +137,9 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
|
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.autoExpand = this.properties && this.properties.length === 1;
|
if (this.properties.length === 1) {
|
||||||
|
this.properties[0].expanded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyling() {
|
applyStyling() {
|
||||||
|
@ -127,6 +152,46 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
||||||
return item.name + (item._pointer || '');
|
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() {
|
ngOnInit() {
|
||||||
this.preinit();
|
this.preinit();
|
||||||
}
|
}
|
||||||
|
|
21
lib/components/LoadingBar/loading-bar.scss
Normal file
21
lib/components/LoadingBar/loading-bar.scss
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
: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;
|
||||||
|
}
|
58
lib/components/LoadingBar/loading-bar.spec.ts
Normal file
58
lib/components/LoadingBar/loading-bar.spec.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
'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;
|
||||||
|
}
|
|
@ -6,29 +6,7 @@ import { Input, HostBinding, Component, OnChanges } from '@angular/core';
|
||||||
template: `
|
template: `
|
||||||
<span [style.width]='progress + "%"'> </span>
|
<span [style.width]='progress + "%"'> </span>
|
||||||
`,
|
`,
|
||||||
styles: [`
|
styleUrls: ['loading-bar.scss']
|
||||||
: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;
|
|
||||||
}
|
|
||||||
`]
|
|
||||||
})
|
})
|
||||||
export class LoadingBar implements OnChanges {
|
export class LoadingBar implements OnChanges {
|
||||||
@Input() progress:number = 0;
|
@Input() progress:number = 0;
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<p *ngIf="method.info.description" class="method-description"
|
<p *ngIf="method.info.description" class="method-description"
|
||||||
[innerHtml]="method.info.description | marked">
|
[innerHtml]="method.info.description | marked">
|
||||||
</p>
|
</p>
|
||||||
|
<redoc-externalDocs [docs]="method.externalDocs"></redoc-externalDocs>
|
||||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,10 @@ interface MethodInfo {
|
||||||
bodyParam: any;
|
bodyParam: any;
|
||||||
summary: any;
|
summary: any;
|
||||||
anchor: any;
|
anchor: any;
|
||||||
|
externalDocs: {
|
||||||
|
url: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -50,7 +54,8 @@ export class Method extends BaseComponent implements OnInit {
|
||||||
bodyParam: this.findBodyParam(),
|
bodyParam: this.findBodyParam(),
|
||||||
summary: SchemaHelper.methodSummary(this.componentSchema),
|
summary: SchemaHelper.methodSummary(this.componentSchema),
|
||||||
apiUrl: this.getBaseUrl(),
|
apiUrl: this.getBaseUrl(),
|
||||||
anchor: this.buildAnchor()
|
anchor: this.buildAnchor(),
|
||||||
|
externalDocs: this.componentSchema.externalDocs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +82,7 @@ export class Method extends BaseComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
findBodyParam() {
|
findBodyParam() {
|
||||||
let pathParams = this.specMgr.getMethodParams(this.pointer, true);
|
let pathParams = this.specMgr.getMethodParams(this.pointer);
|
||||||
let bodyParam = pathParams.find(param => param.in === 'body');
|
let bodyParam = pathParams.find(param => param.in === 'body');
|
||||||
return bodyParam;
|
return bodyParam;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div class="tag-info" *ngIf="tag.name">
|
<div class="tag-info" *ngIf="tag.name">
|
||||||
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.id}}"></a>{{tag.name}} </h1>
|
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.id}}"></a>{{tag.name}} </h1>
|
||||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||||
|
<redoc-externalDocs [docs]="tag.metadata.externalDocs"></redoc-externalDocs>
|
||||||
</div>
|
</div>
|
||||||
<method *lazyFor="let methodItem of tag.items; let ready = ready;"
|
<method *lazyFor="let methodItem of tag.items; let ready = ready;"
|
||||||
[hidden]="!ready" [pointer]="methodItem.metadata.pointer"
|
[hidden]="!ready" [pointer]="methodItem.metadata.pointer"
|
||||||
|
|
|
@ -27,12 +27,11 @@ export class ParamsList extends BaseComponent implements OnInit {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.params = [];
|
this.params = [];
|
||||||
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
|
let paramsList = this.specMgr.getMethodParams(this.pointer);
|
||||||
|
|
||||||
paramsList = paramsList.map(paramSchema => {
|
paramsList = paramsList.map(paramSchema => {
|
||||||
let propPointer = paramSchema._pointer;
|
let propPointer = paramSchema._pointer;
|
||||||
if (paramSchema.in === 'body') return paramSchema;
|
if (paramSchema.in === 'body') return paramSchema;
|
||||||
paramSchema._name = paramSchema.name;
|
|
||||||
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
|
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,10 @@
|
||||||
<div class="background-actual"> </div>
|
<div class="background-actual"> </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||||
|
<div class="menu-header">
|
||||||
<api-logo> </api-logo>
|
<api-logo> </api-logo>
|
||||||
|
<redoc-search> </redoc-search>
|
||||||
|
</div>
|
||||||
<side-menu> </side-menu>
|
<side-menu> </side-menu>
|
||||||
</div>
|
</div>
|
||||||
<div class="api-content">
|
<div class="api-content">
|
||||||
|
|
|
@ -40,12 +40,17 @@
|
||||||
|
|
||||||
.menu-content {
|
.menu-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
side-menu {
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
[sticky-sidebar] {
|
[sticky-sidebar] {
|
||||||
width: $side-bar-width;
|
width: $side-bar-width;
|
||||||
background-color: $side-bar-bg-color;
|
background-color: $side-bar-bg-color;
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
z-index: 75;
|
z-index: 75;
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { BaseComponent } from '../base';
|
||||||
import * as detectScollParent from 'scrollparent';
|
import * as detectScollParent from 'scrollparent';
|
||||||
|
|
||||||
import { SpecManager } from '../../utils/spec-manager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
import { OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/index';
|
import { SearchService, OptionsService, Hash, AppStateService, SchemaHelper } from '../../services/';
|
||||||
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||||
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
|
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
|
||||||
[type]="response.type" [visible]="response.expanded" [empty]="response.empty" (open)="lazySchema.load()">
|
[type]="response.type" [(open)]="response.expanded" [empty]="response.empty" (openChange)="lazySchema.load()">
|
||||||
<div *ngIf="response.headers" class="response-headers">
|
<div *ngIf="response.headers" class="response-headers">
|
||||||
<header>
|
<header>
|
||||||
Headers
|
Headers
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
<header *ngIf="response.schema">
|
<header *ngIf="response.schema">
|
||||||
Response Schema
|
Response Schema
|
||||||
</header>
|
</header>
|
||||||
<json-schema-lazy [auto]="response.expanded" #lazySchema pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
<json-schema-lazy [auto]="response.expanded" #lazySchema
|
||||||
|
pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
||||||
</json-schema-lazy>
|
</json-schema-lazy>
|
||||||
</zippy>
|
</zippy>
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component,
|
||||||
import { BaseComponent, SpecManager } from '../base';
|
Input,
|
||||||
|
OnInit,
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
ChangeDetectorRef
|
||||||
|
} from '@angular/core';
|
||||||
|
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||||
import JsonPointer from '../../utils/JsonPointer';
|
import JsonPointer from '../../utils/JsonPointer';
|
||||||
import { statusCodeType } from '../../utils/helpers';
|
import { statusCodeType } from '../../utils/helpers';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService, AppStateService } from '../../services/index';
|
||||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||||
|
|
||||||
function isNumeric(n) {
|
function isNumeric(n) {
|
||||||
|
@ -17,14 +23,18 @@ function isNumeric(n) {
|
||||||
styleUrls: ['./responses-list.css'],
|
styleUrls: ['./responses-list.css'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class ResponsesList extends BaseComponent implements OnInit {
|
export class ResponsesList extends BaseSearchableComponent implements OnInit {
|
||||||
@Input() pointer:string;
|
@Input() pointer:string;
|
||||||
|
|
||||||
responses: Array<any>;
|
responses: Array<any>;
|
||||||
options: any;
|
options: any;
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
constructor(specMgr:SpecManager,
|
||||||
super(specMgr);
|
optionsMgr:OptionsService,
|
||||||
|
app: AppStateService,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
super(specMgr, app);
|
||||||
this.options = optionsMgr.options;
|
this.options = optionsMgr.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +60,7 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
||||||
resp.code = respCode;
|
resp.code = respCode;
|
||||||
resp.type = statusCodeType(resp.code);
|
resp.type = statusCodeType(resp.code);
|
||||||
|
|
||||||
|
resp.expanded = false;
|
||||||
if (this.options.expandResponses) {
|
if (this.options.expandResponses) {
|
||||||
if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) {
|
if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) {
|
||||||
resp.expanded = true;
|
resp.expanded = true;
|
||||||
|
@ -74,6 +85,17 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
||||||
return el.code;
|
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() {
|
ngOnInit() {
|
||||||
this.preinit();
|
this.preinit();
|
||||||
}
|
}
|
||||||
|
|
13
lib/components/Search/redoc-search.html
Normal file
13
lib/components/Search/redoc-search.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<div class="search-input-wrap">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" 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" *ngFor="let item of items"
|
||||||
|
ngClass="menu-item-depth-{{item.menuItem.depth}} {{item.menuItem.ready ? '' : 'disabled'}}"
|
||||||
|
(click)="clickSearch(item)">
|
||||||
|
{{item.menuItem.name}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
72
lib/components/Search/redoc-search.scss
Normal file
72
lib/components/Search/redoc-search.scss
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
@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%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 5px 5px 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%);
|
||||||
|
|
||||||
|
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: #0033a0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
> li.disabled {
|
||||||
|
cursor: default;
|
||||||
|
color: lighten($text-color, 60%);
|
||||||
|
}
|
||||||
|
}
|
88
lib/components/Search/redoc-search.ts
Normal file
88
lib/components/Search/redoc-search.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
'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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.flatIdx);
|
||||||
|
this.menu.scrollToActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this._subscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
<div #mobile class="mobile-nav" (click)="toggleMobileNav()">
|
<div #mobile class="mobile-nav" (click)="toggleMobileNav()">
|
||||||
<span class="menu-header"> API Reference: </span>
|
|
||||||
<span class="selected-item-info">
|
<span class="selected-item-info">
|
||||||
<span class="selected-tag"> {{activeCatCaption}} </span>
|
<span class="selected-tag"> {{activeCatCaption}} </span>
|
||||||
<span class="selected-endpoint">{{activeItemCaption}}</span>
|
<span class="selected-endpoint">{{activeItemCaption}}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div #desktop id="resources-nav">
|
<div #desktop id="resources-nav">
|
||||||
<h5 class="menu-header"> API reference </h5>
|
|
||||||
<ul class="menu-root">
|
<ul class="menu-root">
|
||||||
<side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items>
|
<side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -11,13 +11,6 @@ ul.menu-root {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-header {
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: $headers-color;
|
|
||||||
padding: 0 $side-menu-item-hpadding;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav {
|
.mobile-nav {
|
||||||
display: none;
|
display: none;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
|
@ -39,15 +32,6 @@ ul.menu-root {
|
||||||
float: right;
|
float: right;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-header {
|
|
||||||
padding: 0 10px 0 20px;
|
|
||||||
font-size: 0.95em;
|
|
||||||
|
|
||||||
@media (max-width: $mobile-menu-compact-breakpoint) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||||
|
@ -61,10 +45,6 @@ ul.menu-root {
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#resources-nav .menu-header {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-subitems {
|
.menu-subitems {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
|
|
||||||
import { getChildDebugElement } from '../../../tests/helpers';
|
import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService, MenuItem } from '../../services/index';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
inject,
|
inject,
|
||||||
async
|
async
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||||
|
|
||||||
import { MethodsList, SideMenu } from '../index';
|
import { MethodsList, SideMenu } from '../index';
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
describe('SideMenu Component', () => {
|
describe('SideMenu Component', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component: SideMenu;
|
||||||
let fixture;
|
let fixture: ComponentFixture<TestAppComponent>;
|
||||||
let specMgr;
|
let specMgr;
|
||||||
|
|
||||||
beforeEach(inject([SpecManager, OptionsService],
|
beforeEach(inject([SpecManager, OptionsService],
|
||||||
|
@ -53,8 +53,34 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init component and component data', () => {
|
it('should init component and component data', () => {
|
||||||
expect(component).not.toBeNull();
|
should.exist(component);
|
||||||
expect(component.data).not.toBeNull();
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Component, EventEmitter, Input, Output, ElementRef, ChangeDetectorRef,
|
||||||
//import { global } from '@angular/core/src/facade/lang';
|
//import { global } from '@angular/core/src/facade/lang';
|
||||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||||
import { BaseComponent, SpecManager } from '../base';
|
import { BaseComponent, SpecManager } from '../base';
|
||||||
import { ScrollService, MenuService, OptionsService, MenuItem } from '../../services/';
|
import { ScrollService, MenuService, OptionsService, MenuItem, Marker} from '../../services/';
|
||||||
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
|
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
|
||||||
|
|
||||||
const global = window;
|
const global = window;
|
||||||
|
@ -55,7 +55,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, elementRef:ElementRef,
|
constructor(specMgr:SpecManager, elementRef:ElementRef,
|
||||||
private scrollService:ScrollService, private menuService:MenuService,
|
private scrollService:ScrollService, private menuService:MenuService,
|
||||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
optionsService:OptionsService, private detectorRef:ChangeDetectorRef, private marker:Marker) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
this.$element = elementRef.nativeElement;
|
this.$element = elementRef.nativeElement;
|
||||||
|
|
||||||
|
@ -64,7 +64,8 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.options = optionsService.options;
|
this.options = optionsService.options;
|
||||||
|
|
||||||
this.menuService.changed.subscribe((evt) => this.changed(evt));
|
this.menuService.changedActiveItem.subscribe((evt) => this.changed(evt));
|
||||||
|
this.menuService.changed.subscribe((evt) => this.detectorRef.detectChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
changed(item) {
|
changed(item) {
|
||||||
|
@ -147,4 +148,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.preinit();
|
this.preinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,11 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { OnInit, OnDestroy } from '@angular/core';
|
import { OnInit, OnDestroy } from '@angular/core';
|
||||||
import { SpecManager } from '../utils/spec-manager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
import { AppStateService } from '../services/app-state.service';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
export { SpecManager };
|
export { SpecManager };
|
||||||
|
|
||||||
function snapshot(obj) {
|
|
||||||
if(obj == undefined || typeof(obj) !== 'object') {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
var temp = new obj.constructor();
|
|
||||||
|
|
||||||
for(var key in obj) {
|
|
||||||
if (obj.hasOwnProperty(key)) {
|
|
||||||
temp[key] = snapshot(obj[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic Component
|
* Generic Component
|
||||||
* @class
|
* @class
|
||||||
|
@ -65,3 +50,35 @@ export class BaseComponent implements OnInit, OnDestroy {
|
||||||
// emtpy
|
// emtpy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export abstract class BaseSearchableComponent extends BaseComponent implements OnDestroy {
|
||||||
|
searchSubscription: Subscription;
|
||||||
|
constructor(public specMgr: SpecManager, public app: AppStateService) {
|
||||||
|
super(specMgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeForSearch() {
|
||||||
|
this.searchSubscription = this.app.searchContainingPointers.subscribe(ptrs => {
|
||||||
|
for (let i = 0; i < ptrs.length; ++i) {
|
||||||
|
if (ptrs[i]) this.ensureSearchIsShown(ptrs[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
preinit() {
|
||||||
|
super.preinit();
|
||||||
|
this.subscribeForSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.searchSubscription) {
|
||||||
|
this.searchSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
+ Used to destroy component
|
||||||
|
* @abstract
|
||||||
|
*/
|
||||||
|
abstract ensureSearchIsShown(ptr: string);
|
||||||
|
}
|
||||||
|
|
|
@ -15,15 +15,17 @@ import { Method } from './Method/method';
|
||||||
import { Warnings } from './Warnings/warnings';
|
import { Warnings } from './Warnings/warnings';
|
||||||
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
||||||
import { LoadingBar } from './LoadingBar/loading-bar';
|
import { LoadingBar } from './LoadingBar/loading-bar';
|
||||||
|
import { RedocSearch } from './Search/redoc-search';
|
||||||
|
import { ExternalDocs } from './ExternalDocs/external-docs';
|
||||||
|
|
||||||
import { Redoc } from './Redoc/redoc';
|
import { Redoc } from './Redoc/redoc';
|
||||||
|
|
||||||
export const REDOC_DIRECTIVES = [
|
export const REDOC_DIRECTIVES = [
|
||||||
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||||
LoadingBar, SideMenuItems
|
LoadingBar, SideMenuItems, RedocSearch, ExternalDocs
|
||||||
];
|
];
|
||||||
|
|
||||||
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||||
LoadingBar, SideMenuItems }
|
LoadingBar, SideMenuItems, RedocSearch, ExternalDocs }
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {
|
||||||
AppStateService,
|
AppStateService,
|
||||||
ComponentParser,
|
ComponentParser,
|
||||||
ContentProjector,
|
ContentProjector,
|
||||||
|
Marker,
|
||||||
|
SearchService,
|
||||||
COMPONENT_PARSER_ALLOWED } from './services/';
|
COMPONENT_PARSER_ALLOWED } from './services/';
|
||||||
import { SpecManager } from './utils/spec-manager';
|
import { SpecManager } from './utils/spec-manager';
|
||||||
|
|
||||||
|
@ -34,7 +36,9 @@ import { SpecManager } from './utils/spec-manager';
|
||||||
AppStateService,
|
AppStateService,
|
||||||
ComponentParser,
|
ComponentParser,
|
||||||
ContentProjector,
|
ContentProjector,
|
||||||
|
SearchService,
|
||||||
LazyTasksService,
|
LazyTasksService,
|
||||||
|
Marker,
|
||||||
{ provide: APP_ID, useValue: 'redoc' },
|
{ provide: APP_ID, useValue: 'redoc' },
|
||||||
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
||||||
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
||||||
|
|
|
@ -11,6 +11,8 @@ export class AppStateService {
|
||||||
loading = new Subject<boolean>();
|
loading = new Subject<boolean>();
|
||||||
initialized = new BehaviorSubject<any>(false);
|
initialized = new BehaviorSubject<any>(false);
|
||||||
|
|
||||||
|
searchContainingPointers = new BehaviorSubject<string|null[]>([]);
|
||||||
|
|
||||||
startLoading() {
|
startLoading() {
|
||||||
this.loading.next(true);
|
this.loading.next(true);
|
||||||
}
|
}
|
||||||
|
|
56
lib/services/clipboard.service.spec.ts
Normal file
56
lib/services/clipboard.service.spec.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { Clipboard } from './clipboard.service';
|
||||||
|
|
||||||
|
describe('Clipboard Service', () => {
|
||||||
|
let el:Node;
|
||||||
|
let copiedText = null;
|
||||||
|
|
||||||
|
function createEl(html) {
|
||||||
|
let tmpDiv = document.createElement('div');
|
||||||
|
tmpDiv.innerHTML = html;
|
||||||
|
document.body.appendChild(tmpDiv);
|
||||||
|
return tmpDiv.lastChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(Clipboard, 'copySelected').and.callFake(() => {
|
||||||
|
copiedText = window.getSelection().toString();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
copiedText = null;
|
||||||
|
if (el && el.parentNode) el.parentNode.removeChild(el);
|
||||||
|
(<jasmine.Spy>Clipboard.copySelected).and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('selectElement should select element text', () => {
|
||||||
|
el = createEl('<div>Test</div>');
|
||||||
|
Clipboard.selectElement(el);
|
||||||
|
let selected = window.getSelection().toString();
|
||||||
|
selected.should.be.equal('Test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deselect should clear selection', () => {
|
||||||
|
el = createEl('<div>Test</div>');
|
||||||
|
Clipboard.selectElement(el);
|
||||||
|
let selected = window.getSelection().toString();
|
||||||
|
selected.should.be.equal('Test');
|
||||||
|
Clipboard.deselect();
|
||||||
|
window.getSelection().toString().should.be.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('copyElement should copy and deselect', () => {
|
||||||
|
el = createEl('<div>Test</div>');
|
||||||
|
Clipboard.copyElement(el);
|
||||||
|
copiedText.should.be.equal('Test');
|
||||||
|
window.getSelection().toString().should.be.equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('copyCustom should copy custom text', () => {
|
||||||
|
Clipboard.copyCustom('Custom text');
|
||||||
|
copiedText.should.be.equal('Custom text');
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,6 +8,8 @@ export * from './hash.service';
|
||||||
export * from './schema-normalizer.service';
|
export * from './schema-normalizer.service';
|
||||||
export * from './schema-helper.service';
|
export * from './schema-helper.service';
|
||||||
export * from './warnings.service';
|
export * from './warnings.service';
|
||||||
|
export * from './search.service';
|
||||||
|
|
||||||
export * from './component-parser.service';
|
export * from './component-parser.service';
|
||||||
export * from './content-projector.service';
|
export * from './content-projector.service';
|
||||||
|
export * from './marker.service';
|
||||||
|
|
87
lib/services/marker.service.ts
Normal file
87
lib/services/marker.service.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import * as Mark from 'mark.js';
|
||||||
|
import { MenuService } from './menu.service';
|
||||||
|
|
||||||
|
const ROLL_LEN = 5;
|
||||||
|
@Injectable()
|
||||||
|
export class Marker {
|
||||||
|
permInstances = [];
|
||||||
|
rolledInstances = new Array(ROLL_LEN);
|
||||||
|
term: string;
|
||||||
|
|
||||||
|
currIdx = -1;
|
||||||
|
|
||||||
|
constructor(private menu: MenuService) {
|
||||||
|
menu.changedActiveItem.subscribe(() => {
|
||||||
|
this.roll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addElement(el: Element) {
|
||||||
|
this.permInstances.push(new Mark(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
newMarkerAtMenuItem(idx:number) {
|
||||||
|
let context = this.menu.getEl(idx);
|
||||||
|
|
||||||
|
if (this.menu.isTagOrGroupItem(idx)) {
|
||||||
|
context = this.menu.getTagInfoEl(idx);
|
||||||
|
}
|
||||||
|
let newInst = context && new Mark(context);
|
||||||
|
if (newInst && this.term) {
|
||||||
|
newInst.mark(this.term);
|
||||||
|
}
|
||||||
|
return newInst;
|
||||||
|
}
|
||||||
|
|
||||||
|
roll() {
|
||||||
|
let newIdx = this.menu.activeIdx;
|
||||||
|
let diff = newIdx - this.currIdx;
|
||||||
|
this.currIdx = newIdx;
|
||||||
|
if (diff < 0) {
|
||||||
|
diff = - diff;
|
||||||
|
for (let i=0; i < Math.min(diff, ROLL_LEN); i++) {
|
||||||
|
let prevInst = this.rolledInstances.pop();
|
||||||
|
if(prevInst) prevInst.unmark();
|
||||||
|
|
||||||
|
let idx = newIdx - Math.floor(ROLL_LEN/2) + i;
|
||||||
|
let newMark = this.newMarkerAtMenuItem(idx);
|
||||||
|
this.rolledInstances.unshift(newMark);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i=0; i < Math.min(diff, ROLL_LEN); i++) {
|
||||||
|
let oldInst = this.rolledInstances.shift();
|
||||||
|
if (oldInst) oldInst.unmark();
|
||||||
|
|
||||||
|
let idx = newIdx + Math.floor(ROLL_LEN/2) - i;
|
||||||
|
let newMark = this.newMarkerAtMenuItem(idx);
|
||||||
|
this.rolledInstances.push(newMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mark(term: string) {
|
||||||
|
this.term = term || null;
|
||||||
|
this.remark();
|
||||||
|
}
|
||||||
|
|
||||||
|
remark() {
|
||||||
|
for (let marker of this.permInstances) {
|
||||||
|
if (marker) {
|
||||||
|
marker.unmark();
|
||||||
|
if (this.term) marker.mark(this.term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let marker of this.rolledInstances) {
|
||||||
|
if (marker) {
|
||||||
|
marker.unmark();
|
||||||
|
if (this.term) marker.mark(this.term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unmark() {
|
||||||
|
this.term = null;
|
||||||
|
this.remark();
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ import { Hash } from './hash.service';
|
||||||
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
||||||
import { ScrollService } from './scroll.service';
|
import { ScrollService } from './scroll.service';
|
||||||
import { SchemaHelper } from './schema-helper.service';
|
import { SchemaHelper } from './schema-helper.service';
|
||||||
import { SpecManager } from '../utils/spec-manager';;
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
|
||||||
describe('Menu service', () => {
|
describe('Menu service', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { SpecManager } from '../utils/spec-manager';
|
||||||
import { SchemaHelper } from './schema-helper.service';
|
import { SchemaHelper } from './schema-helper.service';
|
||||||
import { AppStateService } from './app-state.service';
|
import { AppStateService } from './app-state.service';
|
||||||
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer, MarkdownHeading, StringMap } from '../utils/';
|
||||||
import * as slugify from 'slugify';
|
import * as slugify from 'slugify';
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export interface MenuItem {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
ready?: boolean;
|
ready?: boolean;
|
||||||
|
|
||||||
level?: number;
|
depth?: number;
|
||||||
flatIdx?: number;
|
flatIdx?: number;
|
||||||
|
|
||||||
metadata?: any;
|
metadata?: any;
|
||||||
|
@ -44,6 +44,7 @@ export interface MenuItem {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class MenuService {
|
||||||
changed: EventEmitter<any> = new EventEmitter();
|
changed: EventEmitter<any> = new EventEmitter();
|
||||||
|
changedActiveItem: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
items: MenuItem[];
|
items: MenuItem[];
|
||||||
activeIdx: number = -1;
|
activeIdx: number = -1;
|
||||||
|
@ -51,6 +52,7 @@ export class MenuService {
|
||||||
private _flatItems: MenuItem[];
|
private _flatItems: MenuItem[];
|
||||||
private _hashSubscription: Subscription;
|
private _hashSubscription: Subscription;
|
||||||
private _scrollSubscription: Subscription;
|
private _scrollSubscription: Subscription;
|
||||||
|
private _progressSubscription: Subscription;
|
||||||
private _tagsWithMethods: any;
|
private _tagsWithMethods: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -70,6 +72,12 @@ export class MenuService {
|
||||||
this._hashSubscription = this.hash.value.subscribe((hash) => {
|
this._hashSubscription = this.hash.value.subscribe((hash) => {
|
||||||
this.onHashChange(hash);
|
this.onHashChange(hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._progressSubscription = this.tasks.loadProgress.subscribe(progress => {
|
||||||
|
if (progress === 100) {
|
||||||
|
this.makeSureLastItemsEnabled();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get flatItems():MenuItem[] {
|
get flatItems():MenuItem[] {
|
||||||
|
@ -87,7 +95,7 @@ export class MenuService {
|
||||||
idx = item.parent.flatIdx;
|
idx = item.parent.flatIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if previous items can be enabled
|
// check if previous items§ can be enabled
|
||||||
let prevItem = this.flatItems[idx -= 1];
|
let prevItem = this.flatItems[idx -= 1];
|
||||||
while(prevItem && (!prevItem.metadata || !prevItem.items)) {
|
while(prevItem && (!prevItem.metadata || !prevItem.items)) {
|
||||||
prevItem.ready = true;
|
prevItem.ready = true;
|
||||||
|
@ -97,6 +105,15 @@ export class MenuService {
|
||||||
this.changed.next();
|
this.changed.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeSureLastItemsEnabled() {
|
||||||
|
let lastIdx = this.flatItems.length - 1;
|
||||||
|
let item = this.flatItems[lastIdx];
|
||||||
|
while(item && (!item.metadata || !item.items)) {
|
||||||
|
item.ready = true;
|
||||||
|
item = this.flatItems[lastIdx -= 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onScroll(isScrolledDown) {
|
onScroll(isScrolledDown) {
|
||||||
let stable = false;
|
let stable = false;
|
||||||
while(!stable) {
|
while(!stable) {
|
||||||
|
@ -135,6 +152,7 @@ export class MenuService {
|
||||||
|
|
||||||
getEl(flatIdx:number):Element {
|
getEl(flatIdx:number):Element {
|
||||||
if (flatIdx < 0) return null;
|
if (flatIdx < 0) return null;
|
||||||
|
if (flatIdx > this.flatItems.length - 1) return null;
|
||||||
let currentItem = this.flatItems[flatIdx];
|
let currentItem = this.flatItems[flatIdx];
|
||||||
if (!currentItem) return;
|
if (!currentItem) return;
|
||||||
if (currentItem.isGroup) currentItem = this.flatItems[flatIdx + 1];
|
if (currentItem.isGroup) currentItem = this.flatItems[flatIdx + 1];
|
||||||
|
@ -156,6 +174,18 @@ export class MenuService {
|
||||||
return selector ? document.querySelector(selector) : null;
|
return selector ? document.querySelector(selector) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTagOrGroupItem(flatIdx: number):boolean {
|
||||||
|
let item = this.flatItems[flatIdx];
|
||||||
|
return item && (item.isGroup || (item.metadata && item.metadata.type === 'tag'));
|
||||||
|
}
|
||||||
|
|
||||||
|
getTagInfoEl(flatIdx: number):Element {
|
||||||
|
if (!this.isTagOrGroupItem(flatIdx)) return null;
|
||||||
|
|
||||||
|
let el = this.getEl(flatIdx);
|
||||||
|
return el && el.querySelector('.tag-info');
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentEl():Element {
|
getCurrentEl():Element {
|
||||||
return this.getEl(this.activeIdx);
|
return this.getEl(this.activeIdx);
|
||||||
}
|
}
|
||||||
|
@ -186,7 +216,7 @@ export class MenuService {
|
||||||
cItem.parent.active = true;
|
cItem.parent.active = true;
|
||||||
cItem = cItem.parent;
|
cItem = cItem.parent;
|
||||||
}
|
}
|
||||||
this.changed.next(item);
|
this.changedActiveItem.next(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
changeActive(offset = 1):boolean {
|
changeActive(offset = 1):boolean {
|
||||||
|
@ -236,40 +266,35 @@ export class MenuService {
|
||||||
|
|
||||||
addMarkdownItems() {
|
addMarkdownItems() {
|
||||||
let schema = this.specMgr.schema;
|
let schema = this.specMgr.schema;
|
||||||
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
let headings:StringMap<MarkdownHeading> = schema.info && schema.info['x-redoc-markdown-headers'] || {};
|
||||||
let id = 'section/' + slugify(header);
|
Object.keys(headings).forEach(h => {
|
||||||
|
let heading = headings[h];
|
||||||
|
let id = 'section/' + heading.id;
|
||||||
let item = {
|
let item = {
|
||||||
name: header,
|
name: heading.title,
|
||||||
id: id,
|
id: id,
|
||||||
items: null
|
items: null
|
||||||
};
|
};
|
||||||
item.items = this.getMarkdownSubheaders(item);
|
item.items = this.getMarkdownSubheaders(item, heading);
|
||||||
|
|
||||||
this.items.push(item);
|
this.items.push(item);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getMarkdownSubheaders(parent: MenuItem):MenuItem[] {
|
getMarkdownSubheaders(parent: MenuItem, parentHeading: MarkdownHeading):MenuItem[] {
|
||||||
let res = [];
|
let res = [];
|
||||||
|
|
||||||
let schema = this.specMgr.schema;
|
Object.keys(parentHeading.children || {}).forEach(h => {
|
||||||
for (let subheader of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-subheaders'] || []))) {
|
let heading = parentHeading.children[h];
|
||||||
let parts = subheader.split('/');
|
let id = 'section/' + heading.id;
|
||||||
let header = parts[0];
|
|
||||||
if (parent.name !== header) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = parts[1];
|
|
||||||
let id = parent.id + '/' + slugify(name);
|
|
||||||
|
|
||||||
let subItem = {
|
let subItem = {
|
||||||
name: name,
|
name: heading.title,
|
||||||
id: id,
|
id: id,
|
||||||
parent: parent
|
parent: parent
|
||||||
};
|
};
|
||||||
res.push(subItem);
|
res.push(subItem);
|
||||||
}
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -332,7 +357,7 @@ export class MenuService {
|
||||||
name: tag['x-displayName'] || tag.name,
|
name: tag['x-displayName'] || tag.name,
|
||||||
id: id,
|
id: id,
|
||||||
description: tag.description,
|
description: tag.description,
|
||||||
metadata: { type: 'tag' },
|
metadata: { type: 'tag', externalDocs: tag.externalDocs },
|
||||||
parent: parent,
|
parent: parent,
|
||||||
items: null
|
items: null
|
||||||
};
|
};
|
||||||
|
@ -403,6 +428,10 @@ export class MenuService {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getItemById(id: string):MenuItem {
|
||||||
|
return this.flatItems.find(item => item.id === id || item.id === `section/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this._hashSubscription.unsubscribe();
|
this._hashSubscription.unsubscribe();
|
||||||
this._scrollSubscription.unsubscribe();
|
this._scrollSubscription.unsubscribe();
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as slugify from 'slugify';
|
||||||
interface PropertyPreprocessOptions {
|
interface PropertyPreprocessOptions {
|
||||||
childFor: string;
|
childFor: string;
|
||||||
skipReadOnly?: boolean;
|
skipReadOnly?: boolean;
|
||||||
|
discriminator?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// global var for this module
|
// global var for this module
|
||||||
|
@ -214,14 +215,13 @@ export class SchemaHelper {
|
||||||
let propPointer = propertySchema._pointer ||
|
let propPointer = propertySchema._pointer ||
|
||||||
JsonPointer.join(pointer, ['properties', propName]);
|
JsonPointer.join(pointer, ['properties', propName]);
|
||||||
propertySchema = SchemaHelper.preprocess(propertySchema, propPointer);
|
propertySchema = SchemaHelper.preprocess(propertySchema, propPointer);
|
||||||
propertySchema._name = propName;
|
propertySchema.name = propName;
|
||||||
// stop endless discriminator recursion
|
// stop endless discriminator recursion
|
||||||
if (propertySchema._pointer === opts.childFor) {
|
if (propertySchema._pointer === opts.childFor) {
|
||||||
propertySchema._pointer = null;
|
propertySchema._pointer = null;
|
||||||
}
|
}
|
||||||
propertySchema._required = !!requiredMap[propName];
|
propertySchema._required = !!requiredMap[propName];
|
||||||
propertySchema.isDiscriminator = (schema.discriminator === propName
|
propertySchema.isDiscriminator = opts.discriminator === propName;
|
||||||
|| schema['x-extendedDiscriminator'] === propName);
|
|
||||||
return propertySchema;
|
return propertySchema;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ export class SchemaHelper {
|
||||||
var addProps = schema.additionalProperties;
|
var addProps = schema.additionalProperties;
|
||||||
let ptr = addProps._pointer || JsonPointer.join(pointer, ['additionalProperties']);
|
let ptr = addProps._pointer || JsonPointer.join(pointer, ['additionalProperties']);
|
||||||
let res = SchemaHelper.preprocess(addProps, ptr);
|
let res = SchemaHelper.preprocess(addProps, ptr);
|
||||||
res._name = '<Additional Properties> *';
|
res.name = '<Additional Properties> *';
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ export class SchemaNormalizer {
|
||||||
let hasPtr = !!schema.$ref;
|
let hasPtr = !!schema.$ref;
|
||||||
if (opts.resolved && !hasPtr) this._dereferencer.visit(ptr);
|
if (opts.resolved && !hasPtr) this._dereferencer.visit(ptr);
|
||||||
|
|
||||||
|
if (opts.childFor) this._dereferencer.visit(opts.childFor);
|
||||||
if (schema['x-redoc-normalized']) return schema;
|
if (schema['x-redoc-normalized']) return schema;
|
||||||
let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => {
|
let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => {
|
||||||
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
||||||
|
@ -38,6 +39,7 @@ export class SchemaNormalizer {
|
||||||
return resolved;
|
return resolved;
|
||||||
});
|
});
|
||||||
if (opts.resolved && !hasPtr) this._dereferencer.exit(ptr);
|
if (opts.resolved && !hasPtr) this._dereferencer.exit(ptr);
|
||||||
|
if (opts.childFor) this._dereferencer.exit(opts.childFor);
|
||||||
res['x-redoc-normalized'] = true;
|
res['x-redoc-normalized'] = true;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -94,7 +96,7 @@ class SchemaWalker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AllOfMerger {
|
export class AllOfMerger {
|
||||||
static merge(into, schemas) {
|
static merge(into, schemas) {
|
||||||
into['x-derived-from'] = [];
|
into['x-derived-from'] = [];
|
||||||
for (let i=0; i < schemas.length; i++) {
|
for (let i=0; i < schemas.length; i++) {
|
||||||
|
@ -113,6 +115,7 @@ class AllOfMerger {
|
||||||
defaults(into, subSchema);
|
defaults(into, subSchema);
|
||||||
subSchema._pointer = tmpPtr;
|
subSchema._pointer = tmpPtr;
|
||||||
}
|
}
|
||||||
|
into.discriminator = null;
|
||||||
into.allOf = null;
|
into.allOf = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +199,6 @@ class SchemaDereferencer {
|
||||||
|
|
||||||
dereference(schema: Reference, pointer:string):any {
|
dereference(schema: Reference, pointer:string):any {
|
||||||
if (!schema || !schema.$ref) return schema;
|
if (!schema || !schema.$ref) return schema;
|
||||||
window['derefCount'] = window['derefCount'] ? window['derefCount'] + 1 : 1;
|
|
||||||
let $ref = schema.$ref;
|
let $ref = schema.$ref;
|
||||||
let resolved = this._spec.byPointer($ref);
|
let resolved = this._spec.byPointer($ref);
|
||||||
if (!this._refCouner.visited($ref)) {
|
if (!this._refCouner.visited($ref)) {
|
||||||
|
|
225
lib/services/search.service.ts
Normal file
225
lib/services/search.service.ts
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { AppStateService } from './app-state.service';
|
||||||
|
import { SchemaNormalizer } from './schema-normalizer.service';
|
||||||
|
import { JsonPointer, groupBy, SpecManager, StringMap, snapshot, MarkdownHeading } from '../utils/';
|
||||||
|
import { methods as swaggerMethods } from '../utils/swagger-defs';
|
||||||
|
import * as slugify from 'slugify';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SwaggerSpec,
|
||||||
|
SwaggerOperation,
|
||||||
|
SwaggerSchema,
|
||||||
|
SwaggerBodyParameter,
|
||||||
|
SwaggerResponse
|
||||||
|
} from '../utils/swagger-typings';
|
||||||
|
|
||||||
|
import * as lunr from 'lunr';
|
||||||
|
|
||||||
|
interface IndexElement {
|
||||||
|
menuId: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
pointer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = lunr(function () {
|
||||||
|
this.field('title', {boost: 1.5});
|
||||||
|
this.field('body');
|
||||||
|
this.ref('pointer');
|
||||||
|
});
|
||||||
|
|
||||||
|
const store:StringMap<IndexElement> = {};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SearchService {
|
||||||
|
normalizer: SchemaNormalizer;
|
||||||
|
constructor(private app: AppStateService, private spec: SpecManager) {
|
||||||
|
this.normalizer = new SchemaNormalizer(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureSearchVisible(containingPointers: string|null[]) {
|
||||||
|
this.app.searchContainingPointers.next(containingPointers);
|
||||||
|
}
|
||||||
|
|
||||||
|
indexAll() {
|
||||||
|
console.time('Indexing');
|
||||||
|
this.indexPaths(this.spec.schema);
|
||||||
|
this.indexTags(this.spec.schema);
|
||||||
|
this.indexDescriptionHeadings(this.spec.schema.info['x-redoc-markdown-headers']);
|
||||||
|
console.time('Indexing end');
|
||||||
|
}
|
||||||
|
|
||||||
|
search(q):StringMap<IndexElement[]> {
|
||||||
|
var items = {};
|
||||||
|
const res:IndexElement[] = index.search(q).map(res => {
|
||||||
|
items[res.menuId] = res;
|
||||||
|
return store[res.ref];
|
||||||
|
});
|
||||||
|
const grouped = groupBy(res, 'menuId');
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
index(element: IndexElement) {
|
||||||
|
// don't reindex same pointers (for discriminator)
|
||||||
|
if (store[element.pointer]) return;
|
||||||
|
index.add(element);
|
||||||
|
store[element.pointer] = element;
|
||||||
|
}
|
||||||
|
|
||||||
|
indexDescriptionHeadings(headings:StringMap<MarkdownHeading>) {
|
||||||
|
if (!headings) return;
|
||||||
|
Object.keys(headings).forEach(k => {
|
||||||
|
let heading = headings[k];
|
||||||
|
this.index({
|
||||||
|
menuId: heading.id,
|
||||||
|
title: heading.title,
|
||||||
|
body: heading.content,
|
||||||
|
pointer: '/heading/' + heading.id
|
||||||
|
});
|
||||||
|
|
||||||
|
this.indexDescriptionHeadings(heading.children);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
indexTags(swagger:SwaggerSpec) {
|
||||||
|
let tags = swagger.tags;
|
||||||
|
if (!tags) return;
|
||||||
|
for (let tag of tags) {
|
||||||
|
if (tag['x-traitTag']) continue;
|
||||||
|
let id = `tag/${slugify(tag.name)}`;
|
||||||
|
this.index({
|
||||||
|
menuId: id,
|
||||||
|
title: tag.name,
|
||||||
|
body: tag.description,
|
||||||
|
pointer: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexPaths(swagger:SwaggerSpec) {
|
||||||
|
const paths = swagger.paths;
|
||||||
|
const basePtr = '#/paths';
|
||||||
|
Object.keys(paths).forEach(path => {
|
||||||
|
let opearations = paths[path];
|
||||||
|
Object.keys(opearations).forEach(verb => {
|
||||||
|
if (!swaggerMethods.has(verb)) return;
|
||||||
|
const opearation = opearations[verb];
|
||||||
|
const ptr = JsonPointer.join(basePtr, [path, verb]);
|
||||||
|
|
||||||
|
this.indexOperation(opearation, ptr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOperation(operation:SwaggerOperation, operationPointer:string) {
|
||||||
|
this.index({
|
||||||
|
pointer: operationPointer,
|
||||||
|
menuId: operationPointer,
|
||||||
|
title: operation.summary,
|
||||||
|
body: operation.description
|
||||||
|
});
|
||||||
|
this.indexOperationResponses(operation, operationPointer);
|
||||||
|
this.indexOperationParameters(operation, operationPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOperationParameters(operation: SwaggerOperation, operationPointer: string) {
|
||||||
|
const parameters = this.spec.getMethodParams(operationPointer);
|
||||||
|
if (!parameters) return;
|
||||||
|
for (let i=0; i<parameters.length; ++i) {
|
||||||
|
const param = parameters[i];
|
||||||
|
const paramPointer = JsonPointer.join(operationPointer, ['parameters', i]);
|
||||||
|
this.index({
|
||||||
|
pointer: paramPointer,
|
||||||
|
menuId: operationPointer,
|
||||||
|
title: param.in === 'body' ? '' : param.name,
|
||||||
|
body: param.description
|
||||||
|
});
|
||||||
|
|
||||||
|
if (param.in === 'body') {
|
||||||
|
this.normalizer.reset();
|
||||||
|
this.indexSchema((<SwaggerBodyParameter>param).schema,
|
||||||
|
'', JsonPointer.join(paramPointer, ['schema']), operationPointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOperationResponses(operation:SwaggerOperation, operationPtr:string) {
|
||||||
|
const responses = operation.responses;
|
||||||
|
if (!responses) return;
|
||||||
|
Object.keys(responses).forEach(code => {
|
||||||
|
const resp = responses[code];
|
||||||
|
const respPtr = JsonPointer.join(operationPtr, ['responses', code]);
|
||||||
|
this.index({
|
||||||
|
pointer: respPtr,
|
||||||
|
menuId: operationPtr,
|
||||||
|
title: code,
|
||||||
|
body: resp.description
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.schema) {
|
||||||
|
this.normalizer.reset();
|
||||||
|
this.indexSchema(resp.schema, '', JsonPointer.join(respPtr, 'schema'), operationPtr);
|
||||||
|
}
|
||||||
|
if (resp.headers) {
|
||||||
|
this.indexOperationResponseHeaders(resp, respPtr, operationPtr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOperationResponseHeaders(response: SwaggerResponse, responsePtr: string, operationPtr: string, ) {
|
||||||
|
let headers = response.headers || [];
|
||||||
|
Object.keys(headers).forEach(headerName => {
|
||||||
|
let header = headers[headerName];
|
||||||
|
this.index({
|
||||||
|
pointer: `${responsePtr}/${headerName}`,
|
||||||
|
menuId: operationPtr,
|
||||||
|
title: headerName,
|
||||||
|
body: header.description
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
indexSchema(_schema:SwaggerSchema, name: string, absolutePointer: string,
|
||||||
|
menuPointer: string, parent?: string) {
|
||||||
|
if (!_schema) return;
|
||||||
|
let schema = _schema;
|
||||||
|
let title = name;
|
||||||
|
schema = this.normalizer.normalize(schema, schema._pointer || absolutePointer, { childFor: parent });
|
||||||
|
|
||||||
|
let body = schema.description; // TODO: defaults, examples, etc...
|
||||||
|
|
||||||
|
if (schema.type === 'array') {
|
||||||
|
this.indexSchema(schema.items, title, JsonPointer.join(absolutePointer, ['items']), menuPointer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.discriminator) {
|
||||||
|
let derived = this.spec.findDerivedDefinitions(schema._pointer, schema);
|
||||||
|
for (let defInfo of derived ) {
|
||||||
|
let subSpec = this.spec.getDescendant(defInfo, schema);
|
||||||
|
this.indexSchema(snapshot(subSpec), '', absolutePointer, menuPointer, schema._pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.type === 'string' && schema.enum) {
|
||||||
|
body += ' ' + schema.enum.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
// redoc doesn't display top level descriptions and titles
|
||||||
|
this.index({
|
||||||
|
pointer: absolutePointer,
|
||||||
|
menuId: menuPointer,
|
||||||
|
title,
|
||||||
|
body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.properties) {
|
||||||
|
Object.keys(schema.properties).forEach(propName => {
|
||||||
|
let propPtr = JsonPointer.join(absolutePointer, ['properties', propName]);
|
||||||
|
this.indexSchema(schema.properties[propName], propName, propPtr, menuPointer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
lib/shared/components/DropDown/drop-down.html
Normal file
3
lib/shared/components/DropDown/drop-down.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<select (change)=onChange($event.target.value)>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</select>
|
|
@ -1,19 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, EventEmitter, ElementRef, Output, AfterContentInit } from '@angular/core';
|
import { Component, EventEmitter, ElementRef, Output, Input, AfterContentInit, OnChanges } from '@angular/core';
|
||||||
import * as DropKick from 'dropkickjs';
|
import * as DropKick from 'dropkickjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'drop-down',
|
selector: 'drop-down',
|
||||||
template: `
|
templateUrl: 'drop-down.html',
|
||||||
<select (change)=onChange($event.target.value)>
|
|
||||||
<ng-content></ng-content>
|
|
||||||
</select>
|
|
||||||
`,
|
|
||||||
styleUrls: ['./drop-down.css']
|
styleUrls: ['./drop-down.css']
|
||||||
})
|
})
|
||||||
export class DropDown implements AfterContentInit {
|
export class DropDown implements AfterContentInit, OnChanges {
|
||||||
@Output() change = new EventEmitter();
|
@Output() change = new EventEmitter();
|
||||||
|
@Input() active: string;
|
||||||
elem: any;
|
elem: any;
|
||||||
inst: any;
|
inst: any;
|
||||||
constructor(elem:ElementRef) {
|
constructor(elem:ElementRef) {
|
||||||
|
@ -28,6 +25,12 @@ export class DropDown implements AfterContentInit {
|
||||||
this.change.next(value);
|
this.change.next(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(ch) {
|
||||||
|
if (ch.active.currentValue) {
|
||||||
|
this.inst && this.inst.select(ch.active.currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.inst.dispose();
|
this.inst.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,11 +38,11 @@ describe('Common components', () => {
|
||||||
expect(component.stickBottom).not.toHaveBeenCalled();
|
expect(component.stickBottom).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should stick to the top on the next VM tick', (done) => {
|
it('should stick to the top on the next animation frame', (done) => {
|
||||||
spyOn(component, 'stick').and.callThrough();
|
spyOn(component, 'stick').and.callThrough();
|
||||||
spyOn(component, 'stickBottom').and.callThrough();
|
spyOn(component, 'stickBottom').and.callThrough();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
setTimeout(() => {
|
requestAnimationFrame(() => {
|
||||||
expect(component.stick).toHaveBeenCalled();
|
expect(component.stick).toHaveBeenCalled();
|
||||||
expect(component.stickBottom).toHaveBeenCalled();
|
expect(component.stickBottom).toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
|
|
3
lib/shared/components/Tabs/tab.html
Normal file
3
lib/shared/components/Tabs/tab.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="tab-wrap" [ngClass]="{'active': active}">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
10
lib/shared/components/Tabs/tab.scss
Normal file
10
lib/shared/components/Tabs/tab.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tab-wrap {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-wrap.active {
|
||||||
|
display: block;
|
||||||
|
}
|
5
lib/shared/components/Tabs/tabs.html
Normal file
5
lib/shared/components/Tabs/tabs.html
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<ul>
|
||||||
|
<li *ngFor="let tab of tabs" [ngClass]="{active: tab.active}" (click)="selectTab(tab)"
|
||||||
|
class="tab-{{tab.tabStatus}}">{{tab.tabTitle}}</li>
|
||||||
|
</ul>
|
||||||
|
<ng-content></ng-content>
|
|
@ -5,13 +5,7 @@ import { ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tabs',
|
selector: 'tabs',
|
||||||
template: `
|
templateUrl: 'tabs.html',
|
||||||
<ul>
|
|
||||||
<li *ngFor="let tab of tabs" [ngClass]="{active: tab.active}" (click)="selectTab(tab)"
|
|
||||||
class="tab-{{tab.tabStatus}}">{{tab.tabTitle}}</li>
|
|
||||||
</ul>
|
|
||||||
<ng-content></ng-content>
|
|
||||||
`,
|
|
||||||
styleUrls: ['tabs.css'],
|
styleUrls: ['tabs.css'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
|
@ -63,23 +57,8 @@ export class Tabs implements OnInit {
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tab',
|
selector: 'tab',
|
||||||
template: `
|
templateUrl: 'tab.html',
|
||||||
<div class="tab-wrap" [ngClass]="{'active': active}">
|
styleUrls: ['tab.css']
|
||||||
<ng-content></ng-content>
|
|
||||||
</div>
|
|
||||||
`,
|
|
||||||
styles: [`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.tab-wrap {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-wrap.active {
|
|
||||||
display: block;
|
|
||||||
}`
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class Tab {
|
export class Tab {
|
||||||
@Input() active: boolean = false;
|
@Input() active: boolean = false;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="zippy zippy-{{type}}" [ngClass]="{'zippy-empty': empty, 'zippy-hidden': !visible}">
|
<div class="zippy zippy-{{type}}" [ngClass]="{'zippy-empty': empty, 'zippy-hidden': !open}">
|
||||||
<div *ngIf='!headless' class="zippy-title" (click)="toggle()">
|
<div *ngIf='!headless' class="zippy-title" (click)="toggle()">
|
||||||
<span class="zippy-indicator">
|
<span class="zippy-indicator">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||||
|
|
|
@ -16,7 +16,7 @@ describe('Common components', () => {
|
||||||
});
|
});
|
||||||
describe('Zippy Component', () => {
|
describe('Zippy Component', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component: Zippy;
|
||||||
let nativeElement;
|
let nativeElement;
|
||||||
let fixture;
|
let fixture;
|
||||||
|
|
||||||
|
@ -33,13 +33,13 @@ describe('Common components', () => {
|
||||||
|
|
||||||
it('should init component defaults', () => {
|
it('should init component defaults', () => {
|
||||||
component.empty.should.be.false();
|
component.empty.should.be.false();
|
||||||
component.visible.should.be.false();
|
component.open.should.be.false();
|
||||||
component.type.should.be.equal('general');
|
component.type.should.be.equal('general');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init properties from dom params', () => {
|
it('should init properties from dom params', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.visible.should.be.true();
|
component.open.should.be.true();
|
||||||
component.empty.should.be.true();
|
component.empty.should.be.true();
|
||||||
component.title.should.be.equal('Zippy');
|
component.title.should.be.equal('Zippy');
|
||||||
component.type.should.be.equal('test');
|
component.type.should.be.equal('test');
|
||||||
|
@ -54,7 +54,7 @@ describe('Common components', () => {
|
||||||
it('should open and close zippy', (done) => {
|
it('should open and close zippy', (done) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.empty = false;
|
component.empty = false;
|
||||||
component.visible = true;
|
component.open = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
let testComponent = fixture.debugElement.componentInstance;
|
let testComponent = fixture.debugElement.componentInstance;
|
||||||
|
@ -62,13 +62,13 @@ describe('Common components', () => {
|
||||||
let titleEl = nativeElement.querySelector('.zippy-title');
|
let titleEl = nativeElement.querySelector('.zippy-title');
|
||||||
mouseclick(titleEl);
|
mouseclick(titleEl);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.visible.should.be.false();
|
component.open.should.be.false();
|
||||||
testComponent.opened.should.be.false();
|
testComponent.opened.should.be.false();
|
||||||
|
|
||||||
mouseclick(titleEl);
|
mouseclick(titleEl);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
component.visible.should.be.true();
|
component.open.should.be.true();
|
||||||
testComponent.opened.should.be.true();
|
testComponent.opened.should.be.true();
|
||||||
testComponent.clickCount.should.be.equal(2);
|
testComponent.clickCount.should.be.equal(2);
|
||||||
done();
|
done();
|
||||||
|
@ -95,21 +95,17 @@ describe('Common components', () => {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'test-app',
|
selector: 'test-app',
|
||||||
template:
|
template:
|
||||||
`<zippy title="Zippy" type="test" [visible]="true" [empty]="true" (open)="open()" (close)="close()">test</zippy>`
|
`<zippy title="Zippy" type="test" [open]="true" [empty]="true" (openChange)="open($event)">test</zippy>`
|
||||||
})
|
})
|
||||||
class TestApp {
|
class TestApp {
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
clickCount: number;
|
clickCount: number;
|
||||||
constructor() {
|
constructor() {
|
||||||
this.opened = false;
|
this.opened = false;
|
||||||
this.clickCount = 0;
|
this.clickCount = -1; // initial change detection
|
||||||
}
|
}
|
||||||
open() {
|
open(val) {
|
||||||
this.opened = true;
|
this.opened = val;
|
||||||
this.clickCount++;
|
|
||||||
}
|
|
||||||
close() {
|
|
||||||
this.opened = false;
|
|
||||||
this.clickCount++;
|
this.clickCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
import { Component, EventEmitter, Output, Input, OnChanges } from '@angular/core';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'zippy',
|
selector: 'zippy',
|
||||||
templateUrl: './zippy.html',
|
templateUrl: './zippy.html',
|
||||||
styleUrls: ['./zippy.css']
|
styleUrls: ['./zippy.css']
|
||||||
})
|
})
|
||||||
export class Zippy {
|
export class Zippy implements OnChanges {
|
||||||
@Input() type = 'general';
|
@Input() type = 'general';
|
||||||
@Input() visible = false;
|
|
||||||
@Input() empty = false;
|
@Input() empty = false;
|
||||||
@Input() title;
|
@Input() title;
|
||||||
@Input() headless: boolean = false;
|
@Input() headless: boolean = false;
|
||||||
@Output() open = new EventEmitter();
|
@Input() open = false;
|
||||||
@Output() close = new EventEmitter();
|
@Output() openChange = new EventEmitter();
|
||||||
|
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
this.visible = !this.visible;
|
this.open = !this.open;
|
||||||
if (this.empty) return;
|
if (this.empty) return;
|
||||||
if (this.visible) {
|
this.openChange.emit(this.open);
|
||||||
this.open.next({});
|
}
|
||||||
} else {
|
|
||||||
this.close.next({});
|
ngOnChanges(ch) {
|
||||||
|
if (ch.open.currentValue === true) {
|
||||||
|
this.openChange.emit(ch.open.currentValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,20 @@ export class JsonPointer {
|
||||||
return JsonPointerLib.compile(tokens.slice(0, tokens.length - level));
|
return JsonPointerLib.compile(tokens.slice(0, tokens.length - level));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns relative path tokens
|
||||||
|
* @example
|
||||||
|
* // returns ['subpath']
|
||||||
|
* JsonPointerHelper.relative('/path/0', '/path/0/subpath')
|
||||||
|
* // returns ['foo', 'subpath']
|
||||||
|
* JsonPointerHelper.relative('/path', '/path/foo/subpath')
|
||||||
|
*/
|
||||||
|
static relative(from, to):string[] {
|
||||||
|
let fromTokens = JsonPointer.parse(from);
|
||||||
|
let toTokens = JsonPointer.parse(to);
|
||||||
|
return toTokens.slice(fromTokens.length);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* overridden JsonPointer original parse to take care of prefixing '#' symbol
|
* overridden JsonPointer original parse to take care of prefixing '#' symbol
|
||||||
* that is not valid JsonPointer
|
* that is not valid JsonPointer
|
||||||
|
|
|
@ -14,16 +14,6 @@ export class BrowserDomAdapter {
|
||||||
return () => { el.removeEventListener(evt, listener, false); };
|
return () => { el.removeEventListener(evt, listener, false); };
|
||||||
}
|
}
|
||||||
|
|
||||||
static addClass(element: any /** TODO #9100 */, className: string) { element.classList.add(className); }
|
|
||||||
|
|
||||||
static removeClass(element: any /** TODO #9100 */, className: string) {
|
|
||||||
element.classList.remove(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
static hasClass(element: any /** TODO #9100 */, className: string): boolean {
|
|
||||||
return element.classList.contains(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
static attributeMap(element: any /** TODO #9100 */): Map<string, string> {
|
static attributeMap(element: any /** TODO #9100 */): Map<string, string> {
|
||||||
var res = new Map<string, string>();
|
var res = new Map<string, string>();
|
||||||
var elAttrs = element.attributes;
|
var elAttrs = element.attributes;
|
||||||
|
@ -59,15 +49,5 @@ export class BrowserDomAdapter {
|
||||||
return element.getAttribute(attribute);
|
return element.getAttribute(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
static setAttribute(element: any /** TODO #9100 */, name: string, value: string) {
|
|
||||||
element.setAttribute(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeAttribute(element: any /** TODO #9100 */, attribute: string) {
|
|
||||||
element.removeAttribute(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getLocation(): Location { return window.location; }
|
|
||||||
|
|
||||||
static defaultDoc(): HTMLDocument { return document; }
|
static defaultDoc(): HTMLDocument { return document; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
export interface StringMap<T> {
|
||||||
|
[key: string]: T;
|
||||||
|
}
|
||||||
|
|
||||||
export function stringify(obj:any) {
|
export function stringify(obj:any) {
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
|
@ -16,6 +20,18 @@ export function isBlank(obj: any): boolean {
|
||||||
return obj == undefined;
|
return obj == undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
export function groupBy<T>(array: T[], key:string):StringMap<T[]> {
|
||||||
|
return array.reduce<StringMap<T[]>>(function(res, value) {
|
||||||
|
if (hasOwnProperty.call(res, value[key])) {
|
||||||
|
res[value[key]].push(value);
|
||||||
|
} else {
|
||||||
|
res[value[key]] = [value];
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
export function statusCodeType(statusCode) {
|
export function statusCodeType(statusCode) {
|
||||||
if (statusCode < 100 || statusCode > 599) {
|
if (statusCode < 100 || statusCode > 599) {
|
||||||
throw new Error('invalid HTTP code');
|
throw new Error('invalid HTTP code');
|
||||||
|
@ -78,3 +94,19 @@ export function throttle(fn, threshhold, scope) {
|
||||||
export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
|
export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0
|
||||||
|| (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari']
|
|| (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari']
|
||||||
|| safari.pushNotification);
|
|| safari.pushNotification);
|
||||||
|
|
||||||
|
export function snapshot(obj) {
|
||||||
|
if(obj == undefined || typeof(obj) !== 'object') {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
var temp = new obj.constructor();
|
||||||
|
|
||||||
|
for(var key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
temp[key] = snapshot(obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
export * from './custom-error-handler';
|
export * from './custom-error-handler';
|
||||||
export * from './helpers';
|
export * from './helpers';
|
||||||
export * from './md-renderer';
|
export * from './md-renderer';
|
||||||
|
export * from './spec-manager';
|
||||||
|
|
||||||
|
export { default as JsonPointer } from './JsonPointer';
|
||||||
|
|
24
lib/utils/md-renderer.spec.ts
Normal file
24
lib/utils/md-renderer.spec.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { MdRenderer } from '../../lib/utils/md-renderer';
|
||||||
|
|
||||||
|
describe('Utils', () => {
|
||||||
|
describe('Markdown renderer', () => {
|
||||||
|
let mdRender: MdRenderer;
|
||||||
|
beforeEach(() => {
|
||||||
|
mdRender = new MdRenderer();
|
||||||
|
});
|
||||||
|
it('should return a level-1 heading even though only level-2 is present', () => {
|
||||||
|
mdRender.renderMd('## Sub Intro');
|
||||||
|
Object.keys(mdRender.headings).length.should.be.equal(1);
|
||||||
|
should.exist(mdRender.headings['Sub-Intro']);
|
||||||
|
});
|
||||||
|
it('should return a level-2 heading as a child of level-1', () => {
|
||||||
|
mdRender.renderMd('# Introduction \n ## Sub Intro');
|
||||||
|
Object.keys(mdRender.headings).length.should.be.equal(1);
|
||||||
|
should.exist(mdRender.headings['Introduction']);
|
||||||
|
should.exist(mdRender.headings['Introduction'].children);
|
||||||
|
Object.keys(mdRender.headings['Introduction'].children).length.should.be.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,6 +3,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import * as slugify from 'slugify';
|
import * as slugify from 'slugify';
|
||||||
import * as Remarkable from 'remarkable';
|
import * as Remarkable from 'remarkable';
|
||||||
|
import { StringMap } from './';
|
||||||
|
|
||||||
declare var Prism: any;
|
declare var Prism: any;
|
||||||
const md = new Remarkable({
|
const md = new Remarkable({
|
||||||
|
@ -13,17 +14,23 @@ const md = new Remarkable({
|
||||||
highlight: (str, lang) => {
|
highlight: (str, lang) => {
|
||||||
if (lang === 'json') lang = 'js';
|
if (lang === 'json') lang = 'js';
|
||||||
let grammar = Prism.languages[lang];
|
let grammar = Prism.languages[lang];
|
||||||
//fallback to clike
|
// fallback to click
|
||||||
if (!grammar) return str;
|
if (!grammar) return str;
|
||||||
return Prism.highlight(str, grammar);
|
return Prism.highlight(str, grammar);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export interface MarkdownHeading {
|
||||||
|
title?: string;
|
||||||
|
id: string;
|
||||||
|
content?: string;
|
||||||
|
children?: StringMap<MarkdownHeading>;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MdRenderer {
|
export class MdRenderer {
|
||||||
public firstLevelHeadings: string[] = [];
|
public headings: StringMap<MarkdownHeading> = {};
|
||||||
public secondLevelHeadings: string[] = [];
|
currentTopHeading: MarkdownHeading;
|
||||||
public currentHeading: string = null;
|
|
||||||
|
|
||||||
private _origRules:any = {};
|
private _origRules:any = {};
|
||||||
private _preProcessors:Function[] = [];
|
private _preProcessors:Function[] = [];
|
||||||
|
@ -45,21 +52,70 @@ export class MdRenderer {
|
||||||
md.renderer.rules.heading_close = this._origRules.close;
|
md.renderer.rules.heading_close = this._origRules.close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveHeading(title: string, parent:MarkdownHeading = {id:null, children: this.headings}) :MarkdownHeading {
|
||||||
|
let id = slugify(title);
|
||||||
|
if (parent && parent.id) id = `${parent.id}/${id}`;
|
||||||
|
parent.children = parent.children || {};
|
||||||
|
parent.children[id] = {
|
||||||
|
title,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
return parent.children[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
flattenHeadings(container: StringMap<MarkdownHeading>): MarkdownHeading[] {
|
||||||
|
if (!container) return [];
|
||||||
|
let res = [];
|
||||||
|
Object.keys(container).forEach(k => {
|
||||||
|
let heading = container[k];
|
||||||
|
res.push(heading);
|
||||||
|
res.push(...this.flattenHeadings(heading.children));
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
attachHeadingsContent(rawText:string) {
|
||||||
|
const buildRegexp = heading => new RegExp(
|
||||||
|
`<h\\d section="section/${heading.id}">`
|
||||||
|
);
|
||||||
|
|
||||||
|
const tmpEl = document.createElement('DIV');
|
||||||
|
|
||||||
|
const html2Str = html => {
|
||||||
|
tmpEl.innerHTML = html;
|
||||||
|
return tmpEl.innerText;
|
||||||
|
};
|
||||||
|
|
||||||
|
let flatHeadings = this.flattenHeadings(this.headings);
|
||||||
|
if (flatHeadings.length < 1) return;
|
||||||
|
let prevHeading = flatHeadings[0];
|
||||||
|
|
||||||
|
let prevPos = rawText.search(buildRegexp(prevHeading));
|
||||||
|
for (let i=1; i < flatHeadings.length; i++) {
|
||||||
|
let heading = flatHeadings[i];
|
||||||
|
let currentPos = rawText.substr(prevPos + 1).search(buildRegexp(heading)) + prevPos + 1;
|
||||||
|
prevHeading.content = html2Str(rawText.substring(prevPos, currentPos));
|
||||||
|
|
||||||
|
prevHeading = heading;
|
||||||
|
prevPos = currentPos;
|
||||||
|
}
|
||||||
|
prevHeading.content = html2Str(rawText.substring(prevPos));
|
||||||
|
}
|
||||||
|
|
||||||
headingOpenRule(tokens, idx) {
|
headingOpenRule(tokens, idx) {
|
||||||
if (tokens[idx].hLevel > 2 ) {
|
if (tokens[idx].hLevel > 2 ) {
|
||||||
return this._origRules.open(tokens, idx);
|
return this._origRules.open(tokens, idx);
|
||||||
} else {
|
} else {
|
||||||
let content = tokens[idx + 1].content;
|
let content = tokens[idx + 1].content;
|
||||||
if (tokens[idx].hLevel === 1 ) {
|
if (tokens[idx].hLevel === 1 ) {
|
||||||
this.firstLevelHeadings.push(content);
|
this.currentTopHeading = this.saveHeading(content);;
|
||||||
this.currentHeading = content;
|
let id = this.currentTopHeading.id;
|
||||||
let contentSlug = slugify(content);
|
return `<h${tokens[idx].hLevel} section="section/${id}">` +
|
||||||
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
|
`<a class="share-link" href="#section/${id}"></a>`;
|
||||||
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
|
||||||
} else if (tokens[idx].hLevel === 2 ) {
|
} else if (tokens[idx].hLevel === 2 ) {
|
||||||
this.secondLevelHeadings.push(this.currentHeading + `/` + content);
|
let heading = this.saveHeading(content, this.currentTopHeading);
|
||||||
let contentSlug = slugify(this.currentHeading) + `/` + slugify(content);
|
let contentSlug = `${heading.id}`;
|
||||||
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
|
return `<h${tokens[idx].hLevel} section="section/${heading.id}">` +
|
||||||
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +143,8 @@ export class MdRenderer {
|
||||||
|
|
||||||
let res = md.render(text);
|
let res = md.render(text);
|
||||||
|
|
||||||
|
this.attachHeadingsContent(res);
|
||||||
|
|
||||||
if (!this.raw) {
|
if (!this.raw) {
|
||||||
this.restoreOrigRules();
|
this.restoreOrigRules();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,19 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
import { MdRenderer } from './md-renderer';
|
import { MdRenderer } from './md-renderer';
|
||||||
|
|
||||||
|
import { SwaggerOperation, SwaggerParameter } from './swagger-typings';
|
||||||
|
|
||||||
function getDiscriminator(obj) {
|
function getDiscriminator(obj) {
|
||||||
return obj.discriminator || obj['x-extendedDiscriminator'];
|
return obj.discriminator || obj['x-extendedDiscriminator'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DescendantInfo {
|
||||||
|
$ref: string;
|
||||||
|
name: string;
|
||||||
|
active?: boolean;
|
||||||
|
idx?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class SpecManager {
|
export class SpecManager {
|
||||||
public _schema: any = {};
|
public _schema: any = {};
|
||||||
public apiUrl: string;
|
public apiUrl: string;
|
||||||
|
@ -75,8 +84,7 @@ export class SpecManager {
|
||||||
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
|
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
|
||||||
}
|
}
|
||||||
this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
|
this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
|
||||||
this._schema.info['x-redoc-markdown-headers'] = mdRender.firstLevelHeadings;
|
this._schema.info['x-redoc-markdown-headers'] = mdRender.headings;
|
||||||
this._schema.info['x-redoc-markdown-subheaders'] = mdRender.secondLevelHeadings;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get schema() {
|
get schema() {
|
||||||
|
@ -114,9 +122,9 @@ export class SpecManager {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMethodParams(methodPtr, resolveRefs) {
|
getMethodParams(methodPtr:string):SwaggerParameter[] {
|
||||||
/* inject JsonPointer into array elements */
|
/* inject JsonPointer into array elements */
|
||||||
function injectPointers(array, root) {
|
function injectPointers(array:SwaggerParameter[], root) {
|
||||||
if (!Array.isArray(array)) {
|
if (!Array.isArray(array)) {
|
||||||
throw new Error(`parameters must be an array. Got ${typeof array} at ${root}`);
|
throw new Error(`parameters must be an array. Got ${typeof array} at ${root}`);
|
||||||
}
|
}
|
||||||
|
@ -133,17 +141,16 @@ export class SpecManager {
|
||||||
|
|
||||||
//get path params
|
//get path params
|
||||||
let pathParamsPtr = JsonPointer.join(JsonPointer.dirName(methodPtr), ['parameters']);
|
let pathParamsPtr = JsonPointer.join(JsonPointer.dirName(methodPtr), ['parameters']);
|
||||||
let pathParams = this.byPointer(pathParamsPtr) || [];
|
let pathParams:SwaggerParameter[] = this.byPointer(pathParamsPtr) || [];
|
||||||
|
|
||||||
let methodParamsPtr = JsonPointer.join(methodPtr, ['parameters']);
|
let methodParamsPtr = JsonPointer.join(methodPtr, ['parameters']);
|
||||||
let methodParams = this.byPointer(methodParamsPtr) || [];
|
let methodParams:SwaggerParameter[] = this.byPointer(methodParamsPtr) || [];
|
||||||
pathParams = injectPointers(pathParams, pathParamsPtr);
|
pathParams = injectPointers(pathParams, pathParamsPtr);
|
||||||
methodParams = injectPointers(methodParams, methodParamsPtr);
|
methodParams = injectPointers(methodParams, methodParamsPtr);
|
||||||
|
|
||||||
if (resolveRefs) {
|
// resolve references
|
||||||
methodParams = this.resolveRefs(methodParams);
|
methodParams = this.resolveRefs(methodParams);
|
||||||
pathParams = this.resolveRefs(pathParams);
|
pathParams = this.resolveRefs(pathParams);
|
||||||
}
|
|
||||||
return methodParams.concat(pathParams);
|
return methodParams.concat(pathParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,21 +162,18 @@ export class SpecManager {
|
||||||
description: tag.description,
|
description: tag.description,
|
||||||
'x-traitTag': tag['x-traitTag'] || false
|
'x-traitTag': tag['x-traitTag'] || false
|
||||||
};
|
};
|
||||||
if (tag['x-traitTag']) {
|
|
||||||
console.warn(`x-traitTag (${tag.name}) is deprecated since v1.5.0 and will be removed in the future`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagsMap;
|
return tagsMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
findDerivedDefinitions(defPointer, schema) {
|
findDerivedDefinitions(defPointer: string, schema): DescendantInfo[] {
|
||||||
let definition = schema || this.byPointer(defPointer);
|
let definition = schema || this.byPointer(defPointer);
|
||||||
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);
|
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);
|
||||||
if (!definition.discriminator && !definition['x-extendedDiscriminator']) return [];
|
if (!definition.discriminator && !definition['x-extendedDiscriminator']) return [];
|
||||||
|
|
||||||
let globalDefs = this._schema.definitions || {};
|
let globalDefs = this._schema.definitions || {};
|
||||||
let res = [];
|
let res:DescendantInfo[] = [];
|
||||||
let extendedDiscriminatorProp = definition['x-extendedDiscriminator'];
|
let extendedDiscriminatorProp = definition['x-extendedDiscriminator'];
|
||||||
for (let defName of Object.keys(globalDefs)) {
|
for (let defName of Object.keys(globalDefs)) {
|
||||||
let def = globalDefs[defName];
|
let def = globalDefs[defName];
|
||||||
|
@ -206,7 +210,7 @@ export class SpecManager {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescendant(descendant, componentSchema) {
|
getDescendant(descendant:DescendantInfo, componentSchema:any) {
|
||||||
let res;
|
let res;
|
||||||
if (!getDiscriminator(componentSchema) && componentSchema.allOf) {
|
if (!getDiscriminator(componentSchema) && componentSchema.allOf) {
|
||||||
// discriminator inherited from parents
|
// discriminator inherited from parents
|
||||||
|
|
25
lib/utils/swagger-typings.ts
Normal file
25
lib/utils/swagger-typings.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import {
|
||||||
|
Operation,
|
||||||
|
Parameter,
|
||||||
|
Schema,
|
||||||
|
BodyParameter,
|
||||||
|
HeaderParameter,
|
||||||
|
QueryParameter,
|
||||||
|
FormDataParameter,
|
||||||
|
Spec,
|
||||||
|
Response
|
||||||
|
} from '@types/swagger-schema-official';
|
||||||
|
|
||||||
|
interface RedocInjectedPointer {
|
||||||
|
_pointer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface SwaggerOperation extends Operation, RedocInjectedPointer {};
|
||||||
|
export interface SwaggerBodyParameter extends BodyParameter, RedocInjectedPointer {}
|
||||||
|
export interface SwaggerHeaderParameter extends HeaderParameter, RedocInjectedPointer {}
|
||||||
|
export interface SwaggerQueryParameter extends QueryParameter, RedocInjectedPointer {}
|
||||||
|
export interface SwaggerFormDataParameter extends FormDataParameter, RedocInjectedPointer {}
|
||||||
|
export type SwaggerParameter = SwaggerBodyParameter | SwaggerHeaderParameter | SwaggerQueryParameter | SwaggerFormDataParameter;
|
||||||
|
export interface SwaggerSchema extends Schema, RedocInjectedPointer {};
|
||||||
|
export { Spec as SwaggerSpec, Response as SwaggerResponse };
|
1
manual-types/index.d.ts
vendored
1
manual-types/index.d.ts
vendored
|
@ -6,6 +6,7 @@ declare module "scrollparent"
|
||||||
declare module "slugify"
|
declare module "slugify"
|
||||||
declare module "url"
|
declare module "url"
|
||||||
declare module "json-pointer";
|
declare module "json-pointer";
|
||||||
|
declare module "mark.js";
|
||||||
|
|
||||||
declare module "*.css" {
|
declare module "*.css" {
|
||||||
const content: string;
|
const content: string;
|
||||||
|
|
77
package.json
77
package.json
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"description": "Swagger-generated API Reference Documentation",
|
"description": "Swagger-generated API Reference Documentation",
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/Rebilly/ReDoc"
|
"url": "git://github.com/Rebilly/ReDoc"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0",
|
"node": ">=6.9",
|
||||||
"npm": ">=3.0.0"
|
"npm": ">=3.0.0"
|
||||||
},
|
},
|
||||||
"main": "dist/redoc.min.js",
|
"main": "dist/redoc.min.js",
|
||||||
|
@ -15,14 +15,16 @@
|
||||||
"test": "npm run lint && node ./build/run_tests.js",
|
"test": "npm run lint && node ./build/run_tests.js",
|
||||||
"branch-release": "git reset --hard && branch-release",
|
"branch-release": "git reset --hard && branch-release",
|
||||||
"lint": "tslint -e \"lib/**/*{ngfactory|css.shim}.ts\" lib/**/*.ts",
|
"lint": "tslint -e \"lib/**/*{ngfactory|css.shim}.ts\" lib/**/*.ts",
|
||||||
"unit": "npm run build:sass && karma start",
|
"unit": "karma start",
|
||||||
"e2e": "npm run build:prod && npm run e2e-copy && npm run webdriver && protractor",
|
"e2e": "npm run build:prod && npm run e2e-copy && npm run webdriver && protractor",
|
||||||
"deploy": "node ./build/prepare_deploy.js && deploy-to-gh-pages --update demo",
|
"deploy": "node ./build/prepare_deploy.js && deploy-to-gh-pages --update demo",
|
||||||
"ngc": "ngc -p .",
|
"ngc": "ngc -p .",
|
||||||
|
"clean:dist": "npm run rimraf -- dist/",
|
||||||
|
"clean:aot": "npm run rimraf -- .tmp compiled lib/**/*.css",
|
||||||
|
"rimraf": "rimraf",
|
||||||
"webpack:prod": "webpack --config build/webpack.prod.js --profile --bail",
|
"webpack:prod": "webpack --config build/webpack.prod.js --profile --bail",
|
||||||
"build:sass": "node-sass -q -o lib lib",
|
"build:sass": "node-sass -q -o lib lib",
|
||||||
"build:prod": "npm run build:sass && npm run ngc && npm run webpack:prod",
|
"build:prod": "npm run clean:aot && npm run build:sass && npm run webpack:prod",
|
||||||
"build:prod-module": "npm run build:sass && npm run ngc && npm run webpack:prod && IS_MODULE=true npm run webpack:prod",
|
|
||||||
"build-dist": "npm run build:prod",
|
"build-dist": "npm run build:prod",
|
||||||
"stats": "webpack --config build/webpack.prod.js --json > stats.json",
|
"stats": "webpack --config build/webpack.prod.js --json > stats.json",
|
||||||
"start": "webpack-dev-server --config build/webpack.dev.js --content-base demo",
|
"start": "webpack-dev-server --config build/webpack.dev.js --content-base demo",
|
||||||
|
@ -45,19 +47,20 @@
|
||||||
"author": "Roman Hotsiy",
|
"author": "Roman Hotsiy",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/common": "^2.4.0",
|
"@angular/common": "^2.4.5",
|
||||||
"@angular/compiler": "^2.4.0",
|
"@angular/compiler": "^2.4.5",
|
||||||
"@angular/compiler-cli": "^2.4.0",
|
"@angular/compiler-cli": "^2.4.5",
|
||||||
"@angular/core": "^2.4.0",
|
"@angular/core": "^2.4.5",
|
||||||
"@angular/platform-browser": "^2.4.0",
|
"@angular/platform-browser": "^2.4.5",
|
||||||
"@angular/platform-browser-dynamic": "^2.4.0",
|
"@angular/platform-browser-dynamic": "^2.4.5",
|
||||||
"@angular/platform-server": "^2.4.0",
|
"@angular/platform-server": "^2.4.5",
|
||||||
"@types/core-js": "^0.9.31",
|
"@types/core-js": "^0.9.31",
|
||||||
"@types/jasmine": "^2.2.32",
|
"@types/jasmine": "^2.2.32",
|
||||||
"@types/requirejs": "^2.1.26",
|
"@types/requirejs": "^2.1.26",
|
||||||
"@types/should": "^8.1.28",
|
"@types/should": "^8.1.28",
|
||||||
|
"@types/swagger-schema-official": "^2.0.0",
|
||||||
"angular2-template-loader": "^0.6.0",
|
"angular2-template-loader": "^0.6.0",
|
||||||
"awesome-typescript-loader": "^2.2.4",
|
"awesome-typescript-loader": "~3.0.0-beta.17",
|
||||||
"branch-release": "^1.0.3",
|
"branch-release": "^1.0.3",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"codelyzer": "^2.0.0-beta.4",
|
"codelyzer": "^2.0.0-beta.4",
|
||||||
|
@ -66,38 +69,42 @@
|
||||||
"css-loader": "^0.26.0",
|
"css-loader": "^0.26.0",
|
||||||
"deploy-to-gh-pages": "^1.1.2",
|
"deploy-to-gh-pages": "^1.1.2",
|
||||||
"http-server": "^0.9.0",
|
"http-server": "^0.9.0",
|
||||||
"istanbul-instrumenter-loader": "^0.2.0",
|
"istanbul-instrumenter-loader": "^1.2.0",
|
||||||
"jasmine-core": "^2.4.1",
|
"jasmine-core": "^2.4.1",
|
||||||
"jasmine-spec-reporter": "^2.4.0",
|
"jasmine-spec-reporter": "^3.1.0",
|
||||||
"karma": "^1.2.0",
|
"karma": "^1.4.1",
|
||||||
"karma-chrome-launcher": "^2.0.0",
|
"karma-chrome-launcher": "^2.0.0",
|
||||||
"karma-coverage": "github:douglasduteil/karma-coverage#next",
|
"karma-coverage": "^1.1.1",
|
||||||
"karma-coveralls": "^1.1.2",
|
"karma-coveralls": "^1.1.2",
|
||||||
"karma-jasmine": "^1.0.2",
|
"karma-jasmine": "^1.0.2",
|
||||||
"karma-mocha-reporter": "^2.0.0",
|
"karma-mocha-reporter": "^2.0.0",
|
||||||
"karma-phantomjs-launcher": "^1.0.0",
|
"karma-phantomjs-launcher": "^1.0.0",
|
||||||
"karma-phantomjs-shim": "^1.1.2",
|
"karma-phantomjs-shim": "^1.1.2",
|
||||||
|
"karma-remap-coverage": "^0.1.4",
|
||||||
"karma-should": "^1.0.0",
|
"karma-should": "^1.0.0",
|
||||||
"karma-sinon": "^1.0.4",
|
"karma-sinon": "^1.0.4",
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^1.8.0",
|
"karma-webpack": "^2.0.1",
|
||||||
"node-sass": "^4.1.1",
|
"ngc-webpack": "^1.2.0",
|
||||||
|
"node-sass": "^4.5.0",
|
||||||
"phantomjs-prebuilt": "^2.1.7",
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
"protractor": "^4.0.10",
|
"protractor": "^5.1.0",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"rxjs": "^5.0.1",
|
"rimraf": "^2.5.4",
|
||||||
"sass-loader": "^4.0.2",
|
"rxjs": "^5.1.0",
|
||||||
|
"sass-loader": "^4.1.1",
|
||||||
"shelljs": "^0.7.0",
|
"shelljs": "^0.7.0",
|
||||||
"should": "^11.1.0",
|
"should": "^11.1.0",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^1.17.2",
|
||||||
"source-map-loader": "^0.1.5",
|
"source-map-loader": "^0.1.5",
|
||||||
"string-replace-webpack-plugin": "0.0.4",
|
"string-replace-webpack-plugin": "0.0.5",
|
||||||
"style-loader": "^0.13.1",
|
"style-loader": "^0.13.1",
|
||||||
"ts-helpers": "^1.1.1",
|
"ts-helpers": "^1.1.1",
|
||||||
"tslint": "^4.0.2",
|
"tslint": "^4.3.1",
|
||||||
"typescript": "2.0.9",
|
"typescript": "^2.1.5",
|
||||||
"webpack": "^2.1.0-beta.28",
|
"webpack": "^2.2.1",
|
||||||
"webpack-dev-server": "^2.1.0-beta.12",
|
"webpack-dev-server": "^2.2.0-rc.0",
|
||||||
|
"webpack-merge": "^2.6.1",
|
||||||
"zone.js": "^0.7.2"
|
"zone.js": "^0.7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -105,20 +112,22 @@
|
||||||
"hint.css": "^2.3.2",
|
"hint.css": "^2.3.2",
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^3.1.2",
|
"json-schema-ref-parser": "^3.1.2",
|
||||||
|
"lunr": "^0.7.2",
|
||||||
|
"mark.js": "github:julmot/mark.js",
|
||||||
"openapi-sampler": "^0.3.3",
|
"openapi-sampler": "^0.3.3",
|
||||||
"prismjs": "^1.5.1",
|
"prismjs": "^1.5.1",
|
||||||
"remarkable": "^1.6.2",
|
"remarkable": "^1.6.2",
|
||||||
"scrollparent": "^1.0.0",
|
"scrollparent": "^1.0.0",
|
||||||
"slugify": "^1.0.2",
|
"slugify": "^1.0.2",
|
||||||
"stream-http": "^2.5.0"
|
"stream-http": "^2.6.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "^2.4.0",
|
"@angular/common": "^2.4.5",
|
||||||
"@angular/compiler": "^2.4.0",
|
"@angular/compiler": "^2.4.5",
|
||||||
"@angular/core": "^2.4.0",
|
"@angular/core": "^2.4.5",
|
||||||
"@angular/platform-browser": "^2.4.0",
|
"@angular/platform-browser": "^2.4.5",
|
||||||
"@angular/platform-browser-dynamic": "^2.4.0",
|
"@angular/platform-browser-dynamic": "^2.4.5",
|
||||||
"@angular/platform-server": "^2.4.0",
|
"@angular/platform-server": "^2.4.5",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"rxjs": "^5.0.1",
|
"rxjs": "^5.0.1",
|
||||||
"zone.js": "^0.7.2"
|
"zone.js": "^0.7.2"
|
||||||
|
|
|
@ -7,7 +7,7 @@ let config = {
|
||||||
baseUrl: 'http://localhost:3000',
|
baseUrl: 'http://localhost:3000',
|
||||||
framework: 'jasmine2',
|
framework: 'jasmine2',
|
||||||
onPrepare: function() {
|
onPrepare: function() {
|
||||||
var SpecReporter = require('jasmine-spec-reporter');
|
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||||
// add jasmine spec reporter
|
// add jasmine spec reporter
|
||||||
jasmine.getEnv().addReporter(new SpecReporter({displaySpecDuration: true}));
|
jasmine.getEnv().addReporter(new SpecReporter({displaySpecDuration: true}));
|
||||||
// load APIs.guru list
|
// load APIs.guru list
|
||||||
|
|
|
@ -99,10 +99,15 @@ function eachNth(obj, n) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInnerHtml(locator) {
|
||||||
|
return browser.executeScript("return arguments[0].innerHTML;", $(locator));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
loadJson: loadJson,
|
loadJson: loadJson,
|
||||||
verifyNoBrowserErrors: verifyNoBrowserErrors,
|
verifyNoBrowserErrors: verifyNoBrowserErrors,
|
||||||
scrollToEl: scrollToEl,
|
scrollToEl: scrollToEl,
|
||||||
fixFFTest: fixFFTest,
|
fixFFTest: fixFFTest,
|
||||||
eachNth: eachNth
|
eachNth: eachNth,
|
||||||
|
getInnerHtml: getInnerHtml
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ const verifyNoBrowserErrors = require('./helpers').verifyNoBrowserErrors;
|
||||||
const scrollToEl = require('./helpers').scrollToEl;
|
const scrollToEl = require('./helpers').scrollToEl;
|
||||||
const fixFFTest = require('./helpers').fixFFTest;
|
const fixFFTest = require('./helpers').fixFFTest;
|
||||||
const eachNth = require('./helpers').eachNth;
|
const eachNth = require('./helpers').eachNth;
|
||||||
|
const getInnerHtml = require('./helpers').getInnerHtml;
|
||||||
|
|
||||||
const URL = 'index.html';
|
const URL = 'index.html';
|
||||||
|
|
||||||
|
@ -56,17 +57,17 @@ describe('Scroll sync', () => {
|
||||||
|
|
||||||
it('should update active menu entries on page scroll forwards', () => {
|
it('should update active menu entries on page scroll forwards', () => {
|
||||||
scrollToEl('[section="tag/store"]').then(() => {
|
scrollToEl('[section="tag/store"]').then(() => {
|
||||||
expect($('.menu-item.active > .menu-item-header').getInnerHtml()).toContain('store');
|
expect(getInnerHtml('.menu-item.menu-item-depth-1.active > .menu-item-header')).toContain('store');
|
||||||
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
expect(getInnerHtml('.selected-tag')).toContain('store');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update active menu entries on page scroll backwards', () => {
|
it('should update active menu entries on page scroll backwards', () => {
|
||||||
scrollToEl('[operation-id="getPetById"]').then(() => {
|
scrollToEl('[operation-id="getPetById"]').then(() => {
|
||||||
expect($('.menu-item.menu-item-depth-1.active .menu-item-header').getInnerHtml()).toContain('pet');
|
expect(getInnerHtml('.menu-item.menu-item-depth-1.active .menu-item-header')).toContain('pet');
|
||||||
expect($('.selected-tag').getInnerHtml()).toContain('pet');
|
expect(getInnerHtml('.selected-tag')).toContain('pet');
|
||||||
expect($('.menu-item.menu-item-depth-2.active .menu-item-header').getInnerHtml()).toContain('Find pet by ID');
|
expect(getInnerHtml('.menu-item.menu-item-depth-2.active .menu-item-header')).toContain('Find pet by ID');
|
||||||
expect($('.selected-endpoint').getInnerHtml()).toContain('Find pet by ID');
|
expect(getInnerHtml('.selected-endpoint')).toContain('Find pet by ID');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -48,6 +48,8 @@ beforeEach(function() {
|
||||||
services.OptionsService,
|
services.OptionsService,
|
||||||
services.ComponentParser,
|
services.ComponentParser,
|
||||||
services.ContentProjector,
|
services.ContentProjector,
|
||||||
|
services.Marker,
|
||||||
|
services.SearchService,
|
||||||
{ provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync },
|
{ provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync },
|
||||||
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
||||||
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }}
|
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }}
|
||||||
|
@ -61,15 +63,6 @@ beforeEach(function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// afterEach(function() {
|
|
||||||
// TestBed.resetTestingModule();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// afterEach(function() {
|
|
||||||
// TestBed.resetTestEnvironment();
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
var testContext = require.context('..', true, /\.spec\.ts/);
|
var testContext = require.context('..', true, /\.spec\.ts/);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe('Utils', () => {
|
||||||
|
|
||||||
it('load should reject promise for invalid url', (done)=> {
|
it('load should reject promise for invalid url', (done)=> {
|
||||||
specMgr.load('/nonexisting/schema.json').then(() => {
|
specMgr.load('/nonexisting/schema.json').then(() => {
|
||||||
throw new Error('Succees handler should not be called');
|
throw new Error('Success handler should not be called');
|
||||||
}, () => {
|
}, () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -122,13 +122,13 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept pointer directly to parameters', () => {
|
it('should accept pointer directly to parameters', () => {
|
||||||
let params = specMgr.getMethodParams('/paths/test1/get/parameters', true);
|
let params = specMgr.getMethodParams('/paths/test1/get/parameters');
|
||||||
expect(params).not.toBeNull();
|
expect(params).not.toBeNull();
|
||||||
params.length.should.be.equal(2);
|
params.length.should.be.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve path params from Parameters Definitions Object', () => {
|
it('should resolve path params from Parameters Definitions Object', () => {
|
||||||
let params = specMgr.getMethodParams('/paths/test2/get', true);
|
let params = specMgr.getMethodParams('/paths/test2/get');
|
||||||
params.length.should.be.equal(2);
|
params.length.should.be.equal(2);
|
||||||
params[0].name.should.be.equal('methodParam');
|
params[0].name.should.be.equal('methodParam');
|
||||||
params[1].name.should.be.equal('extParam');
|
params[1].name.should.be.equal('extParam');
|
||||||
|
@ -136,14 +136,14 @@ describe('Utils', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve method params from Parameters Definitions Object', () => {
|
it('should resolve method params from Parameters Definitions Object', () => {
|
||||||
let params = specMgr.getMethodParams('/paths/test3/get', true);
|
let params = specMgr.getMethodParams('/paths/test3/get');
|
||||||
params.length.should.be.equal(1);
|
params.length.should.be.equal(1);
|
||||||
params[0].name.should.be.equal('extParam');
|
params[0].name.should.be.equal('extParam');
|
||||||
params[0]._pointer.should.be.equal('#/parameters/extparam');
|
params[0]._pointer.should.be.equal('#/parameters/extparam');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw for parameters other than array', () => {
|
it('should throw for parameters other than array', () => {
|
||||||
let func = () => specMgr.getMethodParams('/paths/test4/get', true);
|
let func = () => specMgr.getMethodParams('/paths/test4/get');
|
||||||
expect(func).toThrow();
|
expect(func).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": ".tmp",
|
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"types": [
|
"types": [
|
||||||
|
@ -20,7 +19,6 @@
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
".tmp",
|
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"awesomeTypescriptLoaderOptions": {
|
"awesomeTypescriptLoaderOptions": {
|
||||||
|
|
41
tsconfig.webpack.json
Normal file
41
tsconfig.webpack.json
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "es2015",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"noEmitHelpers": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"es2015",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.e2e.ts"
|
||||||
|
],
|
||||||
|
"awesomeTypescriptLoaderOptions": {
|
||||||
|
"forkChecker": true,
|
||||||
|
"useWebpackText": true
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"genDir": "./compiled",
|
||||||
|
"skipMetadataEmit": true
|
||||||
|
},
|
||||||
|
"compileOnSave": false,
|
||||||
|
"buildOnSave": false,
|
||||||
|
"atom": { "rewriteTsconfig": false }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user