mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-24 01:23:43 +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
|
||||
/demo/build
|
||||
.tmp
|
||||
compiled
|
||||
/coverage
|
||||
.ghpages-tmp
|
||||
stats.json
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.DS_Store
|
||||
**/.*
|
||||
.tmp
|
||||
compiled
|
||||
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '4.0'
|
||||
- '6'
|
||||
branches:
|
||||
except:
|
||||
- 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=
|
||||
addons:
|
||||
sauce_connect: true
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
cache: yarn
|
||||
before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
|
||||
before_script:
|
||||
- npm run e2e-server > /dev/null & # kill e2e server
|
||||
|
@ -32,7 +30,7 @@ before_script:
|
|||
after_script:
|
||||
- kill %1 # kill e2e server
|
||||
before_deploy:
|
||||
- if [[ ! -z "$TRAVIS_TAG" ]]; then npm run build:prod-module; fi
|
||||
- if [[ ! -z "$TRAVIS_TAG" ]]; then npm run build:prod; fi
|
||||
deploy:
|
||||
- provider: npm
|
||||
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)
|
||||
### Bug fixes
|
||||
* 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
|
||||
**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")
|
||||
|
||||
|
@ -17,15 +17,18 @@
|
|||
|
||||
## Features
|
||||
- Extremely easy deployment
|
||||
- It’s free and open-source project under MIT license
|
||||
- The widest OpenAPI features support (yes, it supports even `discriminator`)
|
||||
- Neat **interactive** documentation for nested objects
|
||||
|
||||
<img src="http://i.imgur.com/260gaV4.png" width="500">
|
||||
|
||||
- Code samples support (via vendor extension)
|
||||
- The widest OpenAPI features support (yes, it supports even `discriminator`) <br>
|
||||
![](docs/images/discriminator-demo.gif)
|
||||
- Neat **interactive** documentation for nested objects <br>
|
||||
![](docs/images/nested-demo.gif)
|
||||
- Code samples support (via vendor extension) <br>
|
||||
![](docs/images/code-samples-demo.gif)
|
||||
- Progressive loading with `lazy-rendering` options <br>
|
||||
![](docs/images/progressive-loading-demo.gif)
|
||||
- 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
|
||||
- [x] performance optimizations
|
||||
|
@ -38,7 +41,7 @@
|
|||
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
|
||||
- `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
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"**/.*",
|
||||
"node_modules",
|
||||
"tests",
|
||||
"compiled",
|
||||
"lib",
|
||||
"demo",
|
||||
"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 ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
||||
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
|
||||
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
||||
|
||||
const root = require('./helpers').root;
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
||||
// 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',
|
||||
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: {
|
||||
'redoc': './lib/index.ts',
|
||||
'polyfills': './lib/polyfills.ts',
|
||||
'vendor': './lib/vendor.ts',
|
||||
'polyfills': './lib/polyfills.ts'
|
||||
'redoc': './lib/index.ts',
|
||||
},
|
||||
|
||||
devServer: {
|
||||
contentBase: root('demo'),
|
||||
watchContentBase: true,
|
||||
|
@ -47,87 +30,22 @@ module.exports = {
|
|||
hot: false,
|
||||
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: {
|
||||
exprContextCritical: false,
|
||||
rules: [{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
loader: 'source-map-loader',
|
||||
exclude: [
|
||||
/node_modules/
|
||||
]
|
||||
}, {
|
||||
enforce: 'pre',
|
||||
test: /\.ts$/,
|
||||
exclude: [
|
||||
/node_modules/
|
||||
],
|
||||
loader: StringReplacePlugin.replace({
|
||||
replacements: [
|
||||
rules: [
|
||||
{
|
||||
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$/,
|
||||
loaders: [
|
||||
'awesome-typescript-loader',
|
||||
'angular2-template-loader'
|
||||
use: [
|
||||
'awesome-typescript-loader?{configFileName: "tsconfig.webpack.json"}',
|
||||
'angular2-template-loader',
|
||||
],
|
||||
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: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: ['vendor', 'polyfills'],
|
||||
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 config = {
|
||||
context: root(),
|
||||
devtool: 'source-map',
|
||||
performance: { hints: false },
|
||||
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||
const commonConfig = require('./webpack.common.js');
|
||||
|
||||
const config = webpackMerge(commonConfig({
|
||||
IS_PRODUCTION: true,
|
||||
AOT: true
|
||||
}), {
|
||||
devtool: 'source-map',
|
||||
|
||||
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: {
|
||||
'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',
|
||||
umdNamedDefine: true
|
||||
},
|
||||
|
||||
module: {
|
||||
exprContextCritical: false,
|
||||
rules: [{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
loader: 'source-map-loader',
|
||||
exclude: [
|
||||
/node_modules/
|
||||
]
|
||||
}, {
|
||||
test: /node_modules\/.*\.ngfactory\.ts$/,
|
||||
loader: 'awesome-typescript-loader'
|
||||
}, {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'awesome-typescript-loader',
|
||||
exclude: /(node_modules)/,
|
||||
}, {
|
||||
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$/]
|
||||
}]
|
||||
use: [
|
||||
'awesome-typescript-loader?{configFileName: "tsconfig.webpack.json"}',
|
||||
'angular2-template-loader',
|
||||
],
|
||||
exclude: [/\.(spec|e2e)\.ts$/]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
|
@ -84,7 +52,8 @@ const config = {
|
|||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
screw_ie8: true
|
||||
screw_ie8: true,
|
||||
negate_iife: false // for lazy v8
|
||||
},
|
||||
mangle: { screw_ie8 : true },
|
||||
output: {
|
||||
|
@ -92,14 +61,9 @@ const config = {
|
|||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
new webpack.BannerPlugin(BANNER),
|
||||
new webpack.DefinePlugin({
|
||||
'IS_PRODUCTION': true,
|
||||
'LIB_VERSION': VERSION,
|
||||
'AOT': true
|
||||
new webpack.BannerPlugin(BANNER)
|
||||
]
|
||||
})
|
||||
],
|
||||
}
|
||||
|
||||
if (IS_MODULE) {
|
||||
config.externals = {
|
||||
|
@ -114,17 +78,6 @@ if (IS_MODULE) {
|
|||
'rxjs': 'rxjs',
|
||||
'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;
|
||||
|
|
|
@ -1,73 +1,31 @@
|
|||
const webpack = require('webpack');
|
||||
|
||||
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',
|
||||
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: {
|
||||
exprContextCritical: false,
|
||||
rules: [{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
loader: 'source-map-loader',
|
||||
exclude: [
|
||||
/node_modules/
|
||||
]
|
||||
},{
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loaders: [
|
||||
'awesome-typescript-loader'
|
||||
]
|
||||
}, {
|
||||
use: 'awesome-typescript-loader'
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loaders: [
|
||||
'angular2-template-loader'
|
||||
use: [
|
||||
'angular2-template-loader',
|
||||
],
|
||||
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.
|
||||
* Instrument only testing sources.
|
||||
|
@ -85,11 +43,6 @@ module.exports = {
|
|||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'IS_PRODUCTION': false,
|
||||
'LIB_VERSION': VERSION,
|
||||
'AOT': 'false'
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
test: /\.ts$/,
|
||||
sourceMap: false,
|
||||
|
@ -106,4 +59,4 @@ module.exports = {
|
|||
/(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.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');
|
||||
|
||||
// $specInput.addEventListener('value-changed', function(e) {
|
||||
// schemaUrlInput.value = e.detail.value;
|
||||
// location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||
// });
|
||||
$specInput.addEventListener('value-changed', function(e) {
|
||||
schemaUrlInput.value = e.detail.value;
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||
});
|
||||
|
||||
function selectItem() {
|
||||
let value = this.innerText.trim();
|
||||
|
|
|
@ -676,6 +676,13 @@ definitions:
|
|||
description: Category name
|
||||
type: string
|
||||
minLength: 1
|
||||
sub:
|
||||
description: Test Sub Category
|
||||
type: object
|
||||
properties:
|
||||
prop1:
|
||||
type: string
|
||||
description: Dumb Property
|
||||
xml:
|
||||
name: Category
|
||||
Dog:
|
||||
|
@ -747,9 +754,6 @@ definitions:
|
|||
- photoUrls
|
||||
discriminator: petType
|
||||
properties:
|
||||
petType:
|
||||
description: Type of a pet
|
||||
type: string
|
||||
id:
|
||||
description: Pet ID
|
||||
allOf:
|
||||
|
@ -786,6 +790,9 @@ definitions:
|
|||
- available
|
||||
- pending
|
||||
- sold
|
||||
petType:
|
||||
description: Type of a pet
|
||||
type: string
|
||||
xml:
|
||||
name: Pet
|
||||
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
|
||||
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
||||
#### x-traitTag [DEPRECATED]
|
||||
#### x-traitTag
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
||||
|
|
|
@ -9,13 +9,13 @@ module.exports = function (config) {
|
|||
},
|
||||
|
||||
coverageReporter: {
|
||||
dir: 'coverage/',
|
||||
reporters: [
|
||||
{type: 'html'},
|
||||
{type: 'lcov'},
|
||||
{type: 'json'},
|
||||
{type: 'text-summary'}
|
||||
]
|
||||
type: 'in-memory'
|
||||
},
|
||||
|
||||
remapCoverageReporter: {
|
||||
'text-summary': null,
|
||||
'text-lcov': './coverage/lcov.info',
|
||||
'html': './coverage/html'
|
||||
},
|
||||
webpack: testWebpackConfig,
|
||||
webpackMiddleware: {
|
||||
|
@ -42,7 +42,7 @@ module.exports = function (config) {
|
|||
},
|
||||
colors: true,
|
||||
singleRun: true,
|
||||
reporters: travis ? ['mocha', 'coverage', 'coveralls'] : ['mocha', 'coverage'],
|
||||
reporters: travis ? ['mocha', 'coverage', 'remap-coverage', 'coveralls'] : ['mocha', 'coverage', 'remap-coverage'],
|
||||
|
||||
browsers: ['PhantomJS'],
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { platformBrowser } from '@angular/platform-browser';
|
||||
import { AppModuleNgFactory } from './app.module.ngfactory';
|
||||
import { AppModuleNgFactory } from '../compiled/lib/app.module.ngfactory';
|
||||
|
||||
export function bootstrapRedoc() {
|
||||
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
|
||||
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
||||
</span>
|
||||
<redoc-externalDocs [docs]="componentSchema.externalDocs"></redoc-externalDocs>
|
||||
</p>
|
||||
<span class="redoc-markdown-block">
|
||||
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
|
||||
import { SpecManager, BaseComponent } from '../base';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, Marker } from '../../services/index';
|
||||
|
||||
@Component({
|
||||
selector: 'api-info',
|
||||
|
@ -12,8 +12,13 @@ import { OptionsService } from '../../services/index';
|
|||
export class ApiInfo extends BaseComponent implements OnInit {
|
||||
info: any = {};
|
||||
specUrl: String;
|
||||
constructor(specMgr: SpecManager, private optionsService: OptionsService) {
|
||||
constructor(specMgr: SpecManager,
|
||||
private optionsService: OptionsService,
|
||||
elRef: ElementRef,
|
||||
marker: Marker
|
||||
) {
|
||||
super(specMgr);
|
||||
marker.addElement(elRef.nativeElement);
|
||||
}
|
||||
|
||||
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 {
|
||||
font-weight: normal;
|
||||
word-break: break-all;
|
||||
&.array::before, &.tuple::before {
|
||||
color: $black;
|
||||
font-weight: $base-font-weight;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, Input,
|
||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input,
|
||||
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
|
||||
|
||||
import { JsonSchema } from './json-schema';
|
||||
|
@ -15,8 +15,9 @@ var cache = {};
|
|||
template: '',
|
||||
styles: [':host { display:none }']
|
||||
})
|
||||
export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||
export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() auto: boolean;
|
||||
@Input() isRequestSchema: boolean;
|
||||
@Input() final: boolean = false;
|
||||
|
@ -63,7 +64,8 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
|
||||
// skip caching view with descendant schemas
|
||||
// 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();
|
||||
return;
|
||||
}
|
||||
|
@ -78,6 +80,10 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
Object.assign(instance, this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.auto && !this.disableLazy) return;
|
||||
this.loadCached();
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
<div class="tuple-item">
|
||||
<span class="tuple-item-index"> [{{idx}}]: </span>
|
||||
<json-schema class="nested-schema" [pointer]="item._pointer"
|
||||
[absolutePointer]="item._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||
</json-schema>
|
||||
</div>
|
||||
|
@ -52,12 +53,12 @@
|
|||
'discriminator': prop.isDiscriminator,
|
||||
'complex': prop._pointer,
|
||||
'additional': prop._additional,
|
||||
'expanded': subSchema.visible
|
||||
'expanded': subSchema.open
|
||||
}">
|
||||
<td class="param-name">
|
||||
<span class="param-name-wrap" (click)="subSchema.toggle()">
|
||||
<span class="param-name-content">
|
||||
{{prop._name}}
|
||||
{{prop.name}}
|
||||
<span class="param-name-enumvalue" [hidden]="!prop._enumItem"> {{prop._enumItem?.val | json}} </span>
|
||||
</span>
|
||||
<svg *ngIf="prop._pointer" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
|
@ -83,7 +84,7 @@
|
|||
</div>
|
||||
<div class="param-description" [innerHtml]="prop.description | marked"></div>
|
||||
<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"
|
||||
[value]="i" [attr.selected]="descendant.active ? '' : null" >{{descendant.name}}</option>
|
||||
</drop-down>
|
||||
|
@ -92,9 +93,9 @@
|
|||
</tr>
|
||||
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
|
||||
<td colspan="2">
|
||||
<zippy #subSchema title="Expand" [headless]="true" (open)="lazySchema.load()" [visible]="autoExpand">
|
||||
<json-schema-lazy #lazySchema [auto]="autoExpand" class="nested-schema" [pointer]="prop._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||
<zippy [attr.disabled]="prop.name" #subSchema title="Expand" [headless]="true" (openChange)="lazySchema.load()" [(open)]="prop.expanded">
|
||||
<json-schema-lazy #lazySchema [auto]="prop.expanded" class="nested-schema" [pointer]="prop._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema" absolutePointer="{{absolutePointer}}/properties/{{prop.name}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
||||
</td>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
'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 { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import { SchemaNormalizer, SchemaHelper, AppStateService } from '../../services/';
|
||||
import { JsonPointer, DescendantInfo } from '../../utils/';
|
||||
import { Zippy } from '../../shared/components';
|
||||
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||
|
||||
@Component({
|
||||
selector: 'json-schema',
|
||||
|
@ -11,8 +21,9 @@ import { SchemaNormalizer, SchemaHelper } from '../../services/index';
|
|||
styleUrls: ['./json-schema.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class JsonSchema extends BaseComponent implements OnInit {
|
||||
export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() final: boolean = false;
|
||||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
|
@ -20,16 +31,20 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
|
||||
schema: any = {};
|
||||
activeDescendant:any = {};
|
||||
hasDescendants: boolean = false;
|
||||
discriminator: string = null;
|
||||
_hasSubSchemas: boolean = false;
|
||||
properties: any;
|
||||
_isArray: boolean;
|
||||
normalizer: SchemaNormalizer;
|
||||
autoExpand = false;
|
||||
descendants: any;
|
||||
descendants: DescendantInfo[];
|
||||
|
||||
constructor(specMgr:SpecManager, private _renderer: Renderer, private _elementRef: ElementRef) {
|
||||
super(specMgr);
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
app: AppStateService,
|
||||
private _renderer: Renderer,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef) {
|
||||
super(specMgr, app);
|
||||
this.normalizer = new SchemaNormalizer(specMgr);
|
||||
}
|
||||
|
||||
|
@ -37,8 +52,11 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
return this.schema._pointer || this.pointer;
|
||||
}
|
||||
|
||||
selectDescendant(idx) {
|
||||
let activeDescendant = this.descendants[idx];
|
||||
selectDescendantByIdx(idx) {
|
||||
this.selectDescendant(this.descendants[idx]);
|
||||
}
|
||||
|
||||
selectDescendant(activeDescendant: DescendantInfo) {
|
||||
if (!activeDescendant || activeDescendant.active) return;
|
||||
this.descendants.forEach(d => {
|
||||
d.active = false;
|
||||
|
@ -51,13 +69,13 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
this.schema = this.normalizer.normalize(this.schema, this.normPointer,
|
||||
{resolved: true});
|
||||
this.preprocessSchema();
|
||||
this.activeDescendant = activeDescendant;
|
||||
}
|
||||
|
||||
initDescendants() {
|
||||
this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema);
|
||||
if (!this.descendants.length) return;
|
||||
this.hasDescendants = true;
|
||||
let discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
|
||||
let discriminator = this.discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
|
||||
let discrProperty = this.schema.properties &&
|
||||
this.schema.properties[discriminator];
|
||||
if (discrProperty && discrProperty.enum) {
|
||||
|
@ -72,12 +90,15 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
}).sort((a, b) => {
|
||||
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
||||
});
|
||||
this.descendants.forEach((d, idx) => d.idx = idx);
|
||||
}
|
||||
this.selectDescendant(0);
|
||||
this.selectDescendantByIdx(0);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.pointer) return;
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
|
||||
this.schema = this.componentSchema;
|
||||
if (!this.schema) {
|
||||
throw new Error(`Can't load component schema at ${this.pointer}`);
|
||||
|
@ -88,6 +109,7 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
|
||||
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
|
||||
this._isArray = this.schema._isArray;
|
||||
this.absolutePointer += (this._isArray ? '/items' : '');
|
||||
this.initDescendants();
|
||||
this.preprocessSchema();
|
||||
}
|
||||
|
@ -97,11 +119,12 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
|
||||
if (!this.schema.isTrivial) {
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
|
||||
this.autoExpand = this.properties && this.properties.length === 1;
|
||||
if (this.properties.length === 1) {
|
||||
this.properties[0].expanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
applyStyling() {
|
||||
|
@ -127,6 +152,46 @@ export class JsonSchema extends BaseComponent implements OnInit {
|
|||
return item.name + (item._pointer || '');
|
||||
}
|
||||
|
||||
trackByIdx(idx: number, _: any): number {
|
||||
return idx;
|
||||
}
|
||||
|
||||
findDescendantWithField(fieldName: string): DescendantInfo {
|
||||
let res: DescendantInfo;
|
||||
for (let descendantInfo of this.descendants) {
|
||||
let schema = this.specMgr.getDescendant(descendantInfo, this.schema);
|
||||
this.normalizer.reset();
|
||||
schema = this.normalizer.normalize(schema, this.normPointer,
|
||||
{resolved: true});
|
||||
if (schema.properties && schema.properties[fieldName]) {
|
||||
res = descendantInfo;
|
||||
break;
|
||||
};
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
if (ptr.startsWith(this.absolutePointer)) {
|
||||
let props = this.properties;
|
||||
if (!props) return;
|
||||
let relative = JsonPointer.relative(this.absolutePointer, ptr);
|
||||
let propName;
|
||||
if (relative.length > 1 && relative[0] === 'properties') {
|
||||
propName = relative[1];
|
||||
}
|
||||
let prop = props.find(p => p.name === propName);
|
||||
if (!prop) {
|
||||
let d = this.findDescendantWithField(propName);
|
||||
this.selectDescendant(d);
|
||||
prop = this.properties.find(p => p.name === propName);
|
||||
}
|
||||
if (prop && !prop.isTrivial) prop.expanded = true;
|
||||
this.cdr.markForCheck();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
|
|
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: `
|
||||
<span [style.width]='progress + "%"'> </span>
|
||||
`,
|
||||
styles: [`
|
||||
: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;
|
||||
}
|
||||
`]
|
||||
styleUrls: ['loading-bar.scss']
|
||||
})
|
||||
export class LoadingBar implements OnChanges {
|
||||
@Input() progress:number = 0;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<p *ngIf="method.info.description" class="method-description"
|
||||
[innerHtml]="method.info.description | marked">
|
||||
</p>
|
||||
<redoc-externalDocs [docs]="method.externalDocs"></redoc-externalDocs>
|
||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||
</div>
|
||||
|
|
|
@ -17,6 +17,10 @@ interface MethodInfo {
|
|||
bodyParam: any;
|
||||
summary: any;
|
||||
anchor: any;
|
||||
externalDocs: {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
@ -50,7 +54,8 @@ export class Method extends BaseComponent implements OnInit {
|
|||
bodyParam: this.findBodyParam(),
|
||||
summary: SchemaHelper.methodSummary(this.componentSchema),
|
||||
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() {
|
||||
let pathParams = this.specMgr.getMethodParams(this.pointer, true);
|
||||
let pathParams = this.specMgr.getMethodParams(this.pointer);
|
||||
let bodyParam = pathParams.find(param => param.in === 'body');
|
||||
return bodyParam;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<div class="tag-info" *ngIf="tag.name">
|
||||
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.id}}"></a>{{tag.name}} </h1>
|
||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||
<redoc-externalDocs [docs]="tag.metadata.externalDocs"></redoc-externalDocs>
|
||||
</div>
|
||||
<method *lazyFor="let methodItem of tag.items; let ready = ready;"
|
||||
[hidden]="!ready" [pointer]="methodItem.metadata.pointer"
|
||||
|
|
|
@ -27,12 +27,11 @@ export class ParamsList extends BaseComponent implements OnInit {
|
|||
|
||||
init() {
|
||||
this.params = [];
|
||||
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
|
||||
let paramsList = this.specMgr.getMethodParams(this.pointer);
|
||||
|
||||
paramsList = paramsList.map(paramSchema => {
|
||||
let propPointer = paramSchema._pointer;
|
||||
if (paramSchema.in === 'body') return paramSchema;
|
||||
paramSchema._name = paramSchema.name;
|
||||
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
<div class="background-actual"> </div>
|
||||
</div>
|
||||
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
<div class="menu-header">
|
||||
<api-logo> </api-logo>
|
||||
<redoc-search> </redoc-search>
|
||||
</div>
|
||||
<side-menu> </side-menu>
|
||||
</div>
|
||||
<div class="api-content">
|
||||
|
|
|
@ -40,12 +40,17 @@
|
|||
|
||||
.menu-content {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
side-menu {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
[sticky-sidebar] {
|
||||
width: $side-bar-width;
|
||||
background-color: $side-bar-bg-color;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transform: translateZ(0);
|
||||
z-index: 75;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { BaseComponent } from '../base';
|
|||
import * as detectScollParent from 'scrollparent';
|
||||
|
||||
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';
|
||||
|
||||
@Component({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
|
||||
[type]="response.type" [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">
|
||||
<header>
|
||||
Headers
|
||||
|
@ -20,6 +20,7 @@
|
|||
<header *ngIf="response.schema">
|
||||
Response Schema
|
||||
</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>
|
||||
</zippy>
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { Component,
|
||||
Input,
|
||||
OnInit,
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { statusCodeType } from '../../utils/helpers';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, AppStateService } from '../../services/index';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
|
||||
function isNumeric(n) {
|
||||
|
@ -17,14 +23,18 @@ function isNumeric(n) {
|
|||
styleUrls: ['./responses-list.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ResponsesList extends BaseComponent implements OnInit {
|
||||
export class ResponsesList extends BaseSearchableComponent implements OnInit {
|
||||
@Input() pointer:string;
|
||||
|
||||
responses: Array<any>;
|
||||
options: any;
|
||||
|
||||
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
||||
super(specMgr);
|
||||
constructor(specMgr:SpecManager,
|
||||
optionsMgr:OptionsService,
|
||||
app: AppStateService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
super(specMgr, app);
|
||||
this.options = optionsMgr.options;
|
||||
}
|
||||
|
||||
|
@ -50,6 +60,7 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
|||
resp.code = respCode;
|
||||
resp.type = statusCodeType(resp.code);
|
||||
|
||||
resp.expanded = false;
|
||||
if (this.options.expandResponses) {
|
||||
if (this.options.expandResponses === 'all' || this.options.expandResponses.has(respCode.toString())) {
|
||||
resp.expanded = true;
|
||||
|
@ -74,6 +85,17 @@ export class ResponsesList extends BaseComponent implements OnInit {
|
|||
return el.code;
|
||||
}
|
||||
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
if (ptr.startsWith(this.pointer)) {
|
||||
let code = JsonPointer.relative(this.pointer, ptr)[0];
|
||||
if (code && this.componentSchema[code]) {
|
||||
this.componentSchema[code].expanded = true;
|
||||
this.cdr.markForCheck();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
|
|
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()">
|
||||
<span class="menu-header"> API Reference: </span>
|
||||
<span class="selected-item-info">
|
||||
<span class="selected-tag"> {{activeCatCaption}} </span>
|
||||
<span class="selected-endpoint">{{activeItemCaption}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div #desktop id="resources-nav">
|
||||
<h5 class="menu-header"> API reference </h5>
|
||||
<ul class="menu-root">
|
||||
<side-menu-items [items]="menuItems" (activate)="activateAndScroll($event)"></side-menu-items>
|
||||
</ul>
|
||||
|
|
|
@ -11,13 +11,6 @@ ul.menu-root {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
text-transform: uppercase;
|
||||
color: $headers-color;
|
||||
padding: 0 $side-menu-item-hpadding;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
height: 3em;
|
||||
|
@ -39,15 +32,6 @@ ul.menu-root {
|
|||
float: right;
|
||||
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) {
|
||||
|
@ -61,10 +45,6 @@ ul.menu-root {
|
|||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#resources-nav .menu-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-subitems {
|
||||
height: auto;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, MenuItem } from '../../services/index';
|
||||
|
||||
import {
|
||||
inject,
|
||||
async
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
|
||||
import { MethodsList, SideMenu } from '../index';
|
||||
|
||||
|
@ -23,8 +23,8 @@ describe('Redoc components', () => {
|
|||
});
|
||||
describe('SideMenu Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let component: SideMenu;
|
||||
let fixture: ComponentFixture<TestAppComponent>;
|
||||
let specMgr;
|
||||
|
||||
beforeEach(inject([SpecManager, OptionsService],
|
||||
|
@ -53,8 +53,34 @@ describe('Redoc components', () => {
|
|||
});
|
||||
|
||||
it('should init component and component data', () => {
|
||||
expect(component).not.toBeNull();
|
||||
expect(component.data).not.toBeNull();
|
||||
should.exist(component);
|
||||
});
|
||||
|
||||
it('should clear active item and cat captions on change to null', () => {
|
||||
component.activeCatCaption = 'test';
|
||||
component.activeItemCaption = 'test';
|
||||
component.changed(null);
|
||||
component.activeCatCaption.should.be.equal('');
|
||||
component.activeItemCaption.should.be.equal('');
|
||||
});
|
||||
|
||||
it('should set active item and cat captions on change event', () => {
|
||||
let parentItem: MenuItem = {
|
||||
id: 'id',
|
||||
name: 'Item'
|
||||
};
|
||||
component.changed(parentItem);
|
||||
component.activeCatCaption.should.be.equal(parentItem.name);
|
||||
component.activeItemCaption.should.be.equal('');
|
||||
|
||||
let childItem: MenuItem = {
|
||||
id: 'id2',
|
||||
name: 'Child',
|
||||
parent: parentItem
|
||||
};
|
||||
component.changed(childItem);
|
||||
component.activeCatCaption.should.be.equal(parentItem.name);
|
||||
component.activeItemCaption.should.be.equal(childItem.name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Component, EventEmitter, Input, Output, ElementRef, ChangeDetectorRef,
|
|||
//import { global } from '@angular/core/src/facade/lang';
|
||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||
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';
|
||||
|
||||
const global = window;
|
||||
|
@ -55,7 +55,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(specMgr:SpecManager, elementRef:ElementRef,
|
||||
private scrollService:ScrollService, private menuService:MenuService,
|
||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef, private marker:Marker) {
|
||||
super(specMgr);
|
||||
this.$element = elementRef.nativeElement;
|
||||
|
||||
|
@ -64,7 +64,8 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
|||
|
||||
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) {
|
||||
|
@ -147,4 +148,7 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
|
|||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,11 @@
|
|||
'use strict';
|
||||
import { OnInit, OnDestroy } from '@angular/core';
|
||||
import { SpecManager } from '../utils/spec-manager';
|
||||
|
||||
import { AppStateService } from '../services/app-state.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
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
|
||||
* @class
|
||||
|
@ -65,3 +50,35 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
// 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 { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
||||
import { LoadingBar } from './LoadingBar/loading-bar';
|
||||
import { RedocSearch } from './Search/redoc-search';
|
||||
import { ExternalDocs } from './ExternalDocs/external-docs';
|
||||
|
||||
import { Redoc } from './Redoc/redoc';
|
||||
|
||||
export const REDOC_DIRECTIVES = [
|
||||
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||
LoadingBar, SideMenuItems
|
||||
LoadingBar, SideMenuItems, RedocSearch, ExternalDocs
|
||||
];
|
||||
|
||||
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions,
|
||||
LoadingBar, SideMenuItems }
|
||||
LoadingBar, SideMenuItems, RedocSearch, ExternalDocs }
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
AppStateService,
|
||||
ComponentParser,
|
||||
ContentProjector,
|
||||
Marker,
|
||||
SearchService,
|
||||
COMPONENT_PARSER_ALLOWED } from './services/';
|
||||
import { SpecManager } from './utils/spec-manager';
|
||||
|
||||
|
@ -34,7 +36,9 @@ import { SpecManager } from './utils/spec-manager';
|
|||
AppStateService,
|
||||
ComponentParser,
|
||||
ContentProjector,
|
||||
SearchService,
|
||||
LazyTasksService,
|
||||
Marker,
|
||||
{ provide: APP_ID, useValue: 'redoc' },
|
||||
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
||||
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
||||
|
|
|
@ -11,6 +11,8 @@ export class AppStateService {
|
|||
loading = new Subject<boolean>();
|
||||
initialized = new BehaviorSubject<any>(false);
|
||||
|
||||
searchContainingPointers = new BehaviorSubject<string|null[]>([]);
|
||||
|
||||
startLoading() {
|
||||
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-helper.service';
|
||||
export * from './warnings.service';
|
||||
export * from './search.service';
|
||||
|
||||
export * from './component-parser.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 { ScrollService } from './scroll.service';
|
||||
import { SchemaHelper } from './schema-helper.service';
|
||||
import { SpecManager } from '../utils/spec-manager';;
|
||||
import { SpecManager } from '../utils/spec-manager';
|
||||
|
||||
describe('Menu service', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -8,7 +8,7 @@ import { SpecManager } from '../utils/spec-manager';
|
|||
import { SchemaHelper } from './schema-helper.service';
|
||||
import { AppStateService } from './app-state.service';
|
||||
import { LazyTasksService } from '../shared/components/LazyFor/lazy-for';
|
||||
import { JsonPointer } from '../utils/JsonPointer';
|
||||
import { JsonPointer, MarkdownHeading, StringMap } from '../utils/';
|
||||
import * as slugify from 'slugify';
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ export interface MenuItem {
|
|||
active?: boolean;
|
||||
ready?: boolean;
|
||||
|
||||
level?: number;
|
||||
depth?: number;
|
||||
flatIdx?: number;
|
||||
|
||||
metadata?: any;
|
||||
|
@ -44,6 +44,7 @@ export interface MenuItem {
|
|||
@Injectable()
|
||||
export class MenuService {
|
||||
changed: EventEmitter<any> = new EventEmitter();
|
||||
changedActiveItem: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
items: MenuItem[];
|
||||
activeIdx: number = -1;
|
||||
|
@ -51,6 +52,7 @@ export class MenuService {
|
|||
private _flatItems: MenuItem[];
|
||||
private _hashSubscription: Subscription;
|
||||
private _scrollSubscription: Subscription;
|
||||
private _progressSubscription: Subscription;
|
||||
private _tagsWithMethods: any;
|
||||
|
||||
constructor(
|
||||
|
@ -70,6 +72,12 @@ export class MenuService {
|
|||
this._hashSubscription = this.hash.value.subscribe((hash) => {
|
||||
this.onHashChange(hash);
|
||||
});
|
||||
|
||||
this._progressSubscription = this.tasks.loadProgress.subscribe(progress => {
|
||||
if (progress === 100) {
|
||||
this.makeSureLastItemsEnabled();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get flatItems():MenuItem[] {
|
||||
|
@ -87,7 +95,7 @@ export class MenuService {
|
|||
idx = item.parent.flatIdx;
|
||||
}
|
||||
|
||||
// check if previous items can be enabled
|
||||
// check if previous items§ can be enabled
|
||||
let prevItem = this.flatItems[idx -= 1];
|
||||
while(prevItem && (!prevItem.metadata || !prevItem.items)) {
|
||||
prevItem.ready = true;
|
||||
|
@ -97,6 +105,15 @@ export class MenuService {
|
|||
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) {
|
||||
let stable = false;
|
||||
while(!stable) {
|
||||
|
@ -135,6 +152,7 @@ export class MenuService {
|
|||
|
||||
getEl(flatIdx:number):Element {
|
||||
if (flatIdx < 0) return null;
|
||||
if (flatIdx > this.flatItems.length - 1) return null;
|
||||
let currentItem = this.flatItems[flatIdx];
|
||||
if (!currentItem) return;
|
||||
if (currentItem.isGroup) currentItem = this.flatItems[flatIdx + 1];
|
||||
|
@ -156,6 +174,18 @@ export class MenuService {
|
|||
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 {
|
||||
return this.getEl(this.activeIdx);
|
||||
}
|
||||
|
@ -186,7 +216,7 @@ export class MenuService {
|
|||
cItem.parent.active = true;
|
||||
cItem = cItem.parent;
|
||||
}
|
||||
this.changed.next(item);
|
||||
this.changedActiveItem.next(item);
|
||||
}
|
||||
|
||||
changeActive(offset = 1):boolean {
|
||||
|
@ -236,40 +266,35 @@ export class MenuService {
|
|||
|
||||
addMarkdownItems() {
|
||||
let schema = this.specMgr.schema;
|
||||
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
||||
let id = 'section/' + slugify(header);
|
||||
let headings:StringMap<MarkdownHeading> = schema.info && schema.info['x-redoc-markdown-headers'] || {};
|
||||
Object.keys(headings).forEach(h => {
|
||||
let heading = headings[h];
|
||||
let id = 'section/' + heading.id;
|
||||
let item = {
|
||||
name: header,
|
||||
name: heading.title,
|
||||
id: id,
|
||||
items: null
|
||||
};
|
||||
item.items = this.getMarkdownSubheaders(item);
|
||||
item.items = this.getMarkdownSubheaders(item, heading);
|
||||
|
||||
this.items.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getMarkdownSubheaders(parent: MenuItem):MenuItem[] {
|
||||
getMarkdownSubheaders(parent: MenuItem, parentHeading: MarkdownHeading):MenuItem[] {
|
||||
let res = [];
|
||||
|
||||
let schema = this.specMgr.schema;
|
||||
for (let subheader of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-subheaders'] || []))) {
|
||||
let parts = subheader.split('/');
|
||||
let header = parts[0];
|
||||
if (parent.name !== header) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = parts[1];
|
||||
let id = parent.id + '/' + slugify(name);
|
||||
Object.keys(parentHeading.children || {}).forEach(h => {
|
||||
let heading = parentHeading.children[h];
|
||||
let id = 'section/' + heading.id;
|
||||
|
||||
let subItem = {
|
||||
name: name,
|
||||
name: heading.title,
|
||||
id: id,
|
||||
parent: parent
|
||||
};
|
||||
res.push(subItem);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -332,7 +357,7 @@ export class MenuService {
|
|||
name: tag['x-displayName'] || tag.name,
|
||||
id: id,
|
||||
description: tag.description,
|
||||
metadata: { type: 'tag' },
|
||||
metadata: { type: 'tag', externalDocs: tag.externalDocs },
|
||||
parent: parent,
|
||||
items: null
|
||||
};
|
||||
|
@ -403,6 +428,10 @@ export class MenuService {
|
|||
return res;
|
||||
}
|
||||
|
||||
getItemById(id: string):MenuItem {
|
||||
return this.flatItems.find(item => item.id === id || item.id === `section/${id}`);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._hashSubscription.unsubscribe();
|
||||
this._scrollSubscription.unsubscribe();
|
||||
|
|
|
@ -7,6 +7,7 @@ import * as slugify from 'slugify';
|
|||
interface PropertyPreprocessOptions {
|
||||
childFor: string;
|
||||
skipReadOnly?: boolean;
|
||||
discriminator?: string;
|
||||
}
|
||||
|
||||
// global var for this module
|
||||
|
@ -214,14 +215,13 @@ export class SchemaHelper {
|
|||
let propPointer = propertySchema._pointer ||
|
||||
JsonPointer.join(pointer, ['properties', propName]);
|
||||
propertySchema = SchemaHelper.preprocess(propertySchema, propPointer);
|
||||
propertySchema._name = propName;
|
||||
propertySchema.name = propName;
|
||||
// stop endless discriminator recursion
|
||||
if (propertySchema._pointer === opts.childFor) {
|
||||
propertySchema._pointer = null;
|
||||
}
|
||||
propertySchema._required = !!requiredMap[propName];
|
||||
propertySchema.isDiscriminator = (schema.discriminator === propName
|
||||
|| schema['x-extendedDiscriminator'] === propName);
|
||||
propertySchema.isDiscriminator = opts.discriminator === propName;
|
||||
return propertySchema;
|
||||
});
|
||||
|
||||
|
@ -244,7 +244,7 @@ export class SchemaHelper {
|
|||
var addProps = schema.additionalProperties;
|
||||
let ptr = addProps._pointer || JsonPointer.join(pointer, ['additionalProperties']);
|
||||
let res = SchemaHelper.preprocess(addProps, ptr);
|
||||
res._name = '<Additional Properties> *';
|
||||
res.name = '<Additional Properties> *';
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ export class SchemaNormalizer {
|
|||
let hasPtr = !!schema.$ref;
|
||||
if (opts.resolved && !hasPtr) this._dereferencer.visit(ptr);
|
||||
|
||||
if (opts.childFor) this._dereferencer.visit(opts.childFor);
|
||||
if (schema['x-redoc-normalized']) return schema;
|
||||
let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => {
|
||||
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
||||
|
@ -38,6 +39,7 @@ export class SchemaNormalizer {
|
|||
return resolved;
|
||||
});
|
||||
if (opts.resolved && !hasPtr) this._dereferencer.exit(ptr);
|
||||
if (opts.childFor) this._dereferencer.exit(opts.childFor);
|
||||
res['x-redoc-normalized'] = true;
|
||||
return res;
|
||||
}
|
||||
|
@ -94,7 +96,7 @@ class SchemaWalker {
|
|||
}
|
||||
}
|
||||
|
||||
class AllOfMerger {
|
||||
export class AllOfMerger {
|
||||
static merge(into, schemas) {
|
||||
into['x-derived-from'] = [];
|
||||
for (let i=0; i < schemas.length; i++) {
|
||||
|
@ -113,6 +115,7 @@ class AllOfMerger {
|
|||
defaults(into, subSchema);
|
||||
subSchema._pointer = tmpPtr;
|
||||
}
|
||||
into.discriminator = null;
|
||||
into.allOf = null;
|
||||
}
|
||||
|
||||
|
@ -196,7 +199,6 @@ class SchemaDereferencer {
|
|||
|
||||
dereference(schema: Reference, pointer:string):any {
|
||||
if (!schema || !schema.$ref) return schema;
|
||||
window['derefCount'] = window['derefCount'] ? window['derefCount'] + 1 : 1;
|
||||
let $ref = schema.$ref;
|
||||
let resolved = this._spec.byPointer($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';
|
||||
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'drop-down',
|
||||
template: `
|
||||
<select (change)=onChange($event.target.value)>
|
||||
<ng-content></ng-content>
|
||||
</select>
|
||||
`,
|
||||
templateUrl: 'drop-down.html',
|
||||
styleUrls: ['./drop-down.css']
|
||||
})
|
||||
export class DropDown implements AfterContentInit {
|
||||
export class DropDown implements AfterContentInit, OnChanges {
|
||||
@Output() change = new EventEmitter();
|
||||
@Input() active: string;
|
||||
elem: any;
|
||||
inst: any;
|
||||
constructor(elem:ElementRef) {
|
||||
|
@ -28,6 +25,12 @@ export class DropDown implements AfterContentInit {
|
|||
this.change.next(value);
|
||||
}
|
||||
|
||||
ngOnChanges(ch) {
|
||||
if (ch.active.currentValue) {
|
||||
this.inst && this.inst.select(ch.active.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.inst.dispose();
|
||||
}
|
||||
|
|
|
@ -38,11 +38,11 @@ describe('Common components', () => {
|
|||
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, 'stickBottom').and.callThrough();
|
||||
fixture.detectChanges();
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
expect(component.stick).toHaveBeenCalled();
|
||||
expect(component.stickBottom).toHaveBeenCalled();
|
||||
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({
|
||||
selector: 'tabs',
|
||||
template: `
|
||||
<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>
|
||||
`,
|
||||
templateUrl: 'tabs.html',
|
||||
styleUrls: ['tabs.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
|
@ -63,23 +57,8 @@ export class Tabs implements OnInit {
|
|||
|
||||
@Component({
|
||||
selector: 'tab',
|
||||
template: `
|
||||
<div class="tab-wrap" [ngClass]="{'active': active}">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
`,
|
||||
styles: [`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.tab-wrap {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-wrap.active {
|
||||
display: block;
|
||||
}`
|
||||
]
|
||||
templateUrl: 'tab.html',
|
||||
styleUrls: ['tab.css']
|
||||
})
|
||||
export class Tab {
|
||||
@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()">
|
||||
<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">
|
||||
|
|
|
@ -16,7 +16,7 @@ describe('Common components', () => {
|
|||
});
|
||||
describe('Zippy Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let component: Zippy;
|
||||
let nativeElement;
|
||||
let fixture;
|
||||
|
||||
|
@ -33,13 +33,13 @@ describe('Common components', () => {
|
|||
|
||||
it('should init component defaults', () => {
|
||||
component.empty.should.be.false();
|
||||
component.visible.should.be.false();
|
||||
component.open.should.be.false();
|
||||
component.type.should.be.equal('general');
|
||||
});
|
||||
|
||||
it('should init properties from dom params', () => {
|
||||
fixture.detectChanges();
|
||||
component.visible.should.be.true();
|
||||
component.open.should.be.true();
|
||||
component.empty.should.be.true();
|
||||
component.title.should.be.equal('Zippy');
|
||||
component.type.should.be.equal('test');
|
||||
|
@ -54,7 +54,7 @@ describe('Common components', () => {
|
|||
it('should open and close zippy', (done) => {
|
||||
fixture.detectChanges();
|
||||
component.empty = false;
|
||||
component.visible = true;
|
||||
component.open = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
let testComponent = fixture.debugElement.componentInstance;
|
||||
|
@ -62,13 +62,13 @@ describe('Common components', () => {
|
|||
let titleEl = nativeElement.querySelector('.zippy-title');
|
||||
mouseclick(titleEl);
|
||||
fixture.detectChanges();
|
||||
component.visible.should.be.false();
|
||||
component.open.should.be.false();
|
||||
testComponent.opened.should.be.false();
|
||||
|
||||
mouseclick(titleEl);
|
||||
fixture.detectChanges();
|
||||
setTimeout(() => {
|
||||
component.visible.should.be.true();
|
||||
component.open.should.be.true();
|
||||
testComponent.opened.should.be.true();
|
||||
testComponent.clickCount.should.be.equal(2);
|
||||
done();
|
||||
|
@ -95,21 +95,17 @@ describe('Common components', () => {
|
|||
@Component({
|
||||
selector: 'test-app',
|
||||
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 {
|
||||
opened: boolean;
|
||||
clickCount: number;
|
||||
constructor() {
|
||||
this.opened = false;
|
||||
this.clickCount = 0;
|
||||
this.clickCount = -1; // initial change detection
|
||||
}
|
||||
open() {
|
||||
this.opened = true;
|
||||
this.clickCount++;
|
||||
}
|
||||
close() {
|
||||
this.opened = false;
|
||||
open(val) {
|
||||
this.opened = val;
|
||||
this.clickCount++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, EventEmitter, Output, Input } from '@angular/core';
|
||||
import { Component, EventEmitter, Output, Input, OnChanges } from '@angular/core';
|
||||
@Component({
|
||||
selector: 'zippy',
|
||||
templateUrl: './zippy.html',
|
||||
styleUrls: ['./zippy.css']
|
||||
})
|
||||
export class Zippy {
|
||||
export class Zippy implements OnChanges {
|
||||
@Input() type = 'general';
|
||||
@Input() visible = false;
|
||||
@Input() empty = false;
|
||||
@Input() title;
|
||||
@Input() headless: boolean = false;
|
||||
@Output() open = new EventEmitter();
|
||||
@Output() close = new EventEmitter();
|
||||
@Input() open = false;
|
||||
@Output() openChange = new EventEmitter();
|
||||
|
||||
|
||||
toggle() {
|
||||
this.visible = !this.visible;
|
||||
this.open = !this.open;
|
||||
if (this.empty) return;
|
||||
if (this.visible) {
|
||||
this.open.next({});
|
||||
} else {
|
||||
this.close.next({});
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* that is not valid JsonPointer
|
||||
|
|
|
@ -14,16 +14,6 @@ export class BrowserDomAdapter {
|
|||
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> {
|
||||
var res = new Map<string, string>();
|
||||
var elAttrs = element.attributes;
|
||||
|
@ -59,15 +49,5 @@ export class BrowserDomAdapter {
|
|||
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; }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
'use strict';
|
||||
|
||||
export interface StringMap<T> {
|
||||
[key: string]: T;
|
||||
}
|
||||
|
||||
export function stringify(obj:any) {
|
||||
return JSON.stringify(obj);
|
||||
}
|
||||
|
@ -16,6 +20,18 @@ export function isBlank(obj: any): boolean {
|
|||
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) {
|
||||
if (statusCode < 100 || statusCode > 599) {
|
||||
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
|
||||
|| (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari']
|
||||
|| 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 './helpers';
|
||||
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 * as slugify from 'slugify';
|
||||
import * as Remarkable from 'remarkable';
|
||||
import { StringMap } from './';
|
||||
|
||||
declare var Prism: any;
|
||||
const md = new Remarkable({
|
||||
|
@ -13,17 +14,23 @@ const md = new Remarkable({
|
|||
highlight: (str, lang) => {
|
||||
if (lang === 'json') lang = 'js';
|
||||
let grammar = Prism.languages[lang];
|
||||
//fallback to clike
|
||||
// fallback to click
|
||||
if (!grammar) return str;
|
||||
return Prism.highlight(str, grammar);
|
||||
}
|
||||
});
|
||||
|
||||
export interface MarkdownHeading {
|
||||
title?: string;
|
||||
id: string;
|
||||
content?: string;
|
||||
children?: StringMap<MarkdownHeading>;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MdRenderer {
|
||||
public firstLevelHeadings: string[] = [];
|
||||
public secondLevelHeadings: string[] = [];
|
||||
public currentHeading: string = null;
|
||||
public headings: StringMap<MarkdownHeading> = {};
|
||||
currentTopHeading: MarkdownHeading;
|
||||
|
||||
private _origRules:any = {};
|
||||
private _preProcessors:Function[] = [];
|
||||
|
@ -45,21 +52,70 @@ export class MdRenderer {
|
|||
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) {
|
||||
if (tokens[idx].hLevel > 2 ) {
|
||||
return this._origRules.open(tokens, idx);
|
||||
} else {
|
||||
let content = tokens[idx + 1].content;
|
||||
if (tokens[idx].hLevel === 1 ) {
|
||||
this.firstLevelHeadings.push(content);
|
||||
this.currentHeading = content;
|
||||
let contentSlug = slugify(content);
|
||||
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
|
||||
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
||||
this.currentTopHeading = this.saveHeading(content);;
|
||||
let id = this.currentTopHeading.id;
|
||||
return `<h${tokens[idx].hLevel} section="section/${id}">` +
|
||||
`<a class="share-link" href="#section/${id}"></a>`;
|
||||
} else if (tokens[idx].hLevel === 2 ) {
|
||||
this.secondLevelHeadings.push(this.currentHeading + `/` + content);
|
||||
let contentSlug = slugify(this.currentHeading) + `/` + slugify(content);
|
||||
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
|
||||
let heading = this.saveHeading(content, this.currentTopHeading);
|
||||
let contentSlug = `${heading.id}`;
|
||||
return `<h${tokens[idx].hLevel} section="section/${heading.id}">` +
|
||||
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +143,8 @@ export class MdRenderer {
|
|||
|
||||
let res = md.render(text);
|
||||
|
||||
this.attachHeadingsContent(res);
|
||||
|
||||
if (!this.raw) {
|
||||
this.restoreOrigRules();
|
||||
}
|
||||
|
|
|
@ -7,10 +7,19 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|||
|
||||
import { MdRenderer } from './md-renderer';
|
||||
|
||||
import { SwaggerOperation, SwaggerParameter } from './swagger-typings';
|
||||
|
||||
function getDiscriminator(obj) {
|
||||
return obj.discriminator || obj['x-extendedDiscriminator'];
|
||||
}
|
||||
|
||||
export interface DescendantInfo {
|
||||
$ref: string;
|
||||
name: string;
|
||||
active?: boolean;
|
||||
idx?: number;
|
||||
}
|
||||
|
||||
export class SpecManager {
|
||||
public _schema: any = {};
|
||||
public apiUrl: string;
|
||||
|
@ -75,8 +84,7 @@ export class SpecManager {
|
|||
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
|
||||
}
|
||||
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-subheaders'] = mdRender.secondLevelHeadings;
|
||||
this._schema.info['x-redoc-markdown-headers'] = mdRender.headings;
|
||||
}
|
||||
|
||||
get schema() {
|
||||
|
@ -114,9 +122,9 @@ export class SpecManager {
|
|||
return obj;
|
||||
}
|
||||
|
||||
getMethodParams(methodPtr, resolveRefs) {
|
||||
getMethodParams(methodPtr:string):SwaggerParameter[] {
|
||||
/* inject JsonPointer into array elements */
|
||||
function injectPointers(array, root) {
|
||||
function injectPointers(array:SwaggerParameter[], root) {
|
||||
if (!Array.isArray(array)) {
|
||||
throw new Error(`parameters must be an array. Got ${typeof array} at ${root}`);
|
||||
}
|
||||
|
@ -133,17 +141,16 @@ export class SpecManager {
|
|||
|
||||
//get path params
|
||||
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 methodParams = this.byPointer(methodParamsPtr) || [];
|
||||
let methodParams:SwaggerParameter[] = this.byPointer(methodParamsPtr) || [];
|
||||
pathParams = injectPointers(pathParams, pathParamsPtr);
|
||||
methodParams = injectPointers(methodParams, methodParamsPtr);
|
||||
|
||||
if (resolveRefs) {
|
||||
// resolve references
|
||||
methodParams = this.resolveRefs(methodParams);
|
||||
pathParams = this.resolveRefs(pathParams);
|
||||
}
|
||||
return methodParams.concat(pathParams);
|
||||
}
|
||||
|
||||
|
@ -155,21 +162,18 @@ export class SpecManager {
|
|||
description: tag.description,
|
||||
'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;
|
||||
}
|
||||
|
||||
findDerivedDefinitions(defPointer, schema) {
|
||||
findDerivedDefinitions(defPointer: string, schema): DescendantInfo[] {
|
||||
let definition = schema || this.byPointer(defPointer);
|
||||
if (!definition) throw new Error(`Can't load schema at ${defPointer}`);
|
||||
if (!definition.discriminator && !definition['x-extendedDiscriminator']) return [];
|
||||
|
||||
let globalDefs = this._schema.definitions || {};
|
||||
let res = [];
|
||||
let res:DescendantInfo[] = [];
|
||||
let extendedDiscriminatorProp = definition['x-extendedDiscriminator'];
|
||||
for (let defName of Object.keys(globalDefs)) {
|
||||
let def = globalDefs[defName];
|
||||
|
@ -206,7 +210,7 @@ export class SpecManager {
|
|||
return res;
|
||||
}
|
||||
|
||||
getDescendant(descendant, componentSchema) {
|
||||
getDescendant(descendant:DescendantInfo, componentSchema:any) {
|
||||
let res;
|
||||
if (!getDiscriminator(componentSchema) && componentSchema.allOf) {
|
||||
// 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 "url"
|
||||
declare module "json-pointer";
|
||||
declare module "mark.js";
|
||||
|
||||
declare module "*.css" {
|
||||
const content: string;
|
||||
|
|
77
package.json
77
package.json
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0",
|
||||
"node": ">=6.9",
|
||||
"npm": ">=3.0.0"
|
||||
},
|
||||
"main": "dist/redoc.min.js",
|
||||
|
@ -15,14 +15,16 @@
|
|||
"test": "npm run lint && node ./build/run_tests.js",
|
||||
"branch-release": "git reset --hard && branch-release",
|
||||
"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",
|
||||
"deploy": "node ./build/prepare_deploy.js && deploy-to-gh-pages --update demo",
|
||||
"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",
|
||||
"build:sass": "node-sass -q -o lib lib",
|
||||
"build:prod": "npm run build:sass && npm run ngc && 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:prod": "npm run clean:aot && npm run build:sass && npm run webpack:prod",
|
||||
"build-dist": "npm run build:prod",
|
||||
"stats": "webpack --config build/webpack.prod.js --json > stats.json",
|
||||
"start": "webpack-dev-server --config build/webpack.dev.js --content-base demo",
|
||||
|
@ -45,19 +47,20 @@
|
|||
"author": "Roman Hotsiy",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@angular/common": "^2.4.0",
|
||||
"@angular/compiler": "^2.4.0",
|
||||
"@angular/compiler-cli": "^2.4.0",
|
||||
"@angular/core": "^2.4.0",
|
||||
"@angular/platform-browser": "^2.4.0",
|
||||
"@angular/platform-browser-dynamic": "^2.4.0",
|
||||
"@angular/platform-server": "^2.4.0",
|
||||
"@angular/common": "^2.4.5",
|
||||
"@angular/compiler": "^2.4.5",
|
||||
"@angular/compiler-cli": "^2.4.5",
|
||||
"@angular/core": "^2.4.5",
|
||||
"@angular/platform-browser": "^2.4.5",
|
||||
"@angular/platform-browser-dynamic": "^2.4.5",
|
||||
"@angular/platform-server": "^2.4.5",
|
||||
"@types/core-js": "^0.9.31",
|
||||
"@types/jasmine": "^2.2.32",
|
||||
"@types/requirejs": "^2.1.26",
|
||||
"@types/should": "^8.1.28",
|
||||
"@types/swagger-schema-official": "^2.0.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",
|
||||
"chalk": "^1.1.3",
|
||||
"codelyzer": "^2.0.0-beta.4",
|
||||
|
@ -66,38 +69,42 @@
|
|||
"css-loader": "^0.26.0",
|
||||
"deploy-to-gh-pages": "^1.1.2",
|
||||
"http-server": "^0.9.0",
|
||||
"istanbul-instrumenter-loader": "^0.2.0",
|
||||
"istanbul-instrumenter-loader": "^1.2.0",
|
||||
"jasmine-core": "^2.4.1",
|
||||
"jasmine-spec-reporter": "^2.4.0",
|
||||
"karma": "^1.2.0",
|
||||
"jasmine-spec-reporter": "^3.1.0",
|
||||
"karma": "^1.4.1",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-coverage": "github:douglasduteil/karma-coverage#next",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-coveralls": "^1.1.2",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-mocha-reporter": "^2.0.0",
|
||||
"karma-phantomjs-launcher": "^1.0.0",
|
||||
"karma-phantomjs-shim": "^1.1.2",
|
||||
"karma-remap-coverage": "^0.1.4",
|
||||
"karma-should": "^1.0.0",
|
||||
"karma-sinon": "^1.0.4",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-webpack": "^1.8.0",
|
||||
"node-sass": "^4.1.1",
|
||||
"karma-webpack": "^2.0.1",
|
||||
"ngc-webpack": "^1.2.0",
|
||||
"node-sass": "^4.5.0",
|
||||
"phantomjs-prebuilt": "^2.1.7",
|
||||
"protractor": "^4.0.10",
|
||||
"protractor": "^5.1.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"rxjs": "^5.0.1",
|
||||
"sass-loader": "^4.0.2",
|
||||
"rimraf": "^2.5.4",
|
||||
"rxjs": "^5.1.0",
|
||||
"sass-loader": "^4.1.1",
|
||||
"shelljs": "^0.7.0",
|
||||
"should": "^11.1.0",
|
||||
"sinon": "^1.17.2",
|
||||
"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",
|
||||
"ts-helpers": "^1.1.1",
|
||||
"tslint": "^4.0.2",
|
||||
"typescript": "2.0.9",
|
||||
"webpack": "^2.1.0-beta.28",
|
||||
"webpack-dev-server": "^2.1.0-beta.12",
|
||||
"tslint": "^4.3.1",
|
||||
"typescript": "^2.1.5",
|
||||
"webpack": "^2.2.1",
|
||||
"webpack-dev-server": "^2.2.0-rc.0",
|
||||
"webpack-merge": "^2.6.1",
|
||||
"zone.js": "^0.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -105,20 +112,22 @@
|
|||
"hint.css": "^2.3.2",
|
||||
"json-pointer": "^0.6.0",
|
||||
"json-schema-ref-parser": "^3.1.2",
|
||||
"lunr": "^0.7.2",
|
||||
"mark.js": "github:julmot/mark.js",
|
||||
"openapi-sampler": "^0.3.3",
|
||||
"prismjs": "^1.5.1",
|
||||
"remarkable": "^1.6.2",
|
||||
"scrollparent": "^1.0.0",
|
||||
"slugify": "^1.0.2",
|
||||
"stream-http": "^2.5.0"
|
||||
"stream-http": "^2.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "^2.4.0",
|
||||
"@angular/compiler": "^2.4.0",
|
||||
"@angular/core": "^2.4.0",
|
||||
"@angular/platform-browser": "^2.4.0",
|
||||
"@angular/platform-browser-dynamic": "^2.4.0",
|
||||
"@angular/platform-server": "^2.4.0",
|
||||
"@angular/common": "^2.4.5",
|
||||
"@angular/compiler": "^2.4.5",
|
||||
"@angular/core": "^2.4.5",
|
||||
"@angular/platform-browser": "^2.4.5",
|
||||
"@angular/platform-browser-dynamic": "^2.4.5",
|
||||
"@angular/platform-server": "^2.4.5",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^5.0.1",
|
||||
"zone.js": "^0.7.2"
|
||||
|
|
|
@ -7,7 +7,7 @@ let config = {
|
|||
baseUrl: 'http://localhost:3000',
|
||||
framework: 'jasmine2',
|
||||
onPrepare: function() {
|
||||
var SpecReporter = require('jasmine-spec-reporter');
|
||||
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||
// add jasmine spec reporter
|
||||
jasmine.getEnv().addReporter(new SpecReporter({displaySpecDuration: true}));
|
||||
// load APIs.guru list
|
||||
|
|
|
@ -99,10 +99,15 @@ function eachNth(obj, n) {
|
|||
return res;
|
||||
}
|
||||
|
||||
function getInnerHtml(locator) {
|
||||
return browser.executeScript("return arguments[0].innerHTML;", $(locator));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadJson: loadJson,
|
||||
verifyNoBrowserErrors: verifyNoBrowserErrors,
|
||||
scrollToEl: scrollToEl,
|
||||
fixFFTest: fixFFTest,
|
||||
eachNth: eachNth
|
||||
eachNth: eachNth,
|
||||
getInnerHtml: getInnerHtml
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ const verifyNoBrowserErrors = require('./helpers').verifyNoBrowserErrors;
|
|||
const scrollToEl = require('./helpers').scrollToEl;
|
||||
const fixFFTest = require('./helpers').fixFFTest;
|
||||
const eachNth = require('./helpers').eachNth;
|
||||
const getInnerHtml = require('./helpers').getInnerHtml;
|
||||
|
||||
const URL = 'index.html';
|
||||
|
||||
|
@ -56,17 +57,17 @@ describe('Scroll sync', () => {
|
|||
|
||||
it('should update active menu entries on page scroll forwards', () => {
|
||||
scrollToEl('[section="tag/store"]').then(() => {
|
||||
expect($('.menu-item.active > .menu-item-header').getInnerHtml()).toContain('store');
|
||||
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active > .menu-item-header')).toContain('store');
|
||||
expect(getInnerHtml('.selected-tag')).toContain('store');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update active menu entries on page scroll backwards', () => {
|
||||
scrollToEl('[operation-id="getPetById"]').then(() => {
|
||||
expect($('.menu-item.menu-item-depth-1.active .menu-item-header').getInnerHtml()).toContain('pet');
|
||||
expect($('.selected-tag').getInnerHtml()).toContain('pet');
|
||||
expect($('.menu-item.menu-item-depth-2.active .menu-item-header').getInnerHtml()).toContain('Find pet by ID');
|
||||
expect($('.selected-endpoint').getInnerHtml()).toContain('Find pet by ID');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active .menu-item-header')).toContain('pet');
|
||||
expect(getInnerHtml('.selected-tag')).toContain('pet');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-2.active .menu-item-header')).toContain('Find pet by ID');
|
||||
expect(getInnerHtml('.selected-endpoint')).toContain('Find pet by ID');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -48,6 +48,8 @@ beforeEach(function() {
|
|||
services.OptionsService,
|
||||
services.ComponentParser,
|
||||
services.ContentProjector,
|
||||
services.Marker,
|
||||
services.SearchService,
|
||||
{ provide: sharedComponents.LazyTasksService, useClass: sharedComponents.LazyTasksServiceSync },
|
||||
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
||||
{ 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/);
|
||||
|
||||
/*
|
||||
|
|
|
@ -15,7 +15,7 @@ describe('Utils', () => {
|
|||
|
||||
it('load should reject promise for invalid url', (done)=> {
|
||||
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();
|
||||
});
|
||||
|
@ -122,13 +122,13 @@ describe('Utils', () => {
|
|||
});
|
||||
|
||||
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();
|
||||
params.length.should.be.equal(2);
|
||||
});
|
||||
|
||||
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[0].name.should.be.equal('methodParam');
|
||||
params[1].name.should.be.equal('extParam');
|
||||
|
@ -136,14 +136,14 @@ describe('Utils', () => {
|
|||
});
|
||||
|
||||
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[0].name.should.be.equal('extParam');
|
||||
params[0]._pointer.should.be.equal('#/parameters/extparam');
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"target": "es5",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": true,
|
||||
"outDir": ".tmp",
|
||||
"pretty": true,
|
||||
"moduleResolution": "node",
|
||||
"types": [
|
||||
|
@ -20,7 +19,6 @@
|
|||
"compileOnSave": false,
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".tmp",
|
||||
"dist"
|
||||
],
|
||||
"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