mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-22 00:26:34 +03:00
Merge branch 'react-rewrite' 🎉
This commit is contained in:
commit
0382034ec2
|
@ -1,30 +1,10 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=false
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.scss]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.styl]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.coffee]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[{.analysis_options,*.yml,*.yaml}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[tslint.json]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
|
|
94
.github/CONTRIBUTING.md
vendored
Normal file
94
.github/CONTRIBUTING.md
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
# ReDoc Contributing Guide
|
||||
|
||||
Hi! We're really excited that you are interested in contributing to ReDoc. Before submitting your contribution though, please make sure to take a moment and read through the following guidelines.
|
||||
|
||||
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
|
||||
- [Pull Request Guidelines](#pull-request-guidelines)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Project Structure](#project-structure)
|
||||
|
||||
## Issue Reporting Guidelines
|
||||
- Before filing a new issue, try to make sure your problem doesn’t already exist.
|
||||
- The best way to get your bug fixed is to provide a reduced test case.
|
||||
|
||||
## Pull Request Guidelines
|
||||
Before submitting a pull request, please make sure the following is done:
|
||||
|
||||
1. Fork the repository and create your branch from master.
|
||||
2. Run `yarn` in the repository root.
|
||||
3. If you’ve fixed a bug or added code that should be tested, add tests!
|
||||
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
|
||||
5. Format your code with prettier (`yarn prettier`).
|
||||
|
||||
## Development Setup
|
||||
|
||||
You will need [Node.js](http://nodejs.org) at `v8.0.0+` and [Yarn](https://yarnpkg.com/en/) at `v1.2.0+`
|
||||
|
||||
After cloning the repo, run:
|
||||
|
||||
```bash
|
||||
$ yarn install # or yarn
|
||||
```
|
||||
|
||||
### Commonly used NPM scripts
|
||||
|
||||
``` bash
|
||||
# dev-server, watch and auto reload playground
|
||||
$ yarn start
|
||||
|
||||
# start playground app in production environement
|
||||
$ yarn start:prod
|
||||
|
||||
# runt tslint
|
||||
$ yarn lint
|
||||
|
||||
# try autofix tslint issues
|
||||
$ yarn lint --fix
|
||||
|
||||
# run unit tests
|
||||
$ yarn unit
|
||||
|
||||
# run e2e tests
|
||||
$ yarn e2e
|
||||
|
||||
# open cypress UI to debug e2e test
|
||||
$ yarn cy:open
|
||||
|
||||
# run the full test suite, include linting / unit / e2e
|
||||
$ yarn test
|
||||
|
||||
# prepare bundles
|
||||
$ yarn bundle
|
||||
|
||||
# format the code using prettier
|
||||
$ yarn prettier
|
||||
|
||||
# auto-generate changelog
|
||||
$ yarn changelog
|
||||
```
|
||||
|
||||
There are some other scripts available in the `scripts` section of the `package.json` file.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **`benchmark`**: contains basic perf benchmark. Not fully ready yet
|
||||
|
||||
- **`demo`**: contains project demo with demo specs and HMR playground used in development
|
||||
|
||||
- `demo/playground`: HMR Playground used in development
|
||||
|
||||
- **`docs`**: contains extra docs (linked from README.md)
|
||||
|
||||
- **`e2e`**: contains e2e tests. The e2e tests are written and run with [Cypress](https://www.cypress.io/).
|
||||
|
||||
|
||||
- **`src`**: contains the source code. The codebase is written in Typescript. CSS styles are managed with [Styled components](https://www.styled-components.com/). State is managed by [MobX](https://github.com/mobxjs/mobx)
|
||||
|
||||
- **`src/common-elements`**: containts common Styled elements or components used in multiple places
|
||||
- **`src/components`**: contains main visual components
|
||||
- **`src/services`**: contains different services used by ReDoc including MobX stores
|
||||
- **`src/services/models`**: contains classes for OpenAPI entities (e.g. Response, Operations, etc)
|
||||
- **`src/types`**: contains extra typescript typings including OpenAPI doc typings
|
||||
- **`src/utils`**: utility functions
|
||||
- **`src/styled-components.ts`**: - reexprots styled-components with proper typescript annotations using theme
|
||||
- **`src/theme.ts`**: - default theme (colors, fonts, etc) used by all the components
|
19
.gitignore
vendored
19
.gitignore
vendored
|
@ -20,20 +20,15 @@ npm-debug.log*
|
|||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
# compiled css
|
||||
lib/**/*.css
|
||||
lib/
|
||||
stats.json
|
||||
e2e/.build/
|
||||
cypress/
|
||||
bundles
|
||||
cli/index.js
|
||||
|
||||
# files produced by ngc
|
||||
lib/**/*.ngfactory.ts
|
||||
lib/**/*.css.shim.ts
|
||||
**/*.ngsummary.json
|
||||
lib/**/*.shim.ngstyle.ts
|
||||
/benchmark/revisions
|
||||
|
||||
# other
|
||||
/dist
|
||||
/demo/build
|
||||
.tmp
|
||||
compiled
|
||||
/coverage
|
||||
.ghpages-tmp
|
||||
stats.json
|
||||
|
|
15
.npmignore
15
.npmignore
|
@ -1,12 +1,3 @@
|
|||
.DS_Store
|
||||
**/.*
|
||||
compiled
|
||||
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
tests
|
||||
lib
|
||||
demo
|
||||
build
|
||||
coverage
|
||||
!bundles/
|
||||
!package.json
|
||||
!README.md
|
57
.travis.yml
57
.travis.yml
|
@ -1,41 +1,34 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
branches:
|
||||
except:
|
||||
- releases
|
||||
matrix:
|
||||
include:
|
||||
- env: JOB=unit
|
||||
- env: JOB=e2e-guru
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: JOB=e2e-guru
|
||||
- '8'
|
||||
cache: yarn
|
||||
env:
|
||||
global:
|
||||
- SAUCE_USERNAME: redoc
|
||||
- secure: OgYIXlyF3tvHVrMjqIMR7UPhNvMzNZRkKE++LsQPUT4aoUmHoeukjgE9tKCZac3Z+PcAlgHQ6dPUqjqij2d/emYBIrCx4ABvTOnxQYHbk+8GriwPvQjV33MCfKvRQAf3XPNcga02aOO/EEGejrtY/i5sz86XPZFXap4scxbvnF0rzOwGU9/A40JLsvKjtfdRxJ3aC2QBnUHP9JkfBOzbs3jjwEWYN7HdUBqtYU+wiUwvHhpEPQ8BVNF5ETp5OxxkTdqJlMRf3azC4U/Rl8dhUXZP2l1ZBdpgahDvSkx1zXPwKRg1jos88jahqH/v8DLHtXNuYQy4S48nKWZfaRtQegG/XsYAftYPtYage7L9D0nFQW9YZI0vwUBEzh2YTf+QNpVwUSyZhVuS1oS+scTb8RU8BVVZd6hRpJiaI2YiPM6ZFN7a69deV0cASov3yI7GSfeq4MmB4hu72Le+GhemQMLRxNgtr32c18XW1XoHEnpV7s0eKXnZrevnM06tW/nX9TQXGloaMUeQzoZY0AuF00zmrHDaNgwT63ULjY0W591Ey1Ztfq4ixdCoi6IPAtl49vTnNMMMqatI7Si/haI6hGyTy+H0xK1GlUpBCe5CGusXDSKUqrlKS5Izreepfj1G+12391ixiapkwV3SbYdQEhcDt7HWupyI4Zfkvg8EwwE=
|
||||
- GH_REF: github.com/Rebilly/ReDoc.git
|
||||
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
||||
- GIT_AUTHOR_NAME: RedocBot
|
||||
- secure: H2GClDJ7TEQaWgnk8d2fIVDpLwG3rTmN8TalUzrCqXGoG6ylCVmlwzKLgfPPWrVgSA7QTdfNV0ab7c2KyPoZBinHmeGMSuKNLVbhOXRc2VFxTBntBTuyJhfCgQEpUKvJesJUuv5RuBn//wC7VZcVVNc06TZDEe8+aDVYQuCRMXZJ4X3e6nSZ64QX2veyVcr+TjnRsZPkeBVcK9hngvuaxLb/hbJ85CvjiseZRt47PGIcrEpMn9n2GMw1m0fNnPoN+MBTSCnIklTmdjPG7t4GUSLmD6H0lNLdXuehYqmQAHgYrLec1aiFlV57QaDoDZrq2hSf4vDmCB/FVydGhD5JunI67pujoV2OnD1V80eUZhYNWOYsJ2Nfp4NxgXsPUcE6zWLYsLfktMPZADhOXInQRACt1cnx8zMYKLnch1RY/ZqjSg0nPtRjLzQ0lNsw5leixvBdBnMjxYHVyAWVwg8WiJMaLO9vog2Qnxg1NTacHO2CsOmm2rw6stpg7ndp/+nOleRlfUKggjt0Tn3FjwCIXeGup2P2EBa+WW2YMAaoMFofYviR5vRlKBgdKo9fsAruaO1r6nm2EdAjOlniyw92bEfU/qOey1nVp/oK2S82uT5In8KB7vl6rF3ak7WAsT9Q5vZUhsrG+eE4PVyIyWNBhs4A7pSwZGHDR/MYtp0E2ug=
|
||||
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
||||
- secure: vVRg9BKGBwF2MbXQnEccFL+XW0/7RaBmge9k7jbGYScBwkP3XjnQ/Xaj0cvTz2CM2EqXsbpwfvr4Jo+enW/E3MGy5RiEzv5hUe/jIFRR0gfAFbZxSTvg5xiFhTDffqQk0fncO4jXu+wPO5lZ2CMRWzyXz3i1MZhjMcAgoDr1+TRss/EGXLNHxr2RM88tpUW0fV2prIRoyGqhCgnYZtrm7hmr41Ej+itg1MqZLml/Rjkt3KsNgI+z0O5Qn3QSAO8GtPZqeftQxAjevOmxZGcssxY8EJvqbjAujr4y51WncXpEmCRPSY2J9R5+fkgZurqwnJapbQpjwKYemok3ps7EHg2gWkAlmPdQO4LKpbffGkM/o5b+8+HdIuQZugsSWQD9hUSftTAFLcfA1isi7V2lHE1m8bX/vk9zIyDdcPSwIaFe9y+w3PexwFmTjPLq+nia/UY2kARFZMEIFAJby6gkA70DcAJ50QOM86InJu5DSzGbIssgTGAXCn0TPPyGveaurVLw8x61j3yh8LDF46gUHey3rqv6WjpCM9h/vg7X/gq5ve/5Q2KHscUKfs/sA53Mt7qPeqRZY1QCaaRjzqJO/ZraHqWWeKmPKaWhPGR0kYEnkvB+K9GZ+HNSWCltjCO4SJ1xeEl7CRqQxAwdiMATF5SKqyiC+bn5oc35mFgbRF8=
|
||||
- secure: ela1tn4wkJQZ8O4iv+4pIZi5cebxeCStVF1tEUe6qa6WWgJYVXmS2tEv3QQ36NUBFrP58Y6yl10XguPnvj/2BCqcZI4FUBHh3BfiBoUtXxDCVKI5LtlniNiOFGUwfzEeYka8T51zFlcUXSCCaxHkRZbmBsIzeJ39UwTi5fy0qwLv9GgL0czhwm8I8sZ8gyWdGmqpXNFEsb9JP4ZA3mw2qpWkGpGAqQPD9XSCkU3LmX1/ltwsBMAgGYKLLo7vU8d5KV2c8L1Gnxfl6BvfmqUD/dsas/1rnk08rU2nez5ekuQa2tJRkDLOv8bqvrGRLjHSUa3yPuisC6SsDGSU7/3DcozZyYsz7WQ6WI8tYabyjqqeJTF1N8a5T3IbZaZNV1J4JHOO9Cb/y7gIg4edANg6tbe7MzZpdEPRBnw6OkdTdirpNsWQ/jnfpY1hn6mraQZz/q8yaz3W21NjbBJhVnvfh5gWLKQ3YAAziCBhmmrThFhUu0czz+G920MuFo477TBcxvlrE7CaNJ0Q6yYkDehEPOv3jvEs1QVHPwuRrlaLTbBhrlTICKZ58gdX30O8N4i0Xgp/v6qrC03bplnMQc8E/uC61wcVLJixnlZVp8FODpUvPjsxVFkpuNSOIAaiqcERmoiPXx05Epzmr78hjU5rYCx/1MmVoeB4gs9YO+4guD4=
|
||||
- secure: YWBKHe3EdY3DcASsMaW3/scxba11eMRjJ8HebyKyampU9JTkyj3rFY9sfYQshusYCVa3/pt2LyYITNLDI2vYGGFYVIu9ia9kMBjt4kZ99QQxvXTftmBruF2Gcw6aqja9e5SnQrDUY+Wnc3k33SqQhhNGiyF6M1HpV0S8JJZNc3oQjZHKkZ93IwA1mU/UWL/K3wE/y5xiKz8d+DjsLrhU94G6NE2mhs0eaJW/fMLLYxn+UfDuidGaRQ4v4a2aRPecalzyLqdRFgFH2YJpBPFF3XEC23J/hm1QChI75iINChWbTrNTJqWYKKljjB7WLNtqVrm3S5qpZwl322nwBhwhPVtT4PqattMribCA8XMSL9Lk/g1hRaxH7gXgGHAfpPaMhyPcKBzQ5HJxYI+UXoKPZqfyxkZbnVY/3s7P0pZ8Nql+UouvFURZkbWK3WLWCISCVVVdyhnB3wn+jKLWdsYCtu9ibvYDDCDT64T08b4Gf30j3BfaAlgO4uU30Bg/IulrFnOC99qhslQRODxY6HGizr6ggtS1C8TFZ2/+3CXuHZJ3LDdj9WoHyBgYtMtnQf+qBVy6z6nvyx/QsdnQ/8A1twObuZoFCLPpOPoKmAbjApC8XS0TgIyqf5KGcq33Jxg+bIakNAGJW33n4jtZW7+Z+p8SVGLQmRC/558Tn402a58=
|
||||
addons:
|
||||
sauce_connect: true
|
||||
chrome: stable
|
||||
cache: yarn
|
||||
before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
|
||||
before_deploy:
|
||||
- if [[ ! -z "$TRAVIS_TAG" ]]; then npm run build:all; fi
|
||||
before_script: npm run bundle && npm run compile:cli
|
||||
script: npm test && npm run e2e-ci
|
||||
after_script: cat ./coverage/lcov.info | coveralls
|
||||
before_deploy: npm run compile:cli
|
||||
deploy:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email: gotsijroman@gmail.com
|
||||
api_key:
|
||||
secure: PuhWLERrCEFmXmdFpw2OVFlqpOIVDmgwk5JUJOYaFdVCh/smp0+jZCQ4vrdFpuG96rnDVirD+A8xvW6NgsNNaRthLgOB/LRdFN69rU6Gvn3At6wlnC55t5dlhxPvCfnzJcHVBLXX4EmMkjnZqDg2uczXTzPodr3FnQJNuXmP8B33fzDVLyHccvXZ90abwXWVrgRIXPU28niqCR8DOC2OTzs7wqz+BLNkYDRRbyYXsg62HWuD33x5iof5IqBmhzBt3usCGmF3QGcgHrXHdZw3sZnit8+Bua++3KrXR0x6HGXXN1AoXVmCAkCa5OTQ5R3tCRxiJN3P2KLnvWeZR74sTFkovJB/6pGCvbJ/c7Wnuw6sD7SgOUBD359ULB6lAf5OnxBLoNebX4JxxVXF+zA4E3Bl44VxkzDpPWc15xqBPMB5vBREzMVmJ5mExn2s5cmLQjADbl9h0y6gZnhnNJ+iTmqtrVyM0ZkF2rPrzrTdGD+ULmRIlTMkdD1bh+/TJ3RdXT3P4/zNUJmiNnvgnnJVYYvsGaXWF+7uCVHT/8k2RsoSHqgkqh0gkDqGSwVix55y5mC7T2Vk9lMBhm6MvFJXaonOX0kxJS4EDQ3plPd6/ybG+TLhwggYnQ8o9msU5Nt6FpUShKiezjKurIhbQZdwlVivX3tahjW2QjNDO58xGgY=
|
||||
on:
|
||||
tags: true
|
||||
condition: $JOB != e2e-guru
|
||||
- skip_cleanup: true
|
||||
provider: script
|
||||
script: npm run deploy
|
||||
on:
|
||||
tags: true
|
||||
condition: $JOB != e2e-guru
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email: gotsijroman@gmail.com
|
||||
tag: next
|
||||
api_key: $NPM_TOKEN
|
||||
on:
|
||||
tags: true
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: cd cli && yarn install && npm run ci-publish || true
|
||||
on:
|
||||
tags: true
|
||||
# TODO demo deployment to GH pages
|
||||
|
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"editor.formatOnSave": true,
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
236
CHANGELOG.md
236
CHANGELOG.md
|
@ -1,3 +1,228 @@
|
|||
<a name="2.0.0-alpha.15"></a>
|
||||
# [2.0.0-alpha.15](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.14...v2.0.0-alpha.15) (2018-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix broken css after installing polished ([6018042](https://github.com/Rebilly/Redoc/commit/6018042))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* more advanced theme engine ([1df690a](https://github.com/Rebilly/Redoc/commit/1df690a))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.14"></a>
|
||||
# [2.0.0-alpha.14](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.13...v2.0.0-alpha.14) (2018-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix CLI crash + build it on travis ([7769ba8](https://github.com/Rebilly/Redoc/commit/7769ba8))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.13"></a>
|
||||
# [2.0.0-alpha.13](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.12...v2.0.0-alpha.13) (2018-03-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* A couple minor bug fixes ([#436](https://github.com/Rebilly/Redoc/issues/436)) ([5dc21af](https://github.com/Rebilly/Redoc/commit/5dc21af))
|
||||
* add extra null-check + warning ([8757fa5](https://github.com/Rebilly/Redoc/commit/8757fa5))
|
||||
* add logo width to the theme ([28f2391](https://github.com/Rebilly/Redoc/commit/28f2391))
|
||||
* align logo by center ([18ec3ac](https://github.com/Rebilly/Redoc/commit/18ec3ac))
|
||||
* discriminator dropdown showing incorrect field if sorted ([bcf39dc](https://github.com/Rebilly/Redoc/commit/bcf39dc))
|
||||
* fix crash when referencing non-existing security scheme ([1f7fc44](https://github.com/Rebilly/Redoc/commit/1f7fc44))
|
||||
* fix overflowing content in JSON samples ([02c2413](https://github.com/Rebilly/Redoc/commit/02c2413))
|
||||
* fix right-panel blinking when scrolling + css improvements ([a78f9ab](https://github.com/Rebilly/Redoc/commit/a78f9ab))
|
||||
* fix search-indexing for SSR ([1428fb5](https://github.com/Rebilly/Redoc/commit/1428fb5))
|
||||
* fix the media queries utils so it gets the values from the current theme ([#420](https://github.com/Rebilly/Redoc/issues/420)) ([3924d3c](https://github.com/Rebilly/Redoc/commit/3924d3c))
|
||||
* fix worker import ([4896346](https://github.com/Rebilly/Redoc/commit/4896346))
|
||||
* make ReactStandalone react on props changes ([0cb0af2](https://github.com/Rebilly/Redoc/commit/0cb0af2))
|
||||
* merge inner properties of allOf ([8926dd4](https://github.com/Rebilly/Redoc/commit/8926dd4))
|
||||
* one-of dropdown not switching ([0f1b6a6](https://github.com/Rebilly/Redoc/commit/0f1b6a6))
|
||||
* referenced header name is empty ([13165fb](https://github.com/Rebilly/Redoc/commit/13165fb))
|
||||
* skipReadOnly/skipWritOnly not passing down to nested array ([6df8127](https://github.com/Rebilly/Redoc/commit/6df8127))
|
||||
* skipReadOnly/skipWritOnly not passing down to nested OneOf ([2462639](https://github.com/Rebilly/Redoc/commit/2462639))
|
||||
* various search fixes ([b797c96](https://github.com/Rebilly/Redoc/commit/b797c96))
|
||||
* writeOnly not respected in response samples ([87abdf7](https://github.com/Rebilly/Redoc/commit/87abdf7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add clear icon to searchbox ([825162e](https://github.com/Rebilly/Redoc/commit/825162e))
|
||||
* add hideDownloadButton option ([8dbe938](https://github.com/Rebilly/Redoc/commit/8dbe938))
|
||||
* add marker ([1ff2bd8](https://github.com/Rebilly/Redoc/commit/1ff2bd8))
|
||||
* arrow navigation in search results ([fe3245a](https://github.com/Rebilly/Redoc/commit/fe3245a))
|
||||
* basis search ([6990cd2](https://github.com/Rebilly/Redoc/commit/6990cd2))
|
||||
* ReDoc CLI ✨ ([390f6c1](https://github.com/Rebilly/Redoc/commit/390f6c1))
|
||||
* reqired-first sort order for params ([ecf33d2](https://github.com/Rebilly/Redoc/commit/ecf33d2))
|
||||
* serialize search-index ([e94f842](https://github.com/Rebilly/Redoc/commit/e94f842))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.12"></a>
|
||||
# [2.0.0-alpha.12](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.11...v2.0.0-alpha.12) (2018-02-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* basic responsiveness ([a29c3cc](https://github.com/Rebilly/Redoc/commit/a29c3cc))
|
||||
* crash in MarkdownRenderer on non-string ([dead161](https://github.com/Rebilly/Redoc/commit/dead161))
|
||||
* discriminator fix ([ff3bb24](https://github.com/Rebilly/Redoc/commit/ff3bb24))
|
||||
* filter out non-existing security schemas + warn ([ee822f6](https://github.com/Rebilly/Redoc/commit/ee822f6))
|
||||
* fix oneOf title for array ([1f3701d](https://github.com/Rebilly/Redoc/commit/1f3701d))
|
||||
* fix tbody > tr nesting warning ([a3cbb14](https://github.com/Rebilly/Redoc/commit/a3cbb14))
|
||||
* improve copy tooltip perf ([29207cf](https://github.com/Rebilly/Redoc/commit/29207cf))
|
||||
* resolve menu synchronization issue (use proper throttle) ([84d1c7b](https://github.com/Rebilly/Redoc/commit/84d1c7b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* responsive side menu ([3aab2d9](https://github.com/Rebilly/Redoc/commit/3aab2d9))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.11"></a>
|
||||
# [2.0.0-alpha.11](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.10...v2.0.0-alpha.11) (2018-01-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* courier misspelling ([#409](https://github.com/Rebilly/Redoc/issues/409)) ([96fb7ce](https://github.com/Rebilly/Redoc/commit/96fb7ce))
|
||||
* crash on 2-level md heading at the beginning ([e9f23f7](https://github.com/Rebilly/Redoc/commit/e9f23f7))
|
||||
* make active tab more clear ([4b5df22](https://github.com/Rebilly/Redoc/commit/4b5df22))
|
||||
* perfect scroll not working ([199f240](https://github.com/Rebilly/Redoc/commit/199f240))
|
||||
* use array items example ([12f79f0](https://github.com/Rebilly/Redoc/commit/12f79f0)), closes [#408](https://github.com/Rebilly/Redoc/issues/408)
|
||||
* wrap text in code samples ([6c71a66](https://github.com/Rebilly/Redoc/commit/6c71a66))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* port "copy to clipboard" / "expand/collapse all" functionality ([5bb0bdf](https://github.com/Rebilly/Redoc/commit/5bb0bdf)), closes [#410](https://github.com/Rebilly/Redoc/issues/410)
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.9"></a>
|
||||
# [2.0.0-alpha.9](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.8...v2.0.0-alpha.9) (2018-01-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* handle scrollYOffset in ScrollService ([dcab770](https://github.com/Rebilly/Redoc/commit/dcab770))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.8"></a>
|
||||
# [2.0.0-alpha.8](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.7...v2.0.0-alpha.8) (2018-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* undo section id + some minor fixes ([0253c5d](https://github.com/Rebilly/Redoc/commit/0253c5d))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.7"></a>
|
||||
# [2.0.0-alpha.7](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.6...v2.0.0-alpha.7) (2018-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add id attr to headers to work before react is loaded if ssr ([1743453](https://github.com/Rebilly/Redoc/commit/1743453))
|
||||
* crate spec as data/base64 link when ssr ([33678e6](https://github.com/Rebilly/Redoc/commit/33678e6))
|
||||
* example value is not showed if it is false ([9756364](https://github.com/Rebilly/Redoc/commit/9756364))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.6"></a>
|
||||
# [2.0.0-alpha.6](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.5...v2.0.0-alpha.6) (2018-01-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allOf and deref exit not only named refs ([435cccd](https://github.com/Rebilly/Redoc/commit/435cccd))
|
||||
* do not ignore path level parameters ([14f8408](https://github.com/Rebilly/Redoc/commit/14f8408))
|
||||
* improve rendering of types ([17da7b7](https://github.com/Rebilly/Redoc/commit/17da7b7))
|
||||
* move title propagation to the correct place ([0b0bc99](https://github.com/Rebilly/Redoc/commit/0b0bc99))
|
||||
* owerwrite text-align to left ([bfee3ed](https://github.com/Rebilly/Redoc/commit/bfee3ed))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* initial display security requirements ([50e2a58](https://github.com/Rebilly/Redoc/commit/50e2a58))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.5"></a>
|
||||
# [2.0.0-alpha.5](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.4...v2.0.0-alpha.5) (2017-12-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct pointer for the schema ([4ae1574](https://github.com/Rebilly/Redoc/commit/4ae1574))
|
||||
* bundle in reftools in lib build (do not crash on prod builds in create-react-app) ([57129d3](https://github.com/Rebilly/Redoc/commit/57129d3))
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.4"></a>
|
||||
# [2.0.0-alpha.4](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.3...v2.0.0-alpha.4) (2017-11-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add ellipsis for menu items with long words ([3421be2](https://github.com/Rebilly/Redoc/commit/3421be2))
|
||||
* crashes on some dereferencing/allOf merging cases ([335deb9](https://github.com/Rebilly/Redoc/commit/335deb9))
|
||||
* do not auto-append security-definitions if they are not in the spec ([426e5b6](https://github.com/Rebilly/Redoc/commit/426e5b6))
|
||||
* don't display operations without tags as tag items in menu ([ca81b6d](https://github.com/Rebilly/Redoc/commit/ca81b6d))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.3"></a>
|
||||
# [2.0.0-alpha.3](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.2...v2.0.0-alpha.3) (2017-11-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* crash when $ref is url encoded ([bdf6079](https://github.com/Rebilly/Redoc/commit/bdf6079))
|
||||
* make oneOf not skip fields defined alongside ([8680775](https://github.com/Rebilly/Redoc/commit/8680775))
|
||||
|
||||
|
||||
|
||||
<a name="2.0.0-alpha.2"></a>
|
||||
# 2.0.0-alpha.2 (2017-11-23)
|
||||
|
||||
### Bug Fixes
|
||||
* Fix crash when using type `file` in OpenAPI 2.0 in some places
|
||||
|
||||
<a name="2.0.0-alpha.1"></a>
|
||||
# 2.0.0-alpha.1 (2017-11-23)
|
||||
|
||||
Complete rewrite of ReDoc using React so here only major changes are listed.
|
||||
Complete rewrite also means that this rewrite may introduce issues, but they should be resolved before `2.0.0`.
|
||||
|
||||
### Features
|
||||
|
||||
- Basic Support for OpenAPI 3
|
||||
- Usage as a React component
|
||||
|
||||
### Deprecations
|
||||
|
||||
- Fonts are not loaded by ReDoc so you should load them. Default fonts can be loaded as bellow:
|
||||
|
||||
```html
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
```
|
||||
- no more bower releases
|
||||
- no more GitHub pages-based CDN. Use [unpkg.com](https://unpkg.com/) to access ReDoc releases
|
||||
|
||||
|
||||
### Known Regression (will be resolved before leaving alpha stage)
|
||||
- `lazyLoading` option not implemented yet
|
||||
- Copying to clipboard of samples not implemented yet
|
||||
- Search not implemented yet
|
||||
|
||||
<a name="1.21.2"></a>
|
||||
## [1.21.2](https://github.com/Rebilly/ReDoc/compare/v1.21.1...v1.21.2) (2018-02-26)
|
||||
|
||||
|
@ -51,6 +276,7 @@
|
|||
### Deprecations
|
||||
* Dropped bower support. No more dist files on the `releases` branch.
|
||||
|
||||
|
||||
<a name="1.19.3"></a>
|
||||
## [1.19.3](https://github.com/Rebilly/ReDoc/compare/v1.19.2...v1.19.3) (2017-11-16)
|
||||
|
||||
|
@ -71,11 +297,19 @@
|
|||
|
||||
<a name="1.19.1"></a>
|
||||
# [1.19.1](https://github.com/Rebilly/ReDoc/compare/v1.19.0...v1.19.1) (2017-10-02)
|
||||
<a name="2.0.0-alpha.16"></a>
|
||||
# [2.0.0-alpha.16](https://github.com/Rebilly/Redoc/compare/v2.0.0-alpha.15...v2.0.0-alpha.16) (2018-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* snapshot crashing on `constructor` prop ([04e8606](https://github.com/Rebilly/ReDoc/commit/04e8606)), closes [#341](https://github.com/Rebilly/ReDoc/issues/341)
|
||||
* move cli to a separate npm package ([95c7585](https://github.com/Rebilly/Redoc/commit/95c7585))
|
||||
* prefer `.extend` over `styled()` to make styles more predictable ([ed20ac1](https://github.com/Rebilly/Redoc/commit/ed20ac1))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* use new Context API for options ([e022349](https://github.com/Rebilly/Redoc/commit/e022349))
|
||||
|
||||
|
||||
<a name="1.19.0"></a>
|
||||
|
|
143
README.md
143
README.md
|
@ -1,21 +1,21 @@
|
|||
<p align="center">
|
||||
<img alt="ReDoc logo" src="/docs/images/redoc-logo.png" width="300px" />
|
||||
</p>
|
||||
<p align="center">
|
||||
<b>OpenAPI/Swagger-generated API Reference Documentation</b>
|
||||
</p>
|
||||
<div align="center">
|
||||
<img alt="ReDoc logo" src="https://raw.githubusercontent.com/Rebilly/ReDoc/master/docs/images/redoc-logo.png" width="400px" />
|
||||
|
||||
[![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)
|
||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||
|
||||
[![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")
|
||||
[![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) [![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) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
||||
|
||||
[![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
||||
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc)
|
||||
|
||||
[![Browser Compatibility](https://saucelabs.com/browser-matrix/redoc.svg)](https://saucelabs.com/u/redoc)
|
||||
|
||||
![ReDoc demo](demo/redoc-demo.png)
|
||||
</div>
|
||||
|
||||
## [Live demo](http://rebilly.github.io/ReDoc/)
|
||||
**This is README for `2.0` version of ReDoc (React based). README for `1.x` version is on the branch [v1.x](https://github.com/Rebilly/ReDoc/tree/v1.x)**
|
||||
|
||||
|
||||
![ReDoc demo](https://raw.githubusercontent.com/Rebilly/ReDoc/master/demo/redoc-demo.png)
|
||||
|
||||
## [Live demo (v1.x)](http://rebilly.github.io/ReDoc/)
|
||||
|
||||
[<img alt="Deploy to Github" src="http://i.imgur.com/YZmaqk3.png" height="60px">](https://github.com/Rebilly/generator-openapi-repo#generator-openapi-repo--) [<img alt="ReDoc as a service" src="http://i.imgur.com/edqdCv6.png" height="60px">](https://redoc.ly) [<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||
|
||||
|
@ -35,22 +35,28 @@
|
|||
- Multiple ReDoc instances on single page ([example](demo/examples/multiple-apis/index.html))
|
||||
|
||||
## Roadmap
|
||||
- [ ] [OpenAPI v3.0 support](https://github.com/Rebilly/ReDoc/issues/312)
|
||||
- [x] ~~[OpenAPI v3.0 support](https://github.com/Rebilly/ReDoc/issues/312)~~
|
||||
- [x] ~~performance optimizations~~
|
||||
- [x] ~~better navigation (menu improvements + search)~~
|
||||
- [x] ~~React rewrite~~
|
||||
- [x] ~~docs pre-rendering (performance and SEO)~~
|
||||
- [ ] ability to simple branding/styling
|
||||
- [ ] built-in API Console
|
||||
- [ ] docs pre-rendering (performance and SEO)
|
||||
|
||||
## Releases
|
||||
We host the latest and all the previous ReDoc releases on GitHub Pages-based **CDN**:
|
||||
**Important:** all the 2.x releases are deployed to npm and can be used via unpkg:
|
||||
- particular release, e.g. `v2.0.0-alpha.15`: https://unpkg.com/redoc@2.0.0-alpha.15/bundles/redoc.standalone.js
|
||||
- `next` release: https://unpkg.com/redoc@next/bundles/redoc.standalone.js
|
||||
|
||||
Additionally, all the 1.x releases are hosted on our 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 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`.
|
||||
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg.
|
||||
|
||||
## Version Guidance
|
||||
| ReDoc Release | OpenAPI Specification |
|
||||
|:--------------|:----------------------|
|
||||
| 2.0.0-alpha.x | 3.0, 2.0 |
|
||||
| 1.19.x | 2.0 |
|
||||
| 1.18.x | 2.0 |
|
||||
| 1.17.x | 2.0 |
|
||||
|
@ -75,6 +81,7 @@ We host the latest and all the previous ReDoc releases on GitHub Pages-based **C
|
|||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
|
@ -88,7 +95,7 @@ We host the latest and all the previous ReDoc releases on GitHub Pages-based **C
|
|||
</head>
|
||||
<body>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
|
||||
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
@ -108,12 +115,12 @@ or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
|||
### 2. Reference redoc script in HTML
|
||||
For **CDN**:
|
||||
```html
|
||||
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
|
||||
For npm:
|
||||
```html
|
||||
<script src="node_modules/redoc/dist/redoc.min.js"> </script>
|
||||
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script>
|
||||
```
|
||||
|
||||
### 3. Add `<redoc>` element to your page
|
||||
|
@ -123,6 +130,45 @@ For npm:
|
|||
|
||||
### 4. Enjoy :smile:
|
||||
|
||||
|
||||
## Usage as a React component
|
||||
|
||||
Import `RedocStandalone` component from 'redoc' module:
|
||||
|
||||
```js
|
||||
import { RedocStandalone } from 'redoc';
|
||||
```
|
||||
|
||||
and use it somewhere in your component:
|
||||
|
||||
```js
|
||||
<RedocStandalone specUrl="url/to/your/spec"/>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
<RedocStandalone spec={/* spec as an object */}/>
|
||||
```
|
||||
|
||||
Also you can pass options:
|
||||
|
||||
```js
|
||||
<RedocStandalone
|
||||
specUrl="http://rebilly.github.io/RebillyAPI/swagger.json"
|
||||
options={{
|
||||
nativeScrollbars: true,
|
||||
theme: { colors: { main: '#dd5522' } },
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Here are detailed [options docs](#redoc-options-object).
|
||||
|
||||
## ReDoc CLI
|
||||
|
||||
[See here](https://github.com/Rebilly/ReDoc/blob/react-rewrite/cli/README.md)
|
||||
|
||||
## Configuration
|
||||
|
||||
### Security Definition location
|
||||
|
@ -140,50 +186,43 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica
|
|||
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
||||
|
||||
### `<redoc>` tag attributes
|
||||
* `spec-url` - relative or absolute url to your spec file;
|
||||
* `untrusted-spec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
||||
* `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
||||
`scroll-y-offset` can be specified in various ways:
|
||||
### `<redoc>` options object
|
||||
You can use all of the following optins with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
||||
|
||||
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
||||
* `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
||||
`scrollYOffset` can be specified in various ways:
|
||||
* **number**: A fixed number of pixels to be used as offset;
|
||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset;
|
||||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
||||
* `suppress-warnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
||||
* `lazy-rendering` - if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](https://rebilly.github.io/ReDoc) for the example.
|
||||
* `hide-hostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||
* `hide-download-button` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
||||
* `expand-responses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expand-responses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||
* `required-props-first` - show required properties first ordered in the same order as in `required` array.
|
||||
* `no-auto-auth` - do not inject Authentication section automatically
|
||||
* `path-in-middle-panel` - show path link and HTTP verb in the middle panel instead of the right one
|
||||
* `hide-loading` - do not show loading animation. Useful for small docs
|
||||
* `native-scrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs)
|
||||
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
||||
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\rebilly.github.io/ReDoc) for the example.~~
|
||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
||||
* `noAutoAuth` - do not inject Authentication section automatically
|
||||
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
|
||||
* `hideLoading` - do not show loading animation. Useful for small docs
|
||||
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs)
|
||||
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
||||
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Rebilly/ReDoc/blob/react-rewrite/src/theme.ts)
|
||||
|
||||
## Advanced usage
|
||||
## Advanced usage of standalone version
|
||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||
```js
|
||||
Redoc.init(specOrSpecUrl, options)
|
||||
Redoc.init(specOrSpecUrl, options, element)
|
||||
```
|
||||
`specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format.
|
||||
`options` is javascript object with camel-cased version of `<redoc>` tag attribute names as the keys, e.g.:
|
||||
|
||||
- `specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format
|
||||
- `options` [options object](#redoc-options-object)
|
||||
- `element` DOM element to put ReDoc into
|
||||
|
||||
```js
|
||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||
scrollYOffset: 50
|
||||
})
|
||||
}, document.getElementById('redoc-container'))
|
||||
```
|
||||
|
||||
-----------
|
||||
## Development
|
||||
#### Running local dev-server
|
||||
- Clone repository
|
||||
`git clone https://github.com/Rebilly/ReDoc.git`
|
||||
- Go to the project folder
|
||||
`cd ReDoc`
|
||||
- Install dependencies
|
||||
`npm install`
|
||||
- _(optional)_ Replace `demo/swagger.yaml` with your own schema
|
||||
- Start the server
|
||||
`npm start`
|
||||
- Open `http://localhost:9000`
|
||||
|
||||
Alternatively, Docker can be used by just running `docker-compose up`.
|
||||
see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
125
benchmark/benchmark.js
Normal file
125
benchmark/benchmark.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
const beautifyBenchmark = require('beautify-benchmark');
|
||||
const sh = require('shelljs');
|
||||
const fs = require('fs');
|
||||
const pathJoin = require('path').join;
|
||||
const spawn = require('child_process').spawn;
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
args[0] = args[0] || 'HEAD';
|
||||
args[1] = args[1] || 'local';
|
||||
|
||||
let started = false;
|
||||
|
||||
console.log('Benchmarking revisions: ' + args.join(', '));
|
||||
|
||||
const localDistDir = './benchmark/revisions/local/bundles';
|
||||
sh.rm('-rf', localDistDir);
|
||||
console.log(`Building local dist: ${localDistDir}`);
|
||||
sh.mkdir('-p', localDistDir);
|
||||
exec(`yarn bundle:lib --output-path ${localDistDir}`);
|
||||
|
||||
const revisions = [];
|
||||
for (const arg of args) {
|
||||
revisions.push({ name: arg, path: buildRevisionDist(arg) });
|
||||
}
|
||||
|
||||
const configFile = `
|
||||
export const revisions = [ ${revisions.map(rev => JSON.stringify(rev)).join(', ')} ];
|
||||
`;
|
||||
|
||||
const configDir = './benchmark/revisions/config.js';
|
||||
console.log(`Writing config "${configDir}"`);
|
||||
fs.writeFileSync(configDir, configFile);
|
||||
|
||||
console.log('Starging benchmark server');
|
||||
const proc = spawn('npm', ['run', 'start:benchmark']);
|
||||
|
||||
proc.stdout.on('data', data => {
|
||||
if (data.toString().indexOf('Compiled successfully') > -1) {
|
||||
console.log('Server started');
|
||||
startBenchmark();
|
||||
}
|
||||
});
|
||||
|
||||
proc.stderr.on('data', data => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
|
||||
proc.on('close', code => {
|
||||
console.log(`Benchmark server stopped with code ${code}`);
|
||||
});
|
||||
|
||||
async function runPuppeteer() {
|
||||
return await puppeteer
|
||||
.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
|
||||
.then(async browser => {
|
||||
const page = await browser.newPage();
|
||||
let resolve;
|
||||
const prom = new Promise(_resolve => {
|
||||
resolve = _resolve;
|
||||
});
|
||||
page.on('console', async msg => {
|
||||
const args = msg.args();
|
||||
const obj = args.length > 0 && (await args[0].jsonValue());
|
||||
if (!obj) return;
|
||||
|
||||
if (obj.done) {
|
||||
beautifyBenchmark.log();
|
||||
// resolve(obj);
|
||||
} else if (obj.cycle) {
|
||||
beautifyBenchmark.add(obj.cycle);
|
||||
} else if (obj.allDone) {
|
||||
resolve();
|
||||
} else {
|
||||
console.log(obj);
|
||||
}
|
||||
});
|
||||
await page.goto('http://127.0.0.1:9090', { timeout: 0 });
|
||||
const res = await prom;
|
||||
await browser.close();
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
||||
async function startBenchmark() {
|
||||
if (started) return;
|
||||
started = true;
|
||||
console.log('Starting benchmarks');
|
||||
await runPuppeteer();
|
||||
|
||||
console.log('Killing benchmark server');
|
||||
proc.kill('SIGINT');
|
||||
}
|
||||
|
||||
function exec(command) {
|
||||
const { code, stdout, stderr } = sh.exec(command, { silent: true });
|
||||
if (code !== 0) {
|
||||
console.error(stdout);
|
||||
console.error(stderr);
|
||||
sh.exit(code);
|
||||
}
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
function buildRevisionDist(revision) {
|
||||
if (revision === 'local') {
|
||||
return localDistDir;
|
||||
}
|
||||
const hash = exec(`git log -1 --format=%h "${revision}"`);
|
||||
const buildDir = './benchmark/revisions/' + hash;
|
||||
const distDir = buildDir + '/bundles';
|
||||
if (sh.test('-d', distDir)) {
|
||||
console.log(`Using prebuilt "${revision}"(${hash}) revision: ${buildDir}`);
|
||||
return distDir;
|
||||
}
|
||||
console.log(`Building "${revision}"(${hash}) revision: ${buildDir}`);
|
||||
sh.mkdir('-p', buildDir);
|
||||
exec(`git archive "${hash}" | tar -xC "${buildDir}"`);
|
||||
|
||||
const pwd = sh.pwd();
|
||||
sh.cd(buildDir);
|
||||
exec('yarn remove cypress puppeteer && yarn && yarn bundle:lib');
|
||||
sh.cd(pwd);
|
||||
return distDir;
|
||||
}
|
28
benchmark/index.html
Normal file
28
benchmark/index.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>ReDoc</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<redoc id="example"></redoc>
|
||||
<!-- <redoc spec-url="./openapi.yaml"></redoc> -->
|
||||
<script src="https://unpkg.com/lodash@4.17.4/lodash.js"></script>
|
||||
<script src="https://unpkg.com/benchmark@2.1.4/benchmark.js"></script>
|
||||
<!-- <script src="../bundles/redoc.standalone.js"></script> -->
|
||||
</body>
|
||||
|
||||
</html>
|
128
benchmark/index.tsx
Normal file
128
benchmark/index.tsx
Normal file
|
@ -0,0 +1,128 @@
|
|||
import * as React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { Redoc, RedocProps } from '../src/components';
|
||||
|
||||
import { loadAndBundleSpec } from '../src/utils';
|
||||
|
||||
import { revisions } from './revisions/config';
|
||||
import { configure } from 'mobx';
|
||||
|
||||
declare var Benchmark;
|
||||
|
||||
configure({
|
||||
isolateGlobalState: true,
|
||||
});
|
||||
|
||||
const node = document.getElementById('example');
|
||||
|
||||
const renderRoot = (Component: typeof Redoc, props: RedocProps) =>
|
||||
render(<Component {...props} />, node!);
|
||||
|
||||
async function importRedocs() {
|
||||
return Promise.all(
|
||||
revisions.map(rev => {
|
||||
return import('./' + rev.path.substring(12) + '/redoc.lib.js');
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function startFullTime(redocs, resolvedSpec) {
|
||||
return new Promise(async resolve => {
|
||||
const suite = new Benchmark.Suite('Full time', {
|
||||
maxTime: 20,
|
||||
initCount: 2,
|
||||
onStart(event) {
|
||||
console.log(' ⏱️ ' + event.currentTarget.name);
|
||||
},
|
||||
onCycle(event) {
|
||||
console.log({ cycle: event.target });
|
||||
},
|
||||
onComplete() {
|
||||
console.log({ done: true });
|
||||
setTimeout(() => resolve(), 10);
|
||||
},
|
||||
});
|
||||
|
||||
revisions.forEach((rev, idx) => {
|
||||
const redoc = redocs[idx];
|
||||
suite.add(rev.name, () => {
|
||||
const store = new redoc.AppStore(resolvedSpec, 'openapi.yaml');
|
||||
renderRoot(redoc.Redoc, { store });
|
||||
unmountComponentAtNode(node!);
|
||||
});
|
||||
});
|
||||
|
||||
suite.run({ async: true });
|
||||
});
|
||||
}
|
||||
|
||||
function startInitStore(redocs, resolvedSpec) {
|
||||
return new Promise(async resolve => {
|
||||
const suite = new Benchmark.Suite('Create Store Time', {
|
||||
maxTime: 20,
|
||||
initCount: 2,
|
||||
onStart(event) {
|
||||
console.log(' ⏱️ ' + event.currentTarget.name);
|
||||
},
|
||||
onCycle(event) {
|
||||
console.log({ cycle: event.target });
|
||||
},
|
||||
onComplete() {
|
||||
console.log({ done: true });
|
||||
setTimeout(() => resolve(), 10);
|
||||
},
|
||||
});
|
||||
|
||||
revisions.forEach((rev, idx) => {
|
||||
const redoc = redocs[idx];
|
||||
suite.add(rev.name, () => {
|
||||
const store = new redoc.AppStore(resolvedSpec, 'openapi.yaml');
|
||||
store.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
suite.run({ async: true });
|
||||
});
|
||||
}
|
||||
|
||||
function startRenderTime(redocs, resolvedSpec) {
|
||||
return new Promise(async resolve => {
|
||||
const suite = new Benchmark.Suite('Render time', {
|
||||
maxTime: 20,
|
||||
initCount: 2,
|
||||
onStart(event) {
|
||||
console.log(' ⏱️ ' + event.currentTarget.name);
|
||||
},
|
||||
onCycle(event) {
|
||||
console.log({ cycle: event.target });
|
||||
unmountComponentAtNode(node!);
|
||||
},
|
||||
onComplete() {
|
||||
console.log({ done: true });
|
||||
setTimeout(() => resolve(), 10);
|
||||
},
|
||||
});
|
||||
|
||||
revisions.forEach((rev, idx) => {
|
||||
const redoc = redocs[idx];
|
||||
const store = new redoc.AppStore(resolvedSpec, 'openapi.yaml');
|
||||
suite.add(rev.name, () => {
|
||||
renderRoot(redoc.Redoc, { store });
|
||||
});
|
||||
});
|
||||
|
||||
suite.run({ async: true });
|
||||
});
|
||||
}
|
||||
|
||||
async function runBenchmarks() {
|
||||
const redocs: any[] = await importRedocs();
|
||||
const resolvedSpec = await loadAndBundleSpec('openapi.yaml');
|
||||
await startInitStore(redocs, resolvedSpec);
|
||||
await startRenderTime(redocs, resolvedSpec);
|
||||
await startFullTime(redocs, resolvedSpec);
|
||||
console.log({ allDone: true });
|
||||
}
|
||||
|
||||
runBenchmarks();
|
|
@ -1,9 +0,0 @@
|
|||
const path = require('path');
|
||||
function root(args) {
|
||||
args = Array.prototype.slice.call(arguments, 0);
|
||||
return path.join.apply(path, [__dirname, '..'].concat(args));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
root: root
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
require('shelljs/global');
|
||||
|
||||
set('-e');
|
||||
set('-v');
|
||||
|
||||
|
||||
cat([
|
||||
'lib/components/Redoc/redoc-initial-styles.css',
|
||||
'node_modules/perfect-scrollbar/dist/css/perfect-scrollbar.css',
|
||||
'node_modules/dropkickjs/build/css/dropkick.css',
|
||||
'node_modules/prismjs/themes/prism-dark.css',
|
||||
'node_modules/hint.css/hint.base.css'
|
||||
]).to('dist/redoc.css')
|
|
@ -1,12 +0,0 @@
|
|||
var path = require('path');
|
||||
|
||||
var paths = {
|
||||
outputName: 'redoc.min.js',
|
||||
output: 'dist/',
|
||||
demo: 'demo/**/*',
|
||||
releases: 'demo/releases/'
|
||||
}
|
||||
|
||||
paths.redocBuilt = path.join(paths.output, paths.outputName);
|
||||
|
||||
module.exports = paths;
|
|
@ -1,28 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
require('shelljs/global');
|
||||
|
||||
var paths = require('./paths');
|
||||
var path = require('path');
|
||||
|
||||
set('-e');
|
||||
set('-v');
|
||||
|
||||
// build
|
||||
exec('npm run build-dist');
|
||||
cd('demo');
|
||||
mv('index-gh.html', 'index.html');
|
||||
mkdir('-p', 'dist');
|
||||
cp('-R', '../dist/*', './dist/');
|
||||
cd('..');
|
||||
|
||||
var version = require(path.join(__dirname, '../package.json')).version;
|
||||
var versionDir = path.join(paths.releases, 'v' + version + '/');
|
||||
var latestDir = path.join(paths.releases, 'latest/');
|
||||
var v1Dir = path.join(paths.releases, 'v' + version.split('.')[0] + '.x.x/');
|
||||
mkdir('-p', versionDir)
|
||||
mkdir('-p', latestDir);
|
||||
mkdir('-p', v1Dir);
|
||||
cp(paths.redocBuilt, versionDir);
|
||||
cp(paths.redocBuilt, latestDir);
|
||||
cp(paths.redocBuilt, v1Dir);
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
require('shelljs/global');
|
||||
set('-e');
|
||||
|
||||
function isPR() {
|
||||
return process.env.TRAVIS_PULL_REQUEST && process.env.TRAVIS_PULL_REQUEST !== 'false';
|
||||
}
|
||||
|
||||
function isCI() {
|
||||
return !!process.env.CI;
|
||||
}
|
||||
|
||||
if (process.env.JOB === 'e2e-guru') {
|
||||
if (isPR()) {
|
||||
console.log('Skiping E2E tests on PR');
|
||||
return;
|
||||
}
|
||||
exec('npm run e2e');
|
||||
} else {
|
||||
exec('npm run unit');
|
||||
if (isPR()) {
|
||||
console.log('Skiping E2E tests on PR');
|
||||
return;
|
||||
}
|
||||
if (!isCI()) {
|
||||
console.log('Skiping E2E tests locally. Use `npm run e2e` to run');
|
||||
return;
|
||||
}
|
||||
console.log('Starting Basic E2E');
|
||||
exec('npm run e2e');
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
|
||||
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
|
||||
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
||||
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
|
||||
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
|
||||
const root = require('./helpers').root;
|
||||
|
||||
module.exports = function (options) {
|
||||
const conf = {
|
||||
performance: { hints: false },
|
||||
|
||||
output: {
|
||||
path: root('dist'),
|
||||
filename: '[name].js',
|
||||
sourceMapFilename: '[name].[id].map',
|
||||
chunkFilename: '[id].chunk.js'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.json', '.css'],
|
||||
alias: {
|
||||
http: 'stream-http',
|
||||
https: 'https-browserify'
|
||||
}
|
||||
},
|
||||
|
||||
externals: {
|
||||
'jquery': 'jquery',
|
||||
'esprima': 'esprima' // optional dep of ys-yaml not needed for redoc
|
||||
},
|
||||
|
||||
module: {
|
||||
exprContextCritical: false,
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.ts$/,
|
||||
exclude: [
|
||||
/node_modules/
|
||||
],
|
||||
loader: StringReplacePlugin.replace({
|
||||
replacements: [
|
||||
{
|
||||
pattern: /styleUrls:\s*\[\s*'([\w\.\/-]*)\.css'\s*\][\s,]*$/gm,
|
||||
replacement: function (match, p1, offset, string) {
|
||||
return `styleUrls: ['${p1}.scss'],`;
|
||||
}
|
||||
},
|
||||
{
|
||||
pattern: /(\.\/components\/Redoc\/redoc-initial-styles\.css)/gm,
|
||||
replacement: function (match, p1, offset, string) {
|
||||
return p1.replace('.css', '.scss');
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
},
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.js$/,
|
||||
loader: 'source-map-loader',
|
||||
exclude: [
|
||||
/node_modules/
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
use: 'json-loader'
|
||||
},
|
||||
{
|
||||
test: /lib[\\\/].*\.css$/,
|
||||
loaders: ['raw-loader'],
|
||||
exclude: [/redoc-initial-styles\.css$/]
|
||||
}, {
|
||||
test: /\.css$/,
|
||||
loaders: ['style-loader', 'css-loader?-import'],
|
||||
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.css$/]
|
||||
},
|
||||
{
|
||||
test: /lib[\\\/].*\.scss$/,
|
||||
loaders: ['raw-loader', 'sass-loader'],
|
||||
exclude: [/redoc-initial-styles\.scss$/]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader?-import',
|
||||
'sass-loader'
|
||||
],
|
||||
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'raw-loader'
|
||||
}
|
||||
],
|
||||
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CheckerPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'IS_PRODUCTION': options.IS_PRODUCTION,
|
||||
'LIB_VERSION': VERSION,
|
||||
'AOT': options.AOT
|
||||
}),
|
||||
|
||||
new StringReplacePlugin()
|
||||
],
|
||||
node: {
|
||||
global: true,
|
||||
crypto: 'empty',
|
||||
fs: 'empty',
|
||||
process: true,
|
||||
module: false,
|
||||
clearImmediate: false,
|
||||
setImmediate: false
|
||||
}
|
||||
};
|
||||
|
||||
// if (options.AOT) {
|
||||
// conf.plugins.push(
|
||||
// new ngcWebpack.NgcWebpackPlugin({
|
||||
// disable: !options.AOT,
|
||||
// tsConfig: root('tsconfig.webpack.json'),
|
||||
// resourceOverride: root('build/resource-override.js')
|
||||
// })
|
||||
// );
|
||||
// }
|
||||
|
||||
return conf;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
|
||||
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
||||
|
||||
const root = require('./helpers').root;
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
const IS_PRODUCTION = process.env.NODE_ENV === "production";
|
||||
|
||||
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||
const commonConfig = require('./webpack.common.js');
|
||||
|
||||
module.exports = webpackMerge(commonConfig({
|
||||
IS_PRODUCTION: IS_PRODUCTION,
|
||||
AOT: IS_PRODUCTION
|
||||
}), {
|
||||
devtool: '#inline-source-map',
|
||||
entry: {
|
||||
'polyfills': './lib/polyfills.ts',
|
||||
'redoc': './lib/index.ts',
|
||||
},
|
||||
devServer: {
|
||||
contentBase: root('demo'),
|
||||
watchContentBase: true,
|
||||
compress: true,
|
||||
watchOptions: {
|
||||
poll: true
|
||||
},
|
||||
port: 9000,
|
||||
hot: false,
|
||||
stats: 'errors-only'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
'awesome-typescript-loader',
|
||||
'angular2-template-loader',
|
||||
],
|
||||
exclude: [/\.(spec|e2e)\.ts$/]
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: ['vendor', 'polyfills'],
|
||||
minChunks: Infinity
|
||||
})
|
||||
]
|
||||
})
|
|
@ -1,83 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
|
||||
const root = require('./helpers').root;
|
||||
const BANNER =
|
||||
`ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||
-------------------------------------------------------------
|
||||
Version: ${VERSION}
|
||||
Repo: https://github.com/Rebilly/ReDoc`;
|
||||
|
||||
const IS_MODULE = process.env.IS_MODULE != null;
|
||||
|
||||
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||
const commonConfig = require('./webpack.common.js');
|
||||
|
||||
const config = webpackMerge(commonConfig({
|
||||
IS_PRODUCTION: true,
|
||||
AOT: true
|
||||
}), {
|
||||
devtool: 'source-map',
|
||||
|
||||
entry: {
|
||||
'redoc': IS_MODULE ? ['./lib/redoc.module.ts'] : ['./lib/polyfills.ts', './lib/index.ts']
|
||||
},
|
||||
|
||||
output: {
|
||||
path: root('dist'),
|
||||
filename: IS_MODULE ? '[name]-module.js' : '[name].min.js',
|
||||
sourceMapFilename: IS_MODULE ? '[name]-module.map' : '[name].min.map',
|
||||
library: 'Redoc',
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
'awesome-typescript-loader',
|
||||
'angular2-template-loader',
|
||||
],
|
||||
exclude: [/\.(spec|e2e)\.ts$/]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
debug: false
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false,
|
||||
screw_ie8: true
|
||||
},
|
||||
mangle: { screw_ie8 : true },
|
||||
output: {
|
||||
comments: false
|
||||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new webpack.BannerPlugin(BANNER)
|
||||
]
|
||||
})
|
||||
|
||||
if (IS_MODULE) {
|
||||
config.externals = {
|
||||
'jquery': 'jquery',
|
||||
'esprima': 'esprima', // optional dep of ys-yaml not needed for redoc
|
||||
'@angular/platform-browser-dynamic': '@angular/platform-browser-dynamic',
|
||||
'@angular/platform-browser': '@angular/platform-browser',
|
||||
'@angular/core': '@angular/core',
|
||||
'@angular/common': '@angular/common',
|
||||
'@angular/forms': '@angular/forms',
|
||||
'core-js': 'core-js',
|
||||
'rxjs': 'rxjs',
|
||||
'zone.js/dist/zone': 'zone.js/dist/zone'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = config;
|
|
@ -1,61 +0,0 @@
|
|||
const webpack = require('webpack');
|
||||
|
||||
const root = require('./helpers').root;
|
||||
const path = require('path');
|
||||
|
||||
const webpackMerge = require('webpack-merge'); // used to merge webpack configs
|
||||
const commonConfig = require('./webpack.common.js');
|
||||
|
||||
module.exports = webpackMerge(commonConfig({
|
||||
IS_PRODUCTION: true,
|
||||
AOT: false
|
||||
}), {
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
module: {
|
||||
exprContextCritical: false,
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: 'awesome-typescript-loader'
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: [
|
||||
'angular2-template-loader',
|
||||
],
|
||||
exclude: [/\.(spec|e2e)\.ts$/]
|
||||
},
|
||||
{
|
||||
enforce: 'post',
|
||||
test: /\.(js|ts)$/, loader: 'istanbul-instrumenter-loader',
|
||||
include: root('lib'),
|
||||
exclude: [
|
||||
/\.(e2e|spec)\.ts$/,
|
||||
/node_modules/
|
||||
]
|
||||
}]
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
test: /\.ts$/,
|
||||
sourceMap: false,
|
||||
inlineSourceMap: true,
|
||||
removeComments: true,
|
||||
module: "commonjs"
|
||||
}),
|
||||
// ignore changes during tests
|
||||
new webpack.WatchIgnorePlugin([
|
||||
/[\\\/]ReDoc$/i, // ignore change of ReDoc folder itself
|
||||
/node_modules[\\\/].*$/,
|
||||
/\.tmp[\\\/].*$/,
|
||||
/dist[\\\/].*$/,
|
||||
/(?:[^\\\/]*(?:[\\\/]|$))*[^\\\/]*\.css$/ // ignore css files
|
||||
]),
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
|
||||
path.resolve(__dirname, '../src')
|
||||
)
|
||||
],
|
||||
})
|
3
cli/.npmignore
Normal file
3
cli/.npmignore
Normal file
|
@ -0,0 +1,3 @@
|
|||
!index.js
|
||||
!package.json
|
||||
!README.md
|
21
cli/README.md
Normal file
21
cli/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# redoc-cli
|
||||
|
||||
**[ReDoc](https://github.com/Rebilly/ReDoc)'s Command Line Interface**
|
||||
|
||||
## Installation
|
||||
You can use redoc cli by installing `redoc-cli` globally or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
|
||||
|
||||
## Usage
|
||||
|
||||
Twe following commans are available:
|
||||
|
||||
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc. Supports SSR mode (`--ssr`) and can watch the spec (`--watch`)
|
||||
- `redoc-cli bundle [spec]` - bundles spec and ReDoc into **zero-dependency** HTML file.
|
||||
|
||||
Some examples:
|
||||
|
||||
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.main=orange`
|
||||
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars`
|
||||
- Bundle using custom template (check [default template](https://github.com/Rebilly/ReDoc/blob/react-rewrite/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
||||
|
||||
For more details run `redoc-cli --help`.
|
290
cli/index.ts
Normal file
290
cli/index.ts
Normal file
|
@ -0,0 +1,290 @@
|
|||
#!/usr/bin/env node
|
||||
import * as React from 'react';
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { ServerStyleSheet } from 'styled-components';
|
||||
import { createServer, ServerResponse, ServerRequest } from 'http';
|
||||
import * as zlib from 'zlib';
|
||||
import { join, dirname } from 'path';
|
||||
import { compile } from 'handlebars';
|
||||
|
||||
// @ts-ignore
|
||||
import { Redoc, loadAndBundleSpec, createStore } from 'redoc';
|
||||
|
||||
import { createReadStream, writeFileSync, ReadStream, readFileSync, watch, existsSync } from 'fs';
|
||||
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
type Options = {
|
||||
ssr?: boolean;
|
||||
watch?: boolean;
|
||||
cdn?: boolean;
|
||||
output?: string;
|
||||
title?: string;
|
||||
templateFileName?: string;
|
||||
redocOptions?: any;
|
||||
};
|
||||
|
||||
const BUNDLES_DIR = dirname(require.resolve('redoc'));
|
||||
|
||||
yargs
|
||||
.command(
|
||||
'serve [spec]',
|
||||
'start the server',
|
||||
yargs => {
|
||||
yargs.positional('spec', {
|
||||
describe: 'path or URL to your spec',
|
||||
});
|
||||
|
||||
yargs.option('s', {
|
||||
alias: 'ssr',
|
||||
describe: 'Enable server-side rendering',
|
||||
type: 'boolean',
|
||||
});
|
||||
|
||||
yargs.option('p', {
|
||||
alias: 'port',
|
||||
type: 'number',
|
||||
default: 8080,
|
||||
});
|
||||
|
||||
yargs.option('w', {
|
||||
alias: 'watch',
|
||||
type: 'boolean',
|
||||
});
|
||||
|
||||
yargs.demandOption('spec');
|
||||
return yargs;
|
||||
},
|
||||
async argv => {
|
||||
try {
|
||||
await serve(argv.port, argv.spec, {
|
||||
ssr: argv.ssr,
|
||||
watch: argv.watch,
|
||||
templateFileName: argv.template,
|
||||
redocOptions: argv.options || {},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.stack);
|
||||
}
|
||||
},
|
||||
)
|
||||
.command(
|
||||
'bundle [spec]',
|
||||
'bundle spec into zero-dependency HTML-file',
|
||||
yargs => {
|
||||
yargs.positional('spec', {
|
||||
describe: 'path or URL to your spec',
|
||||
});
|
||||
|
||||
yargs.option('o', {
|
||||
describe: 'Output file',
|
||||
alias: 'output',
|
||||
type: 'string',
|
||||
default: 'redoc-static.html',
|
||||
});
|
||||
|
||||
yargs.options('title', {
|
||||
describe: 'Page Title',
|
||||
type: 'string',
|
||||
default: 'ReDoc documentation',
|
||||
});
|
||||
|
||||
yargs.option('cdn', {
|
||||
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
});
|
||||
|
||||
yargs.demandOption('spec');
|
||||
return yargs;
|
||||
},
|
||||
async argv => {
|
||||
try {
|
||||
await bundle(argv.spec, {
|
||||
ssr: true,
|
||||
output: argv.o,
|
||||
cdn: argv.cdn,
|
||||
title: argv.title,
|
||||
templateFileName: argv.template,
|
||||
redocOptions: argv.options || {},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
},
|
||||
)
|
||||
.demandCommand()
|
||||
.options('t', {
|
||||
alias: 'template',
|
||||
describe: 'Path to handlebars page template, see https://git.io/vxZ3V for the example ',
|
||||
type: 'string',
|
||||
})
|
||||
.options('options', {
|
||||
describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars',
|
||||
}).argv;
|
||||
|
||||
async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||
let spec = await loadAndBundleSpec(pathToSpec);
|
||||
let pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||
|
||||
const server = createServer((request, response) => {
|
||||
console.time('GET ' + request.url);
|
||||
if (request.url === '/redoc.standalone.js') {
|
||||
respondWithGzip(
|
||||
createReadStream(join(BUNDLES_DIR, 'redoc.standalone.js'), 'utf8'),
|
||||
request,
|
||||
response,
|
||||
{
|
||||
'Content-Type': 'application/javascript',
|
||||
},
|
||||
);
|
||||
} else if (request.url === '/') {
|
||||
respondWithGzip(pageHTML, request, response);
|
||||
} else if (request.url === '/spec.json') {
|
||||
const specStr = JSON.stringify(spec, null, 2);
|
||||
respondWithGzip(specStr, request, response, {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.write('Not found');
|
||||
response.end();
|
||||
}
|
||||
|
||||
console.timeEnd('GET ' + request.url);
|
||||
});
|
||||
|
||||
console.log();
|
||||
|
||||
server.listen(port, () => console.log(`Server started: http://127.0.0.1:${port}`));
|
||||
|
||||
if (options.watch && existsSync(pathToSpec)) {
|
||||
watch(
|
||||
pathToSpec,
|
||||
debounce(async (event, filename) => {
|
||||
if (event === 'change' || (event === 'rename' && existsSync(filename))) {
|
||||
console.log(`${pathToSpec} changed, updating docs`);
|
||||
try {
|
||||
spec = await loadAndBundleSpec(pathToSpec);
|
||||
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||
console.log('Updated successfully');
|
||||
} catch (e) {
|
||||
console.error('Error while updating: ', e.message);
|
||||
}
|
||||
}
|
||||
}, 2200),
|
||||
);
|
||||
console.log(`👀 Watching ${pathToSpec} for changes...`);
|
||||
}
|
||||
}
|
||||
|
||||
async function bundle(pathToSpec, options: Options = {}) {
|
||||
const start = Date.now();
|
||||
const spec = await loadAndBundleSpec(pathToSpec);
|
||||
const pageHTML = await getPageHTML(spec, pathToSpec, { ...options, ssr: true });
|
||||
|
||||
writeFileSync(options.output!, pageHTML);
|
||||
const sizeInKiB = Math.ceil(Buffer.byteLength(pageHTML) / 1024);
|
||||
const time = Date.now() - start;
|
||||
console.log(
|
||||
`\n🎉 bundled successfully in: ${options.output!} (${sizeInKiB} KiB) [⏱ ${time / 1000}s]`,
|
||||
);
|
||||
}
|
||||
|
||||
async function getPageHTML(
|
||||
spec: any,
|
||||
pathToSpec: string,
|
||||
{ ssr, cdn, title, templateFileName, redocOptions = {} }: Options,
|
||||
) {
|
||||
let html, css, state;
|
||||
let redocStandaloneSrc;
|
||||
if (ssr) {
|
||||
console.log('Prerendering docs');
|
||||
|
||||
const specUrl = redocOptions.specUrl || (isURL(pathToSpec) ? pathToSpec : undefined);
|
||||
const store = await createStore(spec, specUrl, redocOptions);
|
||||
const sheet = new ServerStyleSheet();
|
||||
html = renderToString(sheet.collectStyles(React.createElement(Redoc, { store })));
|
||||
css = sheet.getStyleTags();
|
||||
state = await store.toJS();
|
||||
|
||||
if (!cdn) {
|
||||
redocStandaloneSrc = readFileSync(join(BUNDLES_DIR, 'redoc.standalone.js'));
|
||||
}
|
||||
}
|
||||
|
||||
templateFileName = templateFileName ? templateFileName : join(__dirname, './template.hbs');
|
||||
const template = compile(readFileSync(templateFileName).toString());
|
||||
return template({
|
||||
redocHTML: `
|
||||
<script>
|
||||
${(ssr && `const __redoc_state = ${JSON.stringify(state)};`) || ''}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var container = document.getElementById('redoc');
|
||||
Redoc.${
|
||||
ssr
|
||||
? 'hydrate(__redoc_state, container);'
|
||||
: `init("spec.json", ${JSON.stringify(redocOptions)}, container)`
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<div id="redoc">${(ssr && html) || ''}</div>`,
|
||||
redocHead: ssr
|
||||
? (cdn
|
||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||
: '<script src="redoc.standalone.js"></script>',
|
||||
title: title,
|
||||
});
|
||||
}
|
||||
|
||||
// credits: https://stackoverflow.com/a/9238214/1749888
|
||||
function respondWithGzip(
|
||||
contents: string | ReadStream,
|
||||
request: ServerRequest,
|
||||
response: ServerResponse,
|
||||
headers = {},
|
||||
) {
|
||||
let compressedStream;
|
||||
const acceptEncoding = (request.headers['accept-encoding'] as string) || '';
|
||||
if (acceptEncoding.match(/\bdeflate\b/)) {
|
||||
response.writeHead(200, { ...headers, 'content-encoding': 'deflate' });
|
||||
compressedStream = zlib.createDeflate();
|
||||
} else if (acceptEncoding.match(/\bgzip\b/)) {
|
||||
response.writeHead(200, { ...headers, 'content-encoding': 'gzip' });
|
||||
compressedStream = zlib.createGzip();
|
||||
} else {
|
||||
response.writeHead(200, headers);
|
||||
if (typeof contents === 'string') {
|
||||
response.write(contents);
|
||||
response.end();
|
||||
} else {
|
||||
contents.pipe(response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof contents === 'string') {
|
||||
compressedStream.write(contents);
|
||||
compressedStream.pipe(response);
|
||||
compressedStream.end();
|
||||
return;
|
||||
} else {
|
||||
contents.pipe(compressedStream).pipe(response);
|
||||
}
|
||||
}
|
||||
|
||||
function debounce(callback: Function, time: number) {
|
||||
let interval;
|
||||
return (...args) => {
|
||||
clearTimeout(interval);
|
||||
interval = setTimeout(() => {
|
||||
interval = null;
|
||||
callback(...args);
|
||||
}, time);
|
||||
};
|
||||
}
|
||||
|
||||
function isURL(str: string): boolean {
|
||||
return /^(https?:)\/\//m.test(str);
|
||||
}
|
28
cli/package.json
Normal file
28
cli/package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.3.5",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
"repository": "https://github.com/Rebilly/ReDoc",
|
||||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"handlebars": "^4.0.11",
|
||||
"isarray": "^2.0.4",
|
||||
"react": "^16.3.0-alpha.2",
|
||||
"react-dom": "^16.3.0-alpha.2",
|
||||
"redoc": "^2.0.0-alpha.15",
|
||||
"yargs": "^11.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"ci-publish": "ci-publish"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/handlebars": "^4.0.36",
|
||||
"ci-publish": "^1.3.1"
|
||||
}
|
||||
}
|
469
cli/redoc-static.html
Normal file
469
cli/redoc-static.html
Normal file
File diff suppressed because one or more lines are too long
23
cli/template.hbs
Normal file
23
cli/template.hbs
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>{{title}}</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{{{redocHead}}}
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{{redocHTML}}}
|
||||
</body>
|
||||
|
||||
</html>
|
2853
cli/yarn.lock
Normal file
2853
cli/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
30
custom.d.ts
vendored
30
custom.d.ts
vendored
|
@ -1,25 +1,25 @@
|
|||
declare module "*.css" {
|
||||
declare module '*.json' {
|
||||
const content: any;
|
||||
export = content;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module "*.json" {
|
||||
declare module '*.css' {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare var LIB_VERSION: any;
|
||||
declare var IS_PRODUCTION: any;
|
||||
declare var AOT: any;
|
||||
declare var __REDOC_VERSION__: string;
|
||||
declare var __REDOC_REVISION__: string;
|
||||
|
||||
interface ErrorStackTraceLimit {
|
||||
stackTraceLimit: number;
|
||||
declare type Dict<T> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
|
||||
interface Element {
|
||||
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
|
||||
}
|
||||
interface History {
|
||||
scrollRestoration: "auto"|"manual";
|
||||
}
|
||||
interface Window {
|
||||
HTMLElement: any
|
||||
}
|
||||
declare var safari: any;
|
||||
interface ErrorConstructor extends ErrorStackTraceLimit {}
|
||||
|
|
11
cypress.json
Normal file
11
cypress.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"ignoreTestFiles": "*.js.map",
|
||||
"integrationFolder": "e2e/integration",
|
||||
"pluginsFile": "e2e/plugins/index.js",
|
||||
"fixturesFolder": false,
|
||||
"supportFile": false,
|
||||
"fileServerFolder": ".",
|
||||
"videoRecording": true,
|
||||
"screenshotOnHeadlessFailure": true,
|
||||
"projectId": "z6eb6h"
|
||||
}
|
220
demo/ComboBox.tsx
Normal file
220
demo/ComboBox.tsx
Normal file
|
@ -0,0 +1,220 @@
|
|||
/**
|
||||
* Could not find ready-to-use component with required behaviour so
|
||||
* I quickly hacked my own. Will refactor into separate npm package later
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import styled, { StyledFunction } from 'styled-components';
|
||||
|
||||
function withProps<T, U extends HTMLElement = HTMLElement>(
|
||||
styledFunction: StyledFunction<React.HTMLProps<U>>,
|
||||
): StyledFunction<T & React.HTMLProps<U>> {
|
||||
return styledFunction;
|
||||
}
|
||||
|
||||
const DropDownItem = withProps<{ active: boolean }>(styled.li)`
|
||||
${props => ((props as any).active ? 'background-color: #eee' : '')};
|
||||
padding: 13px 16px;
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
cursor: pointer;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const DropDownList = styled.ul`
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
||||
0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
background: #fff;
|
||||
border-radius: 0 0 2px 2px;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 200;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
margin: 4px 0 0 0;
|
||||
padding: 5px 0;
|
||||
font-family: 'Lato';
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ComboBoxWrap = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
|
||||
font-size: 16px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
|
||||
|
||||
&:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
`;
|
||||
|
||||
const Button = styled.button`
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 2px 10px;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
border-left: 0;
|
||||
font-size: 16px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
width: 80px;
|
||||
`;
|
||||
|
||||
export interface ComboBoxProps {
|
||||
onChange?: (val: string) => void;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
placeholder?: string;
|
||||
value?: string;
|
||||
}
|
||||
export interface ComboBoxState {
|
||||
open: boolean;
|
||||
value: string;
|
||||
activeItemIdx: number;
|
||||
}
|
||||
|
||||
export default class ComboBox extends React.Component<ComboBoxProps, ComboBoxState> {
|
||||
state = {
|
||||
open: false,
|
||||
value: this.props.value || '',
|
||||
activeItemIdx: -1,
|
||||
};
|
||||
|
||||
open = () => {
|
||||
this.setState({
|
||||
open: true,
|
||||
});
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.setState({
|
||||
open: false,
|
||||
activeItemIdx: -1,
|
||||
});
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
this.updateValue(e.currentTarget.value);
|
||||
};
|
||||
|
||||
updateValue(value) {
|
||||
this.setState({
|
||||
value,
|
||||
activeItemIdx: -1,
|
||||
});
|
||||
}
|
||||
|
||||
handleSelect(value: string) {
|
||||
this.updateValue(value);
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
this.close();
|
||||
}
|
||||
|
||||
handleTryItClick = () => {
|
||||
this.handleSelect(this.state.value);
|
||||
};
|
||||
|
||||
handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.keyCode === 13) {
|
||||
this.handleSelect(e.currentTarget.value);
|
||||
} else if (e.keyCode === 40) {
|
||||
const activeItemIdx = Math.min(this.props.options.length - 1, ++this.state.activeItemIdx);
|
||||
this.setState({
|
||||
open: true,
|
||||
activeItemIdx,
|
||||
value: this.props.options[activeItemIdx].value,
|
||||
});
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 38) {
|
||||
const activeItemIdx = Math.max(0, --this.state.activeItemIdx);
|
||||
this.setState({
|
||||
activeItemIdx,
|
||||
value: this.props.options[activeItemIdx].value,
|
||||
});
|
||||
e.preventDefault();
|
||||
} else if (e.keyCode === 27) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
setTimeout(() => this.close(), 100);
|
||||
};
|
||||
|
||||
handleItemClick = (val, idx) => {
|
||||
this.handleSelect(val);
|
||||
this.setState({
|
||||
activeItemIdx: idx,
|
||||
});
|
||||
};
|
||||
|
||||
renderOption = (option: { value: string; label: string }, idx: number) => {
|
||||
return (
|
||||
<DropDownItem
|
||||
active={idx === this.state.activeItemIdx}
|
||||
key={option.value}
|
||||
// tslint:disable-next-line
|
||||
onMouseDown={() => {
|
||||
this.handleItemClick(option.value, idx);
|
||||
}}
|
||||
>
|
||||
<small>
|
||||
<strong>{option.label}</strong>
|
||||
</small>
|
||||
<br />
|
||||
{option.value}
|
||||
</DropDownItem>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { open, value } = this.state;
|
||||
const { options, placeholder } = this.props;
|
||||
return (
|
||||
<ComboBoxWrap>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
onChange={this.handleChange}
|
||||
value={value}
|
||||
onFocus={this.open}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyPress}
|
||||
/>
|
||||
<Button onClick={this.handleTryItClick}> TRY IT </Button>
|
||||
{open && <DropDownList>{options.map(this.renderOption)}</DropDownList>}
|
||||
</ComboBoxWrap>
|
||||
);
|
||||
}
|
||||
}
|
27362
demo/big-openapi.json
Normal file
27362
demo/big-openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,79 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ReDoc Demo: Multiple apis</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
#links_container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #0033a0;
|
||||
}
|
||||
|
||||
#links_container li {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Top navigation placeholder -->
|
||||
<nav>
|
||||
<ul id="links_container">
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<redoc scroll-y-offset="body > nav"></redoc>
|
||||
|
||||
<script src="https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js"> </script>
|
||||
<script>
|
||||
// list of APIS
|
||||
var apis = [
|
||||
{
|
||||
name: 'PetStore',
|
||||
url: 'https://rebilly.github.io/ReDoc/swagger.yaml'
|
||||
},
|
||||
{
|
||||
name: 'Instagram',
|
||||
url: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml'
|
||||
},
|
||||
{
|
||||
name: 'Google Calendar',
|
||||
url: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml'
|
||||
}
|
||||
];
|
||||
|
||||
// initially render first API
|
||||
Redoc.init(apis[0].url);
|
||||
|
||||
function onClick() {
|
||||
var url = this.getAttribute('data-link');
|
||||
Redoc.init(url);
|
||||
}
|
||||
|
||||
// dynamically building navigation items
|
||||
var $list = document.getElementById('links_container');
|
||||
apis.forEach(function(api) {
|
||||
var $listitem = document.createElement('li');
|
||||
$listitem.setAttribute('data-link', api.url);
|
||||
$listitem.innerText = api.name;
|
||||
$listitem.addEventListener('click', onClick);
|
||||
$list.appendChild($listitem);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,39 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ReDoc</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<header> <a href="/ReDoc"> ReDoc </a> </header>
|
||||
<template is="dom-bind" id="specs">
|
||||
<form id="schema-url-form" is="iron-form">
|
||||
<vaadin-combo-box-light id="spec-input" items="[[specs]]" allow-custom-value>
|
||||
<input placeholder="URL to a spec to try" id="schema-url-input" type="text" is="iron-input" value="https://rebilly.github.io/RebillyAPI/swagger.json">
|
||||
</vaadin-combo-box-light>
|
||||
<button type="submit"> Explore </button>
|
||||
</form>
|
||||
</template>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="150px" height="30px"></iframe>
|
||||
</nav>
|
||||
|
||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering untrusted-spec></redoc>
|
||||
|
||||
<script src="main.js"> </script>
|
||||
<script src="./dist/redoc.min.js"> </script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-81703547-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,35 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ReDoc</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/1.2.0/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<header> <a href="/ReDoc"> ReDoc </a> </header>
|
||||
<template is="dom-bind" id="specs">
|
||||
<form id="schema-url-form" is="iron-form">
|
||||
<vaadin-combo-box-light id="spec-input" items="[[specs]]" allow-custom-value>
|
||||
<input placeholder="URL to a spec to try" id="schema-url-input" type="text" is="iron-input" value="https://rebilly.github.io/RebillyAPI/swagger.json">
|
||||
</vaadin-combo-box-light>
|
||||
<button type="submit"> Explore </button>
|
||||
</form>
|
||||
</template>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="150px" height="30px"></iframe>
|
||||
</nav>
|
||||
|
||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.yaml' lazy-rendering untrusted-spec></redoc>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>ReDoc Interactive Demo</title>
|
||||
<meta name="description" content="ReDoc Interactive Demo. OpenAPI/Swagger-generated API Reference Documentation" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
<script>
|
||||
window.__REDOC_DEV__ = true;
|
||||
</script>
|
||||
<script src="main.js"> </script>
|
||||
<script src="/webpack-dev-server.js"></script>
|
||||
<script src="/polyfills.js"></script>
|
||||
<script src="/redoc.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container"> </div>
|
||||
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
if (window.location.host === 'rebilly.github.io') {
|
||||
ga('create', 'UA-81703547-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
182
demo/index.tsx
Normal file
182
demo/index.tsx
Normal file
|
@ -0,0 +1,182 @@
|
|||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
import { resolve as urlResolve } from 'url';
|
||||
import { RedocStandalone } from '../src';
|
||||
import ComboBox from './ComboBox';
|
||||
|
||||
const demos = [
|
||||
{ value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' },
|
||||
{
|
||||
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
||||
label: 'Google Calendar',
|
||||
},
|
||||
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.0.3/swagger.yaml', label: 'Slack' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' },
|
||||
{
|
||||
value: 'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml',
|
||||
label: 'GraphHopper',
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_SPEC = 'openapi.yaml';
|
||||
|
||||
class DemoApp extends React.Component<
|
||||
{},
|
||||
{ specUrl: string; dropdownOpen: boolean; cors: boolean }
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let parts = window.location.search.match(/url=([^&]+)/);
|
||||
let url = DEFAULT_SPEC;
|
||||
if (parts && parts.length > 1) {
|
||||
url = decodeURIComponent(parts[1]);
|
||||
}
|
||||
|
||||
parts = window.location.search.match(/[?&]nocors(&|#|$)/);
|
||||
let cors = true;
|
||||
if (parts && parts.length > 1) {
|
||||
cors = false;
|
||||
}
|
||||
|
||||
this.state = {
|
||||
specUrl: url,
|
||||
dropdownOpen: false,
|
||||
cors,
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (url: string) => {
|
||||
this.setState({
|
||||
specUrl: url,
|
||||
});
|
||||
window.history.pushState(
|
||||
undefined,
|
||||
'',
|
||||
updateQueryStringParameter(location.search, 'url', url),
|
||||
);
|
||||
};
|
||||
|
||||
toggleCors = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const cors = e.currentTarget.checked;
|
||||
this.setState({
|
||||
cors,
|
||||
});
|
||||
window.history.pushState(
|
||||
undefined,
|
||||
'',
|
||||
updateQueryStringParameter(location.search, 'nocors', cors ? undefined : ''),
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { specUrl, cors } = this.state;
|
||||
let proxiedUrl = specUrl;
|
||||
if (specUrl !== DEFAULT_SPEC) {
|
||||
proxiedUrl = cors
|
||||
? '\\\\cors.apis.guru/' + urlResolve(window.location.href, specUrl)
|
||||
: specUrl;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Heading>
|
||||
<a href=".">
|
||||
<Logo src="https://github.com/Rebilly/ReDoc/raw/master/docs/images/redoc-logo.png" />
|
||||
</a>
|
||||
<ControlsContainer>
|
||||
<ComboBox
|
||||
placeholder={'URL to a spec to try'}
|
||||
options={demos}
|
||||
onChange={this.handleChange}
|
||||
value={specUrl === DEFAULT_SPEC ? '' : specUrl}
|
||||
/>
|
||||
<CorsCheckbox title="Use CORS proxy">
|
||||
<input id="cors_checkbox" type="checkbox" onChange={this.toggleCors} checked={cors} />
|
||||
<label htmlFor="cors_checkbox">CORS</label>
|
||||
</CorsCheckbox>
|
||||
</ControlsContainer>
|
||||
<iframe
|
||||
src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
||||
frameBorder="0"
|
||||
scrolling="0"
|
||||
width="150px"
|
||||
height="30px"
|
||||
/>
|
||||
</Heading>
|
||||
<RedocStandalone specUrl={proxiedUrl} options={{ scrollYOffset: 'nav' }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* ====== Styled components ====== */
|
||||
|
||||
const ControlsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
margin: 0 15px;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const CorsCheckbox = styled.div`
|
||||
margin-left: 10px;
|
||||
|
||||
label {
|
||||
font-size: 13px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Heading = styled.nav`
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
border-bottom: 1px solid #cccccc;
|
||||
z-index: 10;
|
||||
padding: 5px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: 'Lato';
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
height: 40px;
|
||||
width: 124px;
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
`;
|
||||
|
||||
render(<DemoApp />, document.getElementById('container'));
|
||||
|
||||
/* ====== Helpers ====== */
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
const keyValue = value === '' ? key : key + '=' + value;
|
||||
const re = new RegExp('([?|&])' + key + '=?.*?(&|#|$)', 'i');
|
||||
if (uri.match(re)) {
|
||||
if (value !== undefined) {
|
||||
return uri.replace(re, '$1' + keyValue + '$2');
|
||||
} else {
|
||||
return uri.replace(re, (_, separator: string, rest: string) => {
|
||||
if (rest.startsWith('&')) {
|
||||
rest = rest.substring(1);
|
||||
}
|
||||
return separator === '&' ? rest : separator + rest;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (value === undefined) {
|
||||
return uri;
|
||||
}
|
||||
let hash = '';
|
||||
if (uri.indexOf('#') !== -1) {
|
||||
hash = uri.replace(/.*#/, '#');
|
||||
uri = uri.replace(/#.*/, '');
|
||||
}
|
||||
const separator = uri.indexOf('?') !== -1 ? '&' : '?';
|
||||
return uri + separator + keyValue + hash;
|
||||
}
|
||||
}
|
133
demo/main.css
133
demo/main.css
|
@ -1,133 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding-top: 50px;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
-moz-tap-highlight-color: rgba(0,0,0,0);
|
||||
-ms-tap-highlight-color: rgba(0,0,0,0);
|
||||
-o-tap-highlight-color: rgba(0,0,0,0);
|
||||
tap-highlight-color: rgba(0,0,0,0);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-smoothing: antialiased;
|
||||
-webkit-osx-font-smoothing: grayscale;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
|
||||
-ms-text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
|
||||
text-shadow: 1px 1px 1px rgba(0,0,0,0.004);
|
||||
text-rendering: optimizeSpeed!important;
|
||||
font-smooth: always;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
font-family: Monserrat, sans-serif;
|
||||
}
|
||||
|
||||
nav input, nav button {
|
||||
font-size: 16px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
outline: none;
|
||||
}
|
||||
nav header {
|
||||
font-family: Monserrat, sans-serif;
|
||||
float: left;
|
||||
margin-left: 20px;
|
||||
font-size: 25px;
|
||||
color: #00329F;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav input {
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
max-width: 500px;
|
||||
padding: 0 10px;
|
||||
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
nav input:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
|
||||
}
|
||||
|
||||
nav button {
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 2px 10px;
|
||||
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
nav button:hover {
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
nav button:active {
|
||||
background-color: #d4d4d4;
|
||||
border-color: #8c8c8c;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
nav iframe {
|
||||
margin: 10px 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
header a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
@media (min-width: 1000px) {
|
||||
nav header {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
nav input {
|
||||
width: 70%;
|
||||
}
|
||||
nav header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
nav iframe {
|
||||
display: none;
|
||||
}
|
||||
}
|
74
demo/main.js
74
demo/main.js
|
@ -1,74 +0,0 @@
|
|||
;(function() {
|
||||
'use strict';
|
||||
|
||||
var schemaUrlForm = document.getElementById('schema-url-form');
|
||||
var schemaUrlInput;
|
||||
|
||||
var url = window.location.search.match(/url=([^&]+)/);
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
url = window.__REDOC_DEV__ ? url : '\\\\cors.apis.guru/' + url;
|
||||
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
|
||||
}
|
||||
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
var re = new RegExp("([?|&])" + key + "=.*?(&|#|$)", "i");
|
||||
if (uri.match(re)) {
|
||||
return uri.replace(re, '$1' + key + "=" + value + '$2');
|
||||
} else {
|
||||
var hash = '';
|
||||
if( uri.indexOf('#') !== -1 ){
|
||||
hash = uri.replace(/.*#/, '#');
|
||||
uri = uri.replace(/#.*/, '');
|
||||
}
|
||||
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
|
||||
return uri + separator + key + "=" + value + hash;
|
||||
}
|
||||
}
|
||||
|
||||
var specs = document.querySelector('#specs');
|
||||
specs.addEventListener('dom-change', function() {
|
||||
schemaUrlForm = document.getElementById('schema-url-form');
|
||||
schemaUrlInput = document.getElementById('schema-url-input');
|
||||
|
||||
schemaUrlForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
|
||||
return false;
|
||||
})
|
||||
|
||||
schemaUrlInput.addEventListener('mousedown', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
schemaUrlInput.value = url;
|
||||
specs.specs = [
|
||||
'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/data2crm.com/1/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml'
|
||||
];
|
||||
|
||||
var $specInput = document.getElementById('spec-input');
|
||||
|
||||
$specInput.addEventListener('value-changed', function(e) {
|
||||
schemaUrlInput.value = e.detail.value;
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||
});
|
||||
|
||||
function selectItem() {
|
||||
let value = this.innerText.trim();
|
||||
schemaUrlInput.value = value;
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||
}
|
||||
|
||||
// for some reason events are not triggered so have to dirty fix this
|
||||
$specInput.addEventListener('click', function(event) {
|
||||
let $elems = document.querySelectorAll('.item.vaadin-combo-box-overlay');
|
||||
$elems.forEach(function($el) {
|
||||
$el.addEventListener('mousedown', selectItem);
|
||||
$el.addEventListener('mousedown', selectItem);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
949
demo/openapi.yaml
Normal file
949
demo/openapi.yaml
Normal file
|
@ -0,0 +1,949 @@
|
|||
openapi: 3.0.0
|
||||
servers:
|
||||
- url: //petstore.swagger.io/v2
|
||||
description: Default server
|
||||
- url: //petstore.swagger.io/sandbox
|
||||
description: Sandbox server
|
||||
info:
|
||||
description: >
|
||||
This is a sample server Petstore server.
|
||||
|
||||
You can find out more about Swagger at
|
||||
|
||||
[http://swagger.io](http://swagger.io) or on [irc.freenode.net,
|
||||
#swagger](http://swagger.io/irc/).
|
||||
|
||||
For this sample, you can use the api key `special-key` to test the
|
||||
authorization filters.
|
||||
|
||||
# Introduction
|
||||
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
|
||||
[Petstore sample](http://petstore.swagger.io/) provided by
|
||||
[swagger.io](http://swagger.io) team.
|
||||
|
||||
It was **extended** to illustrate features of
|
||||
[generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In
|
||||
addition to standard
|
||||
|
||||
OpenAPI syntax we use a few [vendor
|
||||
extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
|
||||
# OpenAPI Specification
|
||||
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
|
||||
[Petstore sample](http://petstore.swagger.io/) provided by
|
||||
[swagger.io](http://swagger.io) team.
|
||||
|
||||
It was **extended** to illustrate features of
|
||||
[generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In
|
||||
addition to standard
|
||||
|
||||
OpenAPI syntax we use a few [vendor
|
||||
extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
|
||||
# Cross-Origin Resource Sharing
|
||||
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in
|
||||
compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||
|
||||
And that allows cross-domain communication from the browser.
|
||||
|
||||
All responses have a wildcard same-origin which makes them completely public
|
||||
and accessible to everyone, including any code on any site.
|
||||
|
||||
# Authentication
|
||||
|
||||
Petstore offers two forms of authentication:
|
||||
- API Key
|
||||
- OAuth2
|
||||
|
||||
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||
and standard method from web, mobile and desktop applications.
|
||||
|
||||
<!-- ReDoc-Inject: <security-definitions> -->
|
||||
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
termsOfService: 'http://swagger.io/terms/'
|
||||
contact:
|
||||
name: API Support
|
||||
email: apiteam@swagger.io
|
||||
url: https://github.com/Rebilly/ReDoc
|
||||
x-logo:
|
||||
url: 'https://rebilly.github.io/ReDoc/petstore-logo.png'
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
||||
externalDocs:
|
||||
description: Find out how to create Github repo for your OpenAPI spec.
|
||||
url: 'https://github.com/Rebilly/generator-openapi-repo'
|
||||
tags:
|
||||
- name: pet
|
||||
description: Everything about your Pets
|
||||
- name: store
|
||||
description: Access to Petstore orders
|
||||
- name: user
|
||||
description: Operations about user
|
||||
x-tagGroups:
|
||||
- name: General
|
||||
tags:
|
||||
- pet
|
||||
- store
|
||||
- name: User Management
|
||||
tags:
|
||||
- user
|
||||
paths:
|
||||
/pet:
|
||||
parameters:
|
||||
- name: cookieParam
|
||||
in: cookie
|
||||
description: Some cookie
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Add a new pet to the store
|
||||
description: Add new pet to the store inventory.
|
||||
operationId: addPet
|
||||
responses:
|
||||
'405':
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
x-code-samples:
|
||||
- lang: 'C#'
|
||||
source: |
|
||||
PetStore.v1.Pet pet = new PetStore.v1.Pet();
|
||||
pet.setApiKey("your api key");
|
||||
pet.petType = PetStore.v1.Pet.TYPE_DOG;
|
||||
pet.name = "Rex";
|
||||
// set other fields
|
||||
PetStoreResponse response = pet.create();
|
||||
if (response.statusCode == HttpStatusCode.Created)
|
||||
{
|
||||
// Successfully created
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something wrong -- check response for errors
|
||||
Console.WriteLine(response.getRawResponse());
|
||||
}
|
||||
- lang: PHP
|
||||
source: |
|
||||
$form = new \PetStore\Entities\Pet();
|
||||
$form->setPetType("Dog");
|
||||
$form->setName("Rex");
|
||||
// set other fields
|
||||
try {
|
||||
$pet = $client->pets()->create($form);
|
||||
} catch (UnprocessableEntityException $e) {
|
||||
var_dump($e->getErrors());
|
||||
}
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Pet'
|
||||
put:
|
||||
tags:
|
||||
- pet
|
||||
summary: Update an existing pet
|
||||
description: ''
|
||||
operationId: updatePet
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Pet not found
|
||||
'405':
|
||||
description: Validation exception
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
x-code-samples:
|
||||
- lang: PHP
|
||||
source: |
|
||||
$form = new \PetStore\Entities\Pet();
|
||||
$form->setPetId(1);
|
||||
$form->setPetType("Dog");
|
||||
$form->setName("Rex");
|
||||
// set other fields
|
||||
try {
|
||||
$pet = $client->pets()->update($form);
|
||||
} catch (UnprocessableEntityException $e) {
|
||||
var_dump($e->getErrors());
|
||||
}
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/Pet'
|
||||
'/pet/{petId}':
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Find pet by ID
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to return
|
||||
required: true
|
||||
deprecated: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Pet not found
|
||||
security:
|
||||
- api_key: []
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: Updates a pet in the store with form data
|
||||
description: ''
|
||||
operationId: updatePetWithForm
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet that needs to be updated
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'405':
|
||||
description: Invalid input
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: Updated name of the pet
|
||||
type: string
|
||||
status:
|
||||
description: Updated status of the pet
|
||||
type: string
|
||||
delete:
|
||||
tags:
|
||||
- pet
|
||||
summary: Deletes a pet
|
||||
description: ''
|
||||
operationId: deletePet
|
||||
parameters:
|
||||
- name: api_key
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
example: "Bearer <TOKEN>"
|
||||
- name: petId
|
||||
in: path
|
||||
description: Pet id to delete
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid pet value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
'/pet/{petId}/uploadImage':
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
summary: uploads an image
|
||||
description: ''
|
||||
operationId: uploadFile
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to update
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ApiResponse'
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
requestBody:
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
/pet/findByStatus:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by status
|
||||
description: Multiple status values can be provided with comma seperated strings
|
||||
operationId: findPetsByStatus
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
description: Status values that need to be considered for filter
|
||||
required: true
|
||||
style: form
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
default: available
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid status value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
/pet/findByTags:
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: Finds Pets by tags
|
||||
description: >-
|
||||
Muliple tags can be provided with comma seperated strings. Use tag1,
|
||||
tag2, tag3 for testing.
|
||||
operationId: findPetsByTags
|
||||
deprecated: true
|
||||
parameters:
|
||||
- name: tags
|
||||
in: query
|
||||
description: Tags to filter by
|
||||
required: true
|
||||
style: form
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Pet'
|
||||
'400':
|
||||
description: Invalid tag value
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
/store/inventory:
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Returns pet inventories by status
|
||||
description: Returns a map of status codes to quantities
|
||||
operationId: getInventory
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int32
|
||||
security:
|
||||
- api_key: []
|
||||
/store/order:
|
||||
post:
|
||||
tags:
|
||||
- store
|
||||
summary: Place an order for a pet
|
||||
description: ''
|
||||
operationId: placeOrder
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
'400':
|
||||
description: Invalid Order
|
||||
content:
|
||||
application/json:
|
||||
example:
|
||||
status: 400
|
||||
message: "Invalid Order"
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
description: order placed for purchasing the pet
|
||||
required: true
|
||||
'/store/order/{orderId}':
|
||||
get:
|
||||
tags:
|
||||
- store
|
||||
summary: Find purchase order by ID
|
||||
description: >-
|
||||
For valid response try integer IDs with value <= 5 or > 10. Other values
|
||||
will generated exceptions
|
||||
operationId: getOrderById
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of pet that needs to be fetched
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 1
|
||||
maximum: 5
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Order'
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
delete:
|
||||
tags:
|
||||
- store
|
||||
summary: Delete purchase order by ID
|
||||
description: >-
|
||||
For valid response try integer IDs with value < 1000. Anything above
|
||||
1000 or nonintegers will generate API errors
|
||||
operationId: deleteOrder
|
||||
parameters:
|
||||
- name: orderId
|
||||
in: path
|
||||
description: ID of the order that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
minimum: 1
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Create user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: createUser
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Created user object
|
||||
required: true
|
||||
'/user/{username}':
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Get user by user name
|
||||
description: ''
|
||||
operationId: getUserByName
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: 'The name that needs to be fetched. Use user1 for testing. '
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
application/xml:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'400':
|
||||
description: Invalid username supplied
|
||||
'404':
|
||||
description: User not found
|
||||
put:
|
||||
tags:
|
||||
- user
|
||||
summary: Updated user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: updateUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: name that need to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid user supplied
|
||||
'404':
|
||||
description: User not found
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: Updated user object
|
||||
required: true
|
||||
delete:
|
||||
tags:
|
||||
- user
|
||||
summary: Delete user
|
||||
description: This can only be done by the logged in user.
|
||||
operationId: deleteUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
description: The name that needs to be deleted
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'400':
|
||||
description: Invalid username supplied
|
||||
'404':
|
||||
description: User not found
|
||||
/user/createWithArray:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
description: ''
|
||||
operationId: createUsersWithArrayInput
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/UserArray'
|
||||
/user/createWithList:
|
||||
post:
|
||||
tags:
|
||||
- user
|
||||
summary: Creates list of users with given input array
|
||||
description: ''
|
||||
operationId: createUsersWithListInput
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
requestBody:
|
||||
$ref: '#/components/requestBodies/UserArray'
|
||||
/user/login:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs user into the system
|
||||
description: ''
|
||||
operationId: loginUser
|
||||
parameters:
|
||||
- name: username
|
||||
in: query
|
||||
description: The user name for login
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: password
|
||||
in: query
|
||||
description: The password for login in clear text
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
description: calls per hour allowed by the user
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
X-Expires-After:
|
||||
description: date in UTC when toekn expires
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
examples:
|
||||
response:
|
||||
value: OK
|
||||
application/xml:
|
||||
schema:
|
||||
type: string
|
||||
examples:
|
||||
response:
|
||||
value: <Message> OK </Message>
|
||||
text/plain:
|
||||
examples:
|
||||
response:
|
||||
value: OK
|
||||
'400':
|
||||
description: Invalid username/password supplied
|
||||
/user/logout:
|
||||
get:
|
||||
tags:
|
||||
- user
|
||||
summary: Logs out current logged in user session
|
||||
description: ''
|
||||
operationId: logoutUser
|
||||
responses:
|
||||
default:
|
||||
description: successful operation
|
||||
components:
|
||||
schemas:
|
||||
ApiResponse:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
format: int32
|
||||
type:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
Cat:
|
||||
description: A representation of a cat
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
huntingSkill:
|
||||
type: string
|
||||
description: The measured skill for hunting
|
||||
default: lazy
|
||||
enum:
|
||||
- clueless
|
||||
- lazy
|
||||
- adventurous
|
||||
- aggressive
|
||||
required:
|
||||
- huntingSkill
|
||||
Category:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Category ID
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Id'
|
||||
name:
|
||||
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:
|
||||
description: A representation of a dog
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
packSize:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The size of the pack the dog is from
|
||||
default: 1
|
||||
minimum: 1
|
||||
required:
|
||||
- packSize
|
||||
HoneyBee:
|
||||
description: A representation of a honey bee
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
honeyPerDay:
|
||||
type: number
|
||||
description: Average amount of honey produced per day in ounces
|
||||
example: 3.14
|
||||
required:
|
||||
- honeyPerDay
|
||||
Id:
|
||||
type: integer
|
||||
format: int64
|
||||
readOnly: true
|
||||
Order:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Order ID
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Id'
|
||||
petId:
|
||||
description: Pet ID
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Id'
|
||||
quantity:
|
||||
type: integer
|
||||
format: int32
|
||||
minimum: 1
|
||||
default: 1
|
||||
shipDate:
|
||||
description: Estimated ship date
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
type: string
|
||||
description: Order Status
|
||||
enum:
|
||||
- placed
|
||||
- approved
|
||||
- delivered
|
||||
complete:
|
||||
description: Indicates whenever order was completed or not
|
||||
type: boolean
|
||||
default: false
|
||||
xml:
|
||||
name: Order
|
||||
Pet:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- photoUrls
|
||||
discriminator:
|
||||
propertyName: petType
|
||||
mapping:
|
||||
cat: '#/components/schemas/Cat'
|
||||
dog: '#/components/schemas/Dog'
|
||||
bee: '#/components/schemas/HoneyBee'
|
||||
properties:
|
||||
id:
|
||||
description: Pet ID
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Id'
|
||||
category:
|
||||
description: Categories this pet belongs to
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Category'
|
||||
name:
|
||||
description: The name given to a pet
|
||||
type: string
|
||||
example: Guru
|
||||
photoUrls:
|
||||
description: The list of URL to a cute photos featuring pet
|
||||
type: array
|
||||
xml:
|
||||
name: photoUrl
|
||||
wrapped: true
|
||||
items:
|
||||
type: string
|
||||
format: url
|
||||
friend:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
tags:
|
||||
description: Tags attached to the pet
|
||||
type: array
|
||||
xml:
|
||||
name: tag
|
||||
wrapped: true
|
||||
items:
|
||||
$ref: '#/components/schemas/Tag'
|
||||
status:
|
||||
type: string
|
||||
description: Pet status in the store
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
- sold
|
||||
petType:
|
||||
description: Type of a pet
|
||||
type: string
|
||||
xml:
|
||||
name: Pet
|
||||
Tag:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Tag ID
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Id'
|
||||
name:
|
||||
description: Tag name
|
||||
type: string
|
||||
minLength: 1
|
||||
xml:
|
||||
name: Tag
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
$ref: '#/components/schemas/Id'
|
||||
pet:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
- $ref: '#/components/schemas/Tag'
|
||||
username:
|
||||
description: User supplied username
|
||||
type: string
|
||||
minLength: 4
|
||||
example: John78
|
||||
firstName:
|
||||
description: User first name
|
||||
type: string
|
||||
minLength: 1
|
||||
example: John
|
||||
lastName:
|
||||
description: User last name
|
||||
type: string
|
||||
minLength: 1
|
||||
example: Smith
|
||||
email:
|
||||
description: User email address
|
||||
type: string
|
||||
format: email
|
||||
example: john.smith@example.com
|
||||
password:
|
||||
type: string
|
||||
description: >-
|
||||
User password, MUST contain a mix of upper and lower case letters,
|
||||
as well as digits
|
||||
format: password
|
||||
minLength: 8
|
||||
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
|
||||
example: drowssaP123
|
||||
phone:
|
||||
description: User phone number in international format
|
||||
type: string
|
||||
pattern: '^\+(?:[0-9]-?){6,14}[0-9]$'
|
||||
example: +1-202-555-0192
|
||||
nullable: true
|
||||
userStatus:
|
||||
description: User status
|
||||
type: integer
|
||||
format: int32
|
||||
xml:
|
||||
name: User
|
||||
requestBodies:
|
||||
Pet:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- description: My Pet
|
||||
title: Pettie
|
||||
- $ref: '#/components/schemas/Pet'
|
||||
application/xml:
|
||||
schema:
|
||||
type: 'object'
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: hooray
|
||||
description: Pet object that needs to be added to the store
|
||||
required: true
|
||||
UserArray:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
description: List of user object
|
||||
required: true
|
||||
securitySchemes:
|
||||
petstore_auth:
|
||||
description: |
|
||||
Get access to data while protecting your account credentials.
|
||||
OAuth2 is also a safer and more secure way to give you access.
|
||||
type: oauth2
|
||||
flows:
|
||||
implicit:
|
||||
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
|
||||
scopes:
|
||||
'write:pets': modify pets in your account
|
||||
'read:pets': read your pets
|
||||
api_key:
|
||||
description: >
|
||||
For this sample, you can use the api key `special-key` to test the
|
||||
authorization filters.
|
||||
type: apiKey
|
||||
name: api_key
|
||||
in: header
|
50
demo/playground/hmr-playground.tsx
Normal file
50
demo/playground/hmr-playground.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
// import DevTools from 'mobx-react-devtools';
|
||||
|
||||
import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc';
|
||||
import { AppStore } from '../../src/services/AppStore';
|
||||
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
|
||||
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
|
||||
|
||||
const renderRoot = (props: RedocProps) =>
|
||||
render(
|
||||
<AppContainer>
|
||||
<Redoc {...props} />
|
||||
</AppContainer>,
|
||||
document.getElementById('example'),
|
||||
);
|
||||
|
||||
const big = window.location.search.indexOf('big') > -1;
|
||||
const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ?
|
||||
|
||||
const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';
|
||||
|
||||
let store;
|
||||
const options: RedocRawOptions = { nativeScrollbars: false };
|
||||
|
||||
async function init() {
|
||||
const spec = await loadAndBundleSpec(specUrl);
|
||||
store = new AppStore(spec, specUrl, options);
|
||||
renderRoot({ store });
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
if (module.hot) {
|
||||
const reload = (reloadStore = false) => async () => {
|
||||
if (reloadStore) {
|
||||
// create a new Store
|
||||
store.dispose();
|
||||
|
||||
const state = await store.toJS();
|
||||
store = AppStore.fromJS(state);
|
||||
}
|
||||
|
||||
renderRoot({ store });
|
||||
};
|
||||
|
||||
module.hot.accept(['../../src/components/Redoc/Redoc'], reload());
|
||||
module.hot.accept(['../../src/services/AppStore'], reload(true));
|
||||
}
|
25
demo/playground/index.html
Normal file
25
demo/playground/index.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ReDoc</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<redoc id="example"></redoc>
|
||||
</body>
|
||||
|
||||
</html>
|
Binary file not shown.
Before Width: | Height: | Size: 198 KiB |
67
demo/ssr/index.ts
Normal file
67
demo/ssr/index.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { renderToString } from 'react-dom/server';
|
||||
import * as React from 'react';
|
||||
import { ServerStyleSheet } from 'styled-components';
|
||||
// @ts-ignore
|
||||
import { Redoc, createStore } from '../../';
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const yaml = require('yaml-js');
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
const fs = require('fs');
|
||||
|
||||
const PORT = 9999;
|
||||
|
||||
const server = http.createServer(async (request, response) => {
|
||||
console.time('request ' + request.url);
|
||||
if (request.url === '/redoc.standalone.js') {
|
||||
fs.createReadStream('bundles/redoc.standalone.js', 'utf8').pipe(response);
|
||||
} else if (request.url === '/') {
|
||||
const spec = yaml.load(readFileSync(resolve(__dirname, '../openapi.yaml')));
|
||||
let store = await createStore(spec, 'path/to/spec.yaml');
|
||||
|
||||
const sheet = new ServerStyleSheet();
|
||||
|
||||
const html = renderToString(sheet.collectStyles(React.createElement(Redoc, { store })));
|
||||
const css = sheet.getStyleTags();
|
||||
|
||||
const res = `<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>ReDoc</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script src="redoc.standalone.js"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
${css}
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const state = ${JSON.stringify(await store.toJS())};
|
||||
Redoc.hydrate(state, document.getElementById('redoc'));
|
||||
});
|
||||
</script>
|
||||
<div id="redoc">${html}</div>
|
||||
</body>
|
||||
</html>`;
|
||||
response.writeHead(200, { 'Content-Length': res.length });
|
||||
response.write(res);
|
||||
response.end();
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.write('Not found');
|
||||
response.end();
|
||||
}
|
||||
|
||||
console.timeEnd('request ' + request.url);
|
||||
});
|
||||
|
||||
server.listen(PORT, () => console.log(`Server started: http://127.0.0.1:${PORT}`));
|
118
demo/webpack.config.ts
Normal file
118
demo/webpack.config.ts
Normal file
|
@ -0,0 +1,118 @@
|
|||
import * as webpack from 'webpack';
|
||||
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import * as ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
const REVISION = JSON.stringify(
|
||||
require('child_process')
|
||||
.execSync('git rev-parse --short HEAD')
|
||||
.toString()
|
||||
.trim(),
|
||||
);
|
||||
|
||||
function root(filename) {
|
||||
return resolve(__dirname + '/' + filename);
|
||||
}
|
||||
|
||||
const tsLoader = env => ({
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
module: env.bench ? 'esnext' : 'es2015',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const babelHotLoader = {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-typescript',
|
||||
'@babel/plugin-syntax-decorators',
|
||||
'@babel/plugin-syntax-jsx',
|
||||
'react-hot-loader/babel',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
||||
entry: [
|
||||
root('../src/polyfills.ts'),
|
||||
root(
|
||||
env.playground
|
||||
? 'playground/hmr-playground.tsx'
|
||||
: env.bench ? '../benchmark/index.tsx' : 'index.tsx',
|
||||
),
|
||||
],
|
||||
output: {
|
||||
filename: 'redoc-demo.bundle.js',
|
||||
path: root('dist'),
|
||||
globalObject: 'this',
|
||||
},
|
||||
|
||||
devServer: {
|
||||
contentBase: __dirname,
|
||||
watchContentBase: true,
|
||||
port: 9090,
|
||||
disableHostCheck: true,
|
||||
stats: 'minimal',
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||
},
|
||||
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
|
||||
performance: false,
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: mode === 'production' ? [tsLoader(env)] : [tsLoader(env), babelHotLoader],
|
||||
exclude: ['node_modules'],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
minimize: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /node_modules\/(swagger2openapi|reftools)\/.*\.js$/,
|
||||
use: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
instance: 'ts2js-transpiler-only',
|
||||
compilerOptions: {
|
||||
allowJs: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__REDOC_VERSION__: VERSION,
|
||||
__REDOC_REVISION__: REVISION,
|
||||
}),
|
||||
new webpack.NamedModulesPlugin(),
|
||||
new webpack.optimize.ModuleConcatenationPlugin(),
|
||||
new HtmlWebpackPlugin({
|
||||
template: env.playground ? 'demo/playground/index.html' : 'demo/index.html',
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
],
|
||||
});
|
47
e2e/integration/menu.e2e.ts
Normal file
47
e2e/integration/menu.e2e.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
describe('Menu', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('e2e/standalone.html');
|
||||
});
|
||||
|
||||
it('should have valid items count', function() {
|
||||
cy
|
||||
.get('.menu-content')
|
||||
.find('li')
|
||||
.should('have.length', 6 + (2 + 8 + 4) + (1 + 8));
|
||||
});
|
||||
|
||||
it('should sync active menu items while scroll', function() {
|
||||
cy
|
||||
.contains('h1', 'Introduction')
|
||||
.scrollIntoView()
|
||||
.get('[role=menuitem].active:not(.-depth0)')
|
||||
.should('have.text', 'Introduction');
|
||||
|
||||
cy
|
||||
.contains('h2', 'Add a new pet to the store')
|
||||
.scrollIntoView()
|
||||
.get('[role=menuitem].active:not(.-depth0)')
|
||||
.should('have.length', 2)
|
||||
.last()
|
||||
.should('have.text', 'Add a new pet to the store')
|
||||
.should('be.visible');
|
||||
});
|
||||
|
||||
it('should update URL hash when clicking on menu items', function() {
|
||||
cy.contains('[role=menuitem].-depth1', 'pet').click({ force: true });
|
||||
cy.location('hash').should('equal', '#tag/pet');
|
||||
|
||||
cy.contains('[role=menuitem]', 'Find pet by ID').click({ force: true });
|
||||
cy.location('hash').should('equal', '#operation/getPetById');
|
||||
});
|
||||
|
||||
it('should deactivate tag when other is activated', function() {
|
||||
const petItem = () => cy.contains('[role=menuitem].-depth1', 'pet');
|
||||
|
||||
petItem()
|
||||
.click({ force: true })
|
||||
.should('have.class', 'active');
|
||||
cy.contains('[role=menuitem].-depth1', 'store').click({ force: true });
|
||||
petItem().should('not.have.class', 'active');
|
||||
});
|
||||
});
|
55
e2e/integration/search.e2e.ts
Normal file
55
e2e/integration/search.e2e.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
describe('Search', () => {
|
||||
const getSearchInput = () => cy.get('[role="search"] input');
|
||||
const getSearchResults = () => cy.get('[data-role="search:results"]');
|
||||
const getResult = i => cy.get('[role=search] [role=menuitem]').eq(i);
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('e2e/standalone.html');
|
||||
});
|
||||
|
||||
it('should correctly show and hide search results box', function() {
|
||||
getSearchResults().should('not.exist');
|
||||
|
||||
// should not open for less than 3 symbols
|
||||
getSearchInput().type('in', { force: true });
|
||||
getSearchResults().should('not.exist');
|
||||
|
||||
getSearchInput().type('t', { force: true });
|
||||
cy
|
||||
.get('[role=search] [role=menuitem]')
|
||||
.should('have.length', 3)
|
||||
.first()
|
||||
.should('contain', 'Introduction');
|
||||
|
||||
getSearchInput().type('{esc}', { force: true });
|
||||
getSearchResults().should('not.exist');
|
||||
});
|
||||
|
||||
it('should support arrow navigation', function() {
|
||||
getSearchInput().type('int', { force: true });
|
||||
|
||||
getSearchInput().type('{downarrow}', { force: true });
|
||||
getResult(0).should('have.class', 'active');
|
||||
|
||||
getSearchInput().type('{downarrow}', { force: true });
|
||||
getResult(1).should('have.class', 'active');
|
||||
getResult(0).should('not.have.class', 'active');
|
||||
|
||||
getSearchInput().type('{uparrow}', { force: true });
|
||||
getResult(1).should('not.have.class', 'active');
|
||||
getResult(0).should('have.class', 'active');
|
||||
|
||||
getSearchInput().type('{uparrow}', { force: true });
|
||||
getResult(0).should('have.class', 'active');
|
||||
|
||||
getSearchInput().type('{enter}', { force: true });
|
||||
|
||||
cy.contains('[role=navigation] [role=menuitem]', 'Introduction').should('have.class', 'active');
|
||||
});
|
||||
|
||||
it('should mark search results', function() {
|
||||
cy.get('[data-markjs]').should('not.exist');
|
||||
getSearchInput().type('int', { force: true });
|
||||
cy.get('[data-markjs]').should('exist');
|
||||
});
|
||||
});
|
20
e2e/integration/standalone.e2e.ts
Normal file
20
e2e/integration/standalone.e2e.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
describe('Standalone bundle test', function() {
|
||||
function baseCheck(name: string, url: string) {
|
||||
describe(name, () => {
|
||||
before(() => {
|
||||
cy.visit(url);
|
||||
});
|
||||
|
||||
it('Render and check no errors', function() {
|
||||
cy.get('.api-info').should('exist');
|
||||
});
|
||||
|
||||
it('Render and click all the menu items', function() {
|
||||
cy.get('.menu-content li').click({ multiple: true, force: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
baseCheck('OAS3 mode', 'e2e/standalone.html');
|
||||
baseCheck('OAS2 compatibility mode', 'e2e/standalone-compatibility.html');
|
||||
});
|
30
e2e/plugins/cy-ts-preprocessor.js
Normal file
30
e2e/plugins/cy-ts-preprocessor.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
const wp = require('@cypress/webpack-preprocessor');
|
||||
|
||||
const webpackOptions = {
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
performance: false,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: [/node_modules/],
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: 'e2e/tsconfig.json',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const options = {
|
||||
webpackOptions,
|
||||
};
|
||||
|
||||
module.exports = wp(options);
|
5
e2e/plugins/index.js
Normal file
5
e2e/plugins/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const cypressTypeScriptPreprocessor = require('./cy-ts-preprocessor');
|
||||
|
||||
module.exports = on => {
|
||||
on('file:preprocessor', cypressTypeScriptPreprocessor);
|
||||
};
|
8
e2e/standalone-compatibility.html
Normal file
8
e2e/standalone-compatibility.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
|
||||
<body>
|
||||
<redoc spec-url="../demo/swagger.yaml"></redoc>
|
||||
<script src="../bundles/redoc.standalone.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
8
e2e/standalone.html
Normal file
8
e2e/standalone.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<html>
|
||||
|
||||
<body>
|
||||
<redoc spec-url="../demo/openapi.yaml" native-scrollbars></redoc>
|
||||
<script src="../bundles/redoc.standalone.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
27
e2e/tsconfig.json
Normal file
27
e2e/tsconfig.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2015",
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"strictNullChecks": true,
|
||||
"sourceMap": true,
|
||||
"pretty": true,
|
||||
"lib": [
|
||||
"es2015",
|
||||
"es2016",
|
||||
"es2017",
|
||||
"dom"
|
||||
],
|
||||
"jsx": "react",
|
||||
"types": ["cypress"]
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": [
|
||||
"integration/*.ts",
|
||||
"../node_modules/cypress"
|
||||
]
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { RedocModule } from './redoc.module';
|
||||
import { Redoc } from './components/index';
|
||||
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, RedocModule ],
|
||||
bootstrap: [ Redoc ],
|
||||
exports: [ Redoc ]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { NgModuleRef } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
export function bootstrapRedoc(): Promise<NgModuleRef<AppModule>> {
|
||||
return platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import { NgModuleRef } from '@angular/core';
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
import { AppModule } from './app.module';
|
||||
// @ts-ignore
|
||||
import { AppModuleNgFactory } from '../compiled/lib/app.module.ngfactory';
|
||||
|
||||
export function bootstrapRedoc():Promise<NgModuleRef<AppModule>> {
|
||||
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
<div class="api-info-wrapper">
|
||||
<h1>{{info.title}} <span class="api-info-version">({{info.version}})</span></h1>
|
||||
<p class="download-openapi" *ngIf="specUrl && !hideDownloadButton">
|
||||
Download OpenAPI specification:
|
||||
<a class="openapi-button" [attr.download]="downloadFilename" [attr.href]="specUrl"> Download </a>
|
||||
</p>
|
||||
<p>
|
||||
<!-- TODO: create separate components for contact and license ? -->
|
||||
<span *ngIf="info?.contact?.url || info?.contact?.email"> Contact:
|
||||
<a *ngIf="info.contact.url" href="{{info.contact.url}}">
|
||||
{{info.contact.name || info.contact.url}}</a>
|
||||
<a *ngIf="info.contact.email" href="mailto:{{info.contact.email}}">
|
||||
{{info.contact.email}}</a>
|
||||
</span>
|
||||
<span *ngIf="info.license"> License:
|
||||
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
|
||||
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
||||
</span>
|
||||
<redoc-externalDocs [docs]="componentSchema.externalDocs"></redoc-externalDocs>
|
||||
</p>
|
||||
<span class="redoc-markdown-block">
|
||||
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
|
||||
</span>
|
||||
</div>
|
|
@ -1,33 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host > .api-info-wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: $section-spacing;
|
||||
width: 60%;
|
||||
|
||||
|
||||
@media (max-width: $right-panel-squash-breakpoint) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
padding-top: $section-spacing + 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.openapi-button {
|
||||
border: 1px solid $primary-color;
|
||||
color: $primary-color;
|
||||
font-weight: normal;
|
||||
margin-left: 0.5em;
|
||||
padding: 3px 8px 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:host /deep/ [section] {
|
||||
padding-top: 2 * $section-spacing;
|
||||
}
|
||||
|
||||
:host /deep/ h2[section] {
|
||||
padding-top: $section-spacing;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
import { OptionsService } from '../../services/index';
|
||||
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
describe('ApiInfo Component', () => {
|
||||
let component;
|
||||
let fixture;
|
||||
let opts;
|
||||
let specMgr;
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
|
||||
opts = _opts;
|
||||
opts.options = {
|
||||
scrollYOffset: () => 0,
|
||||
$scrollParent: window
|
||||
};
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load('/tests/schemas/api-info-test.json').then(done, done.fail);
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
fixture = TestBed.createComponent(TestAppComponent);
|
||||
component = getChildDebugElement(fixture.debugElement, 'api-info').componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
|
||||
it('should init component data', () => {
|
||||
expect(component).not.toBeNull();
|
||||
expect(component.info).not.toBeNull();
|
||||
component.info.title.should.be.equal('Swagger Petstore');
|
||||
});
|
||||
|
||||
it('should render api name and version', () => {
|
||||
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-info').nativeElement;
|
||||
let headerElement = nativeElement.querySelector('h1');
|
||||
expect(headerElement.innerText).toContain('Swagger Petstore (v1.0.0)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains an ApiInfo. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<api-info></api-info>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { SpecManager, BaseComponent } from '../base';
|
||||
import { OptionsService, Marker } from '../../services/index';
|
||||
|
||||
@Component({
|
||||
selector: 'api-info',
|
||||
styleUrls: ['./api-info.css'],
|
||||
templateUrl: './api-info.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ApiInfo extends BaseComponent implements OnInit {
|
||||
info: any = {};
|
||||
specUrl: String | SafeResourceUrl;
|
||||
downloadFilename = '';
|
||||
|
||||
hideDownloadButton = this.optionsService.options.hideDownloadButton;
|
||||
|
||||
constructor(specMgr: SpecManager,
|
||||
private optionsService: OptionsService,
|
||||
elRef: ElementRef,
|
||||
marker: Marker,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
super(specMgr);
|
||||
marker.addElement(elRef.nativeElement);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.info = this.componentSchema.info;
|
||||
this.specUrl = this.specMgr.specUrl;
|
||||
if (!this.specUrl && window.Blob && window.URL) {
|
||||
const blob = new Blob([JSON.stringify(this.specMgr.rawSpec, null, 2)], {type : 'application/json'});
|
||||
this.specUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
|
||||
this.downloadFilename = 'swagger.json';
|
||||
}
|
||||
|
||||
if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) {
|
||||
this.info.version = 'v' + this.info.version;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<a *ngIf="logo.url" href="{{logo.url}}">
|
||||
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
|
||||
</a>
|
||||
<img *ngIf="logo.imgUrl && !logo.url" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
|
|
@ -1,19 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 150px;
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
//padding: 0 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
|
||||
|
||||
describe('Redoc components', () => {
|
||||
describe('ApiLogo Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let specMgr;
|
||||
|
||||
let schemaUrl = '/tests/schemas/api-info-test.json';
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
|
||||
beforeEach(async(inject([SpecManager], ( _specMgr) => {
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load(schemaUrl).then(done, done.fail);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestAppComponent);
|
||||
component = getChildDebugElement(fixture.debugElement, 'api-logo').componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
||||
it('should init component data', () => {
|
||||
if (specMgr.a) return;
|
||||
expect(component).not.toBeNull();
|
||||
expect(component.logo).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not display image when no x-logo', () => {
|
||||
component.logo.should.be.empty();
|
||||
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement;
|
||||
let imgElement = nativeElement.querySelector('img');
|
||||
expect(imgElement).toBeNull();
|
||||
|
||||
// update schemaUrl to load other schema in the next test
|
||||
schemaUrl = '/tests/schemas/extended-petstore.yml';
|
||||
});
|
||||
|
||||
it('should load values from spec and use transparent bgColor by default', () => {
|
||||
component.logo.imgUrl.should.endWith('petstore-logo.png');
|
||||
component.logo.bgColor.should.be.equal('transparent');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains an ApiInfo. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<api-logo></api-logo>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
|
||||
@Component({
|
||||
selector: 'api-logo',
|
||||
styleUrls: ['./api-logo.css'],
|
||||
templateUrl: './api-logo.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ApiLogo extends BaseComponent implements OnInit {
|
||||
logo:any = {};
|
||||
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
init() {
|
||||
const info = this.componentSchema.info;
|
||||
const logoInfo = info['x-logo'];
|
||||
if (!logoInfo) return;
|
||||
this.logo.imgUrl = logoInfo.url;
|
||||
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
|
||||
this.logo.url = info.contact && info.contact.url || null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
<div class="operation-endpoint" (click)="handleClick()">
|
||||
<h5 class="http-verb" [ngClass]="verb">{{verb}}</h5>
|
||||
<span><!--
|
||||
--><span class="operation-api-url-path">{{path}}</span><!--
|
||||
--></span>
|
||||
<svg class="expand-icon" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
<polygon fill="white" points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="servers-overlay">
|
||||
<div *ngFor="let server of servers" class="server-item">
|
||||
<div class="description" [innerHtml]="server.description | marked"></div>
|
||||
<div select-on-click class="url">
|
||||
<span class="operation-api-url"> {{server.url}}</span>{{path}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,153 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.operation-endpoint {
|
||||
padding: 10px 30px 10px 20px;
|
||||
border-radius: $border-radius*2;
|
||||
background-color: darken($black, 2%);
|
||||
display: block;
|
||||
font-weight: $light;
|
||||
white-space: nowrap;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.operation-endpoint > .operation-params-subheader {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 0;
|
||||
margin: 0;
|
||||
font-size: 12/14em;
|
||||
color: $black;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.operation-api-url {
|
||||
color: rgba($black, 0.8);
|
||||
&-path {
|
||||
font-family: $headers-font, $headers-font-family;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
color: #ffffff;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.http-verb {
|
||||
color: $black;
|
||||
background: #ffffff;
|
||||
padding: 3px 10px;
|
||||
text-transform: uppercase;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
background: $side-bar-bg-color;
|
||||
color: $black;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 4px 4px 6px rgba(0, 0, 0, 0.33);
|
||||
overflow: hidden;
|
||||
border-bottom-left-radius: $border-radius*2;
|
||||
border-bottom-right-radius: $border-radius*2;
|
||||
}
|
||||
|
||||
.server-item {
|
||||
padding: 10px;
|
||||
//margin-bottom: 10px;
|
||||
|
||||
& > .url {
|
||||
padding: 5px;
|
||||
border: 1px solid $border-color;
|
||||
background: $background-color;
|
||||
word-break: break-all;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
background: darken($black, 2%);
|
||||
transform: rotateZ(0);
|
||||
transition: all 0.2s ease;
|
||||
top: 15px;
|
||||
right: 5px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
transform: translateY(-50%) scaleY(0);
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
:host.expanded {
|
||||
> .operation-endpoint {
|
||||
border-color: $side-bar-bg-color;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
transform: translateY(0%) scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
.http-verb {
|
||||
color: white;
|
||||
|
||||
&.get {
|
||||
background-color: $get-color;
|
||||
}
|
||||
|
||||
&.post {
|
||||
background-color: $post-color;
|
||||
}
|
||||
|
||||
&.put {
|
||||
background-color: $put-color;
|
||||
}
|
||||
|
||||
&.options {
|
||||
background-color: $options-color;
|
||||
}
|
||||
|
||||
&.patch {
|
||||
background-color: $patch-color;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
background-color: $delete-color;
|
||||
}
|
||||
|
||||
&.basic {
|
||||
background-color: $basic-color;
|
||||
}
|
||||
|
||||
&.link {
|
||||
background-color: $link-color;
|
||||
}
|
||||
|
||||
&.head {
|
||||
background-color: $head-color;
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
|
||||
import { EndpointLink } from './endpoint-link';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import { OptionsService } from '../../services/';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
describe('EndpointLink Component', () => {
|
||||
let builder;
|
||||
let component: EndpointLink;
|
||||
let specMgr: SpecManager;
|
||||
let opts: OptionsService;
|
||||
|
||||
beforeEach(async(inject([SpecManager, OptionsService], (_specMgr, _opts) => {
|
||||
specMgr = _specMgr;
|
||||
opts = _opts;
|
||||
})));
|
||||
|
||||
beforeEach(() => {
|
||||
specMgr.apiUrl = 'http://test.com/v1';
|
||||
specMgr._schema = {
|
||||
info: {},
|
||||
host: 'petstore.swagger.io',
|
||||
baseName: '/v2',
|
||||
schemes: ['https', 'http'],
|
||||
'x-servers': [
|
||||
{
|
||||
url: '//test.com/v2'
|
||||
},
|
||||
{
|
||||
url: 'ws://test.com/v3',
|
||||
description: 'test'
|
||||
}
|
||||
]
|
||||
};
|
||||
specMgr.init();
|
||||
|
||||
component = new EndpointLink(specMgr, opts);
|
||||
});
|
||||
|
||||
it('should replace // with appropriate protocol', () => {
|
||||
component.ngOnInit();
|
||||
component.servers[0].url.should.be.equal('https://test.com/v2');
|
||||
});
|
||||
|
||||
|
||||
it('should preserve other protocols', () => {
|
||||
component.ngOnInit();
|
||||
component.servers[1].url.should.be.equal('ws://test.com/v3');
|
||||
});
|
||||
|
||||
it('should fallback to host + basePath + schemas if no x-servers', () => {
|
||||
specMgr._schema['x-servers'] = null;
|
||||
specMgr.init();
|
||||
component.ngOnInit();
|
||||
component.servers.should.be.lengthOf(1);
|
||||
component.servers[0].url.should.be.equal('https://petstore.swagger.io');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains an Operation. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<operation pointer='#/paths/~1user~1{username}/put'></operation>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, Input, OnInit, HostListener, HostBinding} from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { OptionsService } from '../../services/';
|
||||
import { stripTrailingSlash } from '../../utils/';
|
||||
|
||||
export interface ServerInfo {
|
||||
description: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'endpoint-link',
|
||||
styleUrls: ['./endpoint-link.css'],
|
||||
templateUrl: './endpoint-link.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class EndpointLink implements OnInit {
|
||||
@Input() path:string;
|
||||
@Input() verb:string;
|
||||
|
||||
apiUrl: string;
|
||||
servers: ServerInfo[];
|
||||
@HostBinding('class.expanded') expanded: boolean = false;
|
||||
|
||||
// @HostListener('click')
|
||||
handleClick() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
|
||||
constructor(public specMgr:SpecManager, public optionsService: OptionsService) {
|
||||
this.expanded = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
let servers:ServerInfo[] = this.specMgr.schema['x-servers'];
|
||||
if (servers) {
|
||||
this.servers = servers.map(({url, description}) => ({
|
||||
description,
|
||||
url: stripTrailingSlash(url.startsWith('//') ? `${this.specMgr.apiProtocol}:${url}` : url)
|
||||
}));
|
||||
} else {
|
||||
this.servers = [
|
||||
{
|
||||
description: 'Server URL',
|
||||
url: this.getBaseUrl()
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
getBaseUrl():string {
|
||||
if (this.optionsService.options.hideHostname) {
|
||||
return '';
|
||||
} else {
|
||||
return this.specMgr.apiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.init();
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
'use strict';
|
||||
import { Component, Input, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
|
||||
@Component({
|
||||
selector: 'redoc-externalDocs',
|
||||
template: `<a *ngIf="docs" [href]="docs.url" [innerHtml]="docs.description | marked"></a>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ExternalDocs implements OnInit {
|
||||
@Input() docs;
|
||||
|
||||
ngOnInit() {
|
||||
if (this.docs && !this.docs.description) {
|
||||
this.docs.description = 'External Docs';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
$lines-width: 1px;
|
||||
$bullet-size: 1px;
|
||||
$cell-spacing: 25px;
|
||||
$cell-padding: 10px;
|
||||
$bullet-margin: 10px;
|
||||
$line-border: $lines-width solid $tree-lines-color;
|
||||
$line-border-erase: ($lines-width + 1px) solid $background-color;
|
||||
|
||||
$border-color: lighten($secondary-color, 50%);
|
||||
$nullable-color: #3195a6;
|
||||
$hint-border: 1px dotted rgba(38, 50, 56, 0.4);
|
||||
|
||||
$param-name-height: 20px;
|
||||
|
||||
$sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
||||
|
||||
.param-name-wrap {
|
||||
display: inline-block;
|
||||
padding-right: $cell-spacing;
|
||||
font-family: $headers-font, $headers-font-family;
|
||||
}
|
||||
|
||||
.param-info {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding: $cell-padding 0;
|
||||
width: 75%;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
> div {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.param-range {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
border-radius: $border-radius;
|
||||
background-color: rgba($primary-color, 0.1);
|
||||
padding: 0 4px;
|
||||
color: rgba($primary-color, 0.7);
|
||||
}
|
||||
|
||||
.param-description {
|
||||
//font-size: 14px;
|
||||
}
|
||||
|
||||
.param-required {
|
||||
vertical-align: middle;
|
||||
line-height: $param-name-height;
|
||||
color: $red;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-nullable {
|
||||
vertical-align: middle;
|
||||
line-height: $param-name-height;
|
||||
color: $nullable-color;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-type,
|
||||
.param-array-format {
|
||||
vertical-align: middle;
|
||||
line-height: $param-name-height;
|
||||
color: rgba($black, 0.4);
|
||||
font-size: 0.929em;
|
||||
}
|
||||
.param-type {
|
||||
font-weight: normal;
|
||||
word-break: break-all;
|
||||
&.array::before,
|
||||
&.tuple::before {
|
||||
color: $black;
|
||||
font-weight: $base-font-weight;
|
||||
.param-collection-format-multi + & {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.array::before {
|
||||
content: $array-text;
|
||||
}
|
||||
&.tuple::before {
|
||||
content: $tuple-text;
|
||||
}
|
||||
|
||||
&.with-hint {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.4em;
|
||||
border-bottom: $hint-border;
|
||||
padding: 0;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
&-trivial {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&-file {
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
// tree
|
||||
|
||||
// Bullet
|
||||
|
||||
.param-name {
|
||||
border-left: $line-border;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
padding: $cell-padding 0;
|
||||
vertical-align: top;
|
||||
line-height: $param-name-height;
|
||||
|
||||
white-space: nowrap;
|
||||
font-size: 0.929em;
|
||||
font-weight: $regular;
|
||||
|
||||
> span::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: $bullet-size;
|
||||
height: $bullet-size + 6;
|
||||
background-color: $primary-color;
|
||||
margin: 0 $bullet-margin;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
> span::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-top: $line-border;
|
||||
width: $bullet-margin;
|
||||
left: 0;
|
||||
top: ($param-name-height / 2) + $cell-padding + 1;
|
||||
}
|
||||
}
|
||||
|
||||
.param:first-of-type {
|
||||
> .param-name::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
top: 0;
|
||||
border-left: $line-border-erase;
|
||||
height: ($param-name-height / 2) + $cell-padding + 1;
|
||||
}
|
||||
}
|
||||
|
||||
.param:last-of-type,
|
||||
.param.last {
|
||||
> .param-name {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width - 1px;
|
||||
border-left: $line-border-erase;
|
||||
top: ($param-name-height / 2) + $cell-padding + $lines-width + 1;
|
||||
background-color: $background-color;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.param-wrap:last-of-type > .param-schema {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.param-schema {
|
||||
.param-wrap:first-of-type {
|
||||
.param-name::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.param-schema.last {
|
||||
> td {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.param-enum {
|
||||
color: $text-color;
|
||||
font-size: 0.95em;
|
||||
|
||||
&::before {
|
||||
content: 'Valid values: ';
|
||||
}
|
||||
}
|
||||
|
||||
.param-enum {
|
||||
color: $text-color;
|
||||
font-size: 0.95em;
|
||||
|
||||
&::before {
|
||||
content: 'Valid values: ';
|
||||
}
|
||||
|
||||
.param-type.array ~ &::before {
|
||||
content: 'Valid items values: ';
|
||||
}
|
||||
}
|
||||
|
||||
.param-pattern {
|
||||
color: $nullable-color;
|
||||
white-space: nowrap;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '/';
|
||||
margin: 0 3px;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.param-default {
|
||||
font-size: 0.95em;
|
||||
|
||||
&::before {
|
||||
content: 'Default: ';
|
||||
}
|
||||
}
|
||||
|
||||
.param-example {
|
||||
font-size: 0.95em;
|
||||
|
||||
&::before {
|
||||
content: 'Example: ';
|
||||
}
|
||||
}
|
||||
|
||||
.param-enum-value,
|
||||
.param-default-value,
|
||||
.param-example-value {
|
||||
font-family: Courier, monospace;
|
||||
background-color: rgba($secondary-color, 0.02);
|
||||
border: 1px solid rgba($secondary-color, 0.1);
|
||||
margin: 2px 3px;
|
||||
padding: 0.1em 0.2em 0.2em;
|
||||
border-radius: $border-radius;
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import {
|
||||
inject,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
|
||||
describe('JsonSchemaLazy Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestAppComponent);
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema-lazy');
|
||||
component = <JsonSchemaLazy>debugEl.componentInstance;
|
||||
spyOn(component, '_loadAfterSelf').and.stub();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
component._loadAfterSelf.and.callThrough();
|
||||
});
|
||||
|
||||
it('should init component', () => {
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should run loadNextToLocation on load', () => {
|
||||
component.pointer = '#/def';
|
||||
fixture.detectChanges();
|
||||
component.load();
|
||||
expect(component._loadAfterSelf).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains a lazy schema. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<json-schema-lazy></json-schema-lazy>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, ElementRef, ViewContainerRef, OnDestroy, OnInit, Input,
|
||||
AfterViewInit, ComponentFactoryResolver, Renderer } from '@angular/core';
|
||||
|
||||
import { JsonSchema } from './json-schema';
|
||||
import { OptionsService } from '../../services/options.service';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
|
||||
var cache = {};
|
||||
|
||||
@Component({
|
||||
selector: 'json-schema-lazy',
|
||||
entryComponents: [ JsonSchema ],
|
||||
template: '',
|
||||
styles: [':host { display:none }']
|
||||
})
|
||||
export class JsonSchemaLazy implements OnDestroy, OnInit, AfterViewInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() auto: boolean;
|
||||
@Input() isRequestSchema: boolean;
|
||||
@Input() final: boolean = false;
|
||||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
@Input() isArray: boolean;
|
||||
disableLazy: boolean = false;
|
||||
loaded: boolean = false;
|
||||
constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef,
|
||||
private resolver:ComponentFactoryResolver, private optionsService:OptionsService, private _renderer: Renderer) {
|
||||
this.disableLazy = this.optionsService.options.disableLazySchemas;
|
||||
}
|
||||
|
||||
normalizePointer() {
|
||||
let schema = this.specMgr.byPointer(this.pointer);
|
||||
return schema && schema.$ref || this.pointer;
|
||||
}
|
||||
|
||||
private _loadAfterSelf() {
|
||||
var componentFactory = this.resolver.resolveComponentFactory(JsonSchema);
|
||||
let contextInjector = this.location.parentInjector;
|
||||
let compRef = this.location.createComponent(componentFactory, null, contextInjector, null);
|
||||
this.projectComponentInputs(compRef.instance);
|
||||
this._renderer.setElementAttribute(compRef.location.nativeElement, 'class', this.location.element.nativeElement.className);
|
||||
compRef.changeDetectorRef.detectChanges();
|
||||
this.loaded = true;
|
||||
return compRef;
|
||||
}
|
||||
|
||||
load() {
|
||||
if (this.disableLazy) return;
|
||||
if (this.loaded) return;
|
||||
if (this.pointer) {
|
||||
this._loadAfterSelf();
|
||||
}
|
||||
}
|
||||
|
||||
// cache JsonSchema view
|
||||
loadCached() {
|
||||
this.pointer = this.normalizePointer();
|
||||
if (cache[this.pointer]) {
|
||||
let compRef = cache[this.pointer];
|
||||
let $element = compRef.location.nativeElement;
|
||||
|
||||
// skip caching view with descendant schemas
|
||||
// as it needs attached controller
|
||||
let hasDescendants = compRef.instance.descendants && compRef.instance.descendants.length;
|
||||
if (!this.disableLazy && (hasDescendants || compRef.instance._hasSubSchemas)) {
|
||||
this._loadAfterSelf();
|
||||
return;
|
||||
}
|
||||
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
||||
this.loaded = true;
|
||||
} else {
|
||||
cache[this.pointer] = this._loadAfterSelf();
|
||||
}
|
||||
}
|
||||
|
||||
projectComponentInputs(instance:JsonSchema) {
|
||||
Object.assign(instance, this);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.auto && !this.disableLazy) return;
|
||||
this.loadCached();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// clear cache
|
||||
cache = {};
|
||||
}
|
||||
}
|
||||
|
||||
function insertAfter(newNode, referenceNode) {
|
||||
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
<ng-container [ngSwitch]="schema._widgetType">
|
||||
<ng-template ngSwitchCase="file">
|
||||
<span class="param-wrap">
|
||||
<span class="param-type-file">file</span>
|
||||
<div *ngIf="schema._produces && !isRequestSchema" class="file produces">
|
||||
<ul>
|
||||
<li *ngFor="let type of schema._produces">{{type}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="schema._consumes && isRequestSchema" class="file consume">
|
||||
<ul>
|
||||
<li *ngFor="let type of schema._consumes">{{type}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="trivial">
|
||||
<span class="param-wrap">
|
||||
<span class="param-type param-type-trivial {{schema.type}}"
|
||||
[ngClass]="{'with-hint': schema._displayTypeHint, 'array': _isArray}"
|
||||
title="{{schema._displayTypeHint}}">{{schema._displayType}} {{schema._displayFormat}}
|
||||
<span class="param-range" *ngIf="schema._range"> {{schema._range}} </span>
|
||||
</span>
|
||||
<span *ngIf="schema['x-nullable']" class="param-nullable">Nullable</span>
|
||||
<div *ngIf="schema.enum" class="param-enum">
|
||||
<span *ngFor="let enumItem of schema.enum" class="param-enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
|
||||
</div>
|
||||
<span *ngIf="schema.pattern" class="param-pattern">{{schema.pattern}}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="tuple">
|
||||
<div class="params-wrap params-array array-tuple">
|
||||
<ng-template ngFor [ngForOf]="schema.items" let-item="$implicit" let-idx="index" [ngForTrackBy]="trackByIdx">
|
||||
<div class="tuple-item">
|
||||
<span class="tuple-item-index"> [{{idx}}]: </span>
|
||||
<json-schema class="nested-schema" [pointer]="item._pointer"
|
||||
[absolutePointer]="item._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema">
|
||||
</json-schema>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="array">
|
||||
<json-schema class="nested-schema" [pointer]="schema._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema"> </json-schema>
|
||||
</ng-template>
|
||||
<ng-template ngSwitchCase="object">
|
||||
<table class="params-wrap" [ngClass]="{'params-array': _isArray}">
|
||||
<!-- <caption> {{_displayType}} </caption> -->
|
||||
<ng-template ngFor [ngForOf]="properties" let-prop="$implicit" let-last="last" [ngForTrackBy]="trackByName">
|
||||
<tr class="param"
|
||||
[class.last]="last"
|
||||
[class.discriminator] = "prop.isDiscriminator"
|
||||
[class.complex] = "prop._pointer"
|
||||
[class.additional] = "prop._additional"
|
||||
[class.expanded] = "subSchema.open">
|
||||
<td class="param-name">
|
||||
<span class="param-name-wrap" (click)="subSchema.toggle()">
|
||||
<span class="param-name-content">
|
||||
{{prop.name}}
|
||||
<span class="param-name-enumvalue" [hidden]="!prop._enumItem"> {{prop._enumItem?.val | json}} </span>
|
||||
</span>
|
||||
<svg *ngIf="prop._pointer" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve">
|
||||
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "/>
|
||||
</svg>
|
||||
</span>
|
||||
</td>
|
||||
<td class="param-info">
|
||||
<div>
|
||||
<span class="param-type {{prop.type}}" [ngClass]="{'with-hint': prop._displayTypeHint, 'tuple': prop._isTuple, 'array': (prop._isArray || prop.type == 'array')}"
|
||||
title="{{prop._displayTypeHint}}"> {{prop._displayType}} {{prop._displayFormat}}
|
||||
<span class="param-range" *ngIf="prop._range"> {{prop._range}} </span>
|
||||
</span>
|
||||
<span *ngIf="prop._required" class="param-required">Required</span>
|
||||
<span *ngIf="prop['x-nullable']" class="param-nullable">Nullable</span>
|
||||
<div class="param-default" *ngIf="prop.default != null">
|
||||
<span class="param-default-value">{{prop.default | json}}</span>
|
||||
</div>
|
||||
<div *ngIf="prop.enum && !prop.isDiscriminator" class="param-enum">
|
||||
<span *ngFor="let enumItem of prop.enum" class="param-enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
|
||||
</div>
|
||||
<span *ngIf="prop.pattern" class="param-pattern">{{prop.pattern}}</span>
|
||||
</div>
|
||||
<div class="param-description" [innerHtml]="prop.description | marked"></div>
|
||||
<div class="discriminator-info" *ngIf="prop.isDiscriminator && descendants.length">
|
||||
<drop-down (change)="selectDescendantByIdx($event)" [active]="activeDescendant.idx">
|
||||
<option *ngFor="let descendant of descendants; let i=index"
|
||||
[value]="i" [attr.selected]="descendant.active ? '' : null" >{{descendant.name}}</option>
|
||||
</drop-down>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="param-schema" [ngClass]="{'last': last}" [hidden]="!prop._pointer">
|
||||
<td colspan="2">
|
||||
<zippy [attr.disabled]="prop.name" #subSchema title="Expand" [headless]="true" (openChange)="lazySchema.load()" [(open)]="prop.expanded">
|
||||
<json-schema-lazy #lazySchema [auto]="prop.expanded" class="nested-schema" [pointer]="prop._pointer"
|
||||
[nestOdd]="!nestOdd" [isRequestSchema]="isRequestSchema" absolutePointer="{{absolutePointer}}/properties/{{prop.name}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</table>
|
||||
</ng-template>
|
||||
|
||||
</ng-container>
|
|
@ -1,221 +0,0 @@
|
|||
@import 'json-schema-common';
|
||||
|
||||
// styles for array-schema for array
|
||||
$array-marker-font-sz: 13px;
|
||||
$array-marker-line-height: 1.5;
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.param-schema > td {
|
||||
border-left: $line-border;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.derived-schema {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.derived-schema.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host.nested-schema {
|
||||
background-color: white;
|
||||
padding: 10px 20px;
|
||||
position: relative;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:before, &:after {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 10px 15px 0;
|
||||
margin-left: -7.5px;
|
||||
border-top-color: $side-menu-active-bg-color;
|
||||
}
|
||||
&:before {
|
||||
left: 10%;
|
||||
}
|
||||
|
||||
&:after {
|
||||
right: 10%;
|
||||
}
|
||||
|
||||
.param:first-of-type > .param-name:before, .param:last-of-type > .param-name:after {
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
:host[nestodd="true"] {
|
||||
background-color: $side-menu-active-bg-color;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&:before, &:after {
|
||||
border-top-color: white;
|
||||
}
|
||||
|
||||
> .params-wrap > .param:first-of-type > .param-name:before,
|
||||
> .params-wrap > .param:last-of-type > .param-name:after {
|
||||
border-color: $side-menu-active-bg-color;
|
||||
}
|
||||
|
||||
> .params-wrap > .param:last-of-type,
|
||||
> .params-wrap > .param.last {
|
||||
> .param-name:after {
|
||||
border-color: $side-menu-active-bg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zippy {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.zippy-content-wrap {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.param.complex.expanded > .param-info {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.param.complex > .param-name .param-name-wrap {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.param.complex > .param-name svg {
|
||||
height: 1.2em;
|
||||
width: 1.2em;
|
||||
vertical-align: middle;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.param.complex.expanded > .param-name svg{
|
||||
transform: rotateZ(-180deg);
|
||||
}
|
||||
|
||||
.param.additional > .param-name {
|
||||
color: rgba($black, 0.4);
|
||||
}
|
||||
|
||||
.params-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table {
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.params-wrap.params-array:before, .params-wrap.params-array:after {
|
||||
display: block;
|
||||
font-weight: $base-font-weight;
|
||||
color: $black;
|
||||
font-size: $array-marker-font-sz;
|
||||
line-height: $array-marker-line-height;
|
||||
}
|
||||
|
||||
.params-wrap.params-array:after {
|
||||
content: "]";
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.params-wrap.params-array:before {
|
||||
content: "Array [";
|
||||
padding-top: 1em;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.params-wrap.params-array {
|
||||
padding-left: $bullet-margin;
|
||||
}
|
||||
|
||||
.param-schema.param-array:before {
|
||||
bottom: ($array-marker-font-sz * $array-marker-line-height) / 2;
|
||||
width: $bullet-margin;
|
||||
border-left-style: dashed;
|
||||
border-bottom: $lines-width dashed $tree-lines-color;
|
||||
}
|
||||
|
||||
.params-wrap.params-array > .param-wrap:first-of-type > .param > .param-name:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
top: 0;
|
||||
border-left: $line-border-erase;
|
||||
height: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.params-wrap > .param > .param-schema.param-array {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.discriminator-info {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.discriminator-wrap:not(.empty) > td {
|
||||
//border-left: $line-border;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
border-left: $line-border;
|
||||
height: ($param-name-height/2) + $cell-padding + 1;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
ul, li {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
li:before {
|
||||
content: "- ";
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.array-tuple > .tuple-item {
|
||||
margin-top: 1.5em;
|
||||
display: flex;
|
||||
|
||||
> span {
|
||||
flex: 0;
|
||||
padding: 10px 15px 10px 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
> json-schema {
|
||||
flex: 1;
|
||||
&:before, &:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.param-name-enumvalue {
|
||||
padding: 2px;
|
||||
background-color: #e6ebf6;
|
||||
|
||||
&:before {
|
||||
content: " = ";
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
inject,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';;
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
describe('JsonSchema Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let specMgr;
|
||||
|
||||
beforeEach(inject([SpecManager], ( _spec) => {
|
||||
|
||||
specMgr = _spec;
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestAppComponent);
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
|
||||
component = debugEl.componentInstance;
|
||||
});
|
||||
|
||||
it('should init component', () => {
|
||||
component.pointer = '';
|
||||
(<SpecManager>specMgr)._schema = {type: 'object'};
|
||||
fixture.detectChanges();
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should set isTrivial for non-object/array types', () => {
|
||||
component.pointer = '#';
|
||||
(<any>specMgr)._schema = {type: 'string'};
|
||||
fixture.detectChanges();
|
||||
component.schema.isTrivial.should.be.true();
|
||||
});
|
||||
|
||||
it('should use < anything > notation for prop without type', () => {
|
||||
component.pointer = '#';
|
||||
(<any>specMgr)._schema = {type: 'object', properties: {
|
||||
test: {}
|
||||
}};
|
||||
fixture.detectChanges();
|
||||
component.schema._properties[0]._displayType.should.be.equal('< anything >');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains a json schema. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<json-schema></json-schema>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component,
|
||||
Input,
|
||||
Renderer,
|
||||
ElementRef,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
|
||||
import { BaseSearchableComponent, SpecManager } from '../base';
|
||||
import { SchemaNormalizer, SchemaHelper, AppStateService, OptionsService } from '../../services/';
|
||||
import { JsonPointer, DescendantInfo } from '../../utils/';
|
||||
import { Zippy } from '../../shared/components';
|
||||
import { JsonSchemaLazy } from './json-schema-lazy';
|
||||
|
||||
@Component({
|
||||
selector: 'json-schema',
|
||||
templateUrl: './json-schema.html',
|
||||
styleUrls: ['./json-schema.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class JsonSchema extends BaseSearchableComponent implements OnInit {
|
||||
@Input() pointer: string;
|
||||
@Input() absolutePointer: string;
|
||||
@Input() final: boolean = false;
|
||||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
@Input() isRequestSchema: boolean;
|
||||
|
||||
schema: any = {};
|
||||
activeDescendant:any = {};
|
||||
discriminator: string = null;
|
||||
_hasSubSchemas: boolean = false;
|
||||
properties: any;
|
||||
_isArray: boolean;
|
||||
normalizer: SchemaNormalizer;
|
||||
descendants: DescendantInfo[];
|
||||
|
||||
constructor(
|
||||
specMgr: SpecManager,
|
||||
app: AppStateService,
|
||||
private _renderer: Renderer,
|
||||
private cdr: ChangeDetectorRef,
|
||||
private _elementRef: ElementRef,
|
||||
private optionsService: OptionsService) {
|
||||
super(specMgr, app);
|
||||
this.normalizer = new SchemaNormalizer(specMgr);
|
||||
}
|
||||
|
||||
get normPointer() {
|
||||
return this.schema._pointer || this.pointer;
|
||||
}
|
||||
|
||||
selectDescendantByIdx(idx) {
|
||||
this.selectDescendant(this.descendants[idx]);
|
||||
}
|
||||
|
||||
selectDescendant(activeDescendant: DescendantInfo) {
|
||||
if (!activeDescendant || activeDescendant.active) return;
|
||||
this.descendants.forEach(d => {
|
||||
d.active = false;
|
||||
});
|
||||
activeDescendant.active = true;
|
||||
|
||||
this.schema = this.specMgr.getDescendant(activeDescendant, this.componentSchema);
|
||||
this.pointer = this.schema._pointer || activeDescendant.$ref;
|
||||
this.normalizer.reset();
|
||||
this.schema = this.normalizer.normalize(this.schema, this.normPointer,
|
||||
{resolved: true});
|
||||
this.preprocessSchema();
|
||||
this.activeDescendant = activeDescendant;
|
||||
}
|
||||
|
||||
initDescendants() {
|
||||
this.descendants = this.specMgr.findDerivedDefinitions(this.normPointer, this.schema);
|
||||
if (!this.descendants.length) return;
|
||||
let discriminator = this.discriminator = this.schema.discriminator || this.schema['x-extendedDiscriminator'];
|
||||
let discrProperty = this.schema.properties &&
|
||||
this.schema.properties[discriminator];
|
||||
if (discrProperty && discrProperty.enum) {
|
||||
let enumOrder = {};
|
||||
discrProperty.enum.forEach((enumItem, idx) => {
|
||||
enumOrder[enumItem] = idx;
|
||||
});
|
||||
|
||||
this.descendants = this.descendants
|
||||
.filter(a => {
|
||||
return enumOrder[a.name] != undefined;
|
||||
}).sort((a, b) => {
|
||||
return enumOrder[a.name] > enumOrder[b.name] ? 1 : -1;
|
||||
});
|
||||
}
|
||||
this.descendants.forEach((d, idx) => d.idx = idx);
|
||||
this.selectDescendantByIdx(0);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.pointer) return;
|
||||
if (!this.absolutePointer) this.absolutePointer = this.pointer;
|
||||
|
||||
this.schema = this.componentSchema;
|
||||
if (!this.schema) {
|
||||
throw new Error(`Can't load component schema at ${this.pointer}`);
|
||||
}
|
||||
|
||||
this.applyStyling();
|
||||
|
||||
this.schema = this.normalizer.normalize(this.schema, this.normPointer, {resolved: true});
|
||||
this.schema = SchemaHelper.unwrapArray(this.schema, this.normPointer);
|
||||
this._isArray = this.schema._isArray;
|
||||
this.absolutePointer += (this._isArray ? '/items' : '');
|
||||
this.initDescendants();
|
||||
this.preprocessSchema();
|
||||
}
|
||||
|
||||
preprocessSchema() {
|
||||
SchemaHelper.preprocess(this.schema, this.normPointer, this.pointer);
|
||||
|
||||
if (!this.schema.isTrivial) {
|
||||
SchemaHelper.preprocessProperties(this.schema, this.normPointer, {
|
||||
childFor: this.childFor,
|
||||
discriminator: this.discriminator
|
||||
});
|
||||
}
|
||||
|
||||
this.properties = this.schema._properties || [];
|
||||
if (this.isRequestSchema) {
|
||||
this.properties = this.properties.filter(prop => !prop.readOnly);
|
||||
}
|
||||
|
||||
if (this.optionsService.options.requiredPropsFirst) {
|
||||
SchemaHelper.moveRequiredPropsFirst(this.properties, this.schema.required);
|
||||
}
|
||||
|
||||
this._hasSubSchemas = this.properties && this.properties.some(
|
||||
propSchema => {
|
||||
if (propSchema.type === 'array') {
|
||||
propSchema = propSchema.items;
|
||||
}
|
||||
return (propSchema && propSchema.type === 'object' && propSchema._pointer);
|
||||
});
|
||||
|
||||
if (this.properties.length === 1) {
|
||||
this.properties[0].expanded = true;
|
||||
}
|
||||
}
|
||||
|
||||
applyStyling() {
|
||||
if (this.nestOdd) {
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
trackByName(_: number, item: any): number {
|
||||
return item.name + (item._pointer || '');
|
||||
}
|
||||
|
||||
trackByIdx(idx: number, _: any): number {
|
||||
return idx;
|
||||
}
|
||||
|
||||
findDescendantWithField(fieldName: string): DescendantInfo {
|
||||
let res: DescendantInfo;
|
||||
for (let descendantInfo of this.descendants) {
|
||||
let schema = this.specMgr.getDescendant(descendantInfo, this.schema);
|
||||
this.normalizer.reset();
|
||||
schema = this.normalizer.normalize(schema, this.normPointer,
|
||||
{resolved: true});
|
||||
if (schema.properties && schema.properties[fieldName]) {
|
||||
res = descendantInfo;
|
||||
break;
|
||||
};
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
ensureSearchIsShown(ptr: string) {
|
||||
if (ptr.startsWith(this.absolutePointer)) {
|
||||
let props = this.properties;
|
||||
if (!props) return;
|
||||
let relative = JsonPointer.relative(this.absolutePointer, ptr);
|
||||
let propName;
|
||||
if (relative.length > 1 && relative[0] === 'properties') {
|
||||
propName = relative[1];
|
||||
}
|
||||
let prop = props.find(p => p.name === propName);
|
||||
if (!prop) {
|
||||
let d = this.findDescendantWithField(propName);
|
||||
this.selectDescendant(d);
|
||||
prop = this.properties.find(p => p.name === propName);
|
||||
}
|
||||
if (prop && !prop.isTrivial) prop.expanded = true;
|
||||
this.cdr.markForCheck();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
:host {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
|
||||
height: 5px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: attr(progress percentage);
|
||||
background-color: #5f7fc3;
|
||||
transition: right 0.2s linear;
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import {
|
||||
Component
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
ComponentFixture,
|
||||
inject,
|
||||
fakeAsync,
|
||||
tick,
|
||||
TestBed,
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { LoadingBar } from './loading-bar';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
describe('Loading Bar', () => {
|
||||
let component: LoadingBar;
|
||||
|
||||
it('should init component', () => {
|
||||
let fixture = TestBed.createComponent(LoadingBar);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
should.exist(component);
|
||||
component.progress.should.be.equal(0);
|
||||
component.display.should.be.equal('block');
|
||||
});
|
||||
|
||||
it('should hide itself in 500ms if progress is 100', fakeAsync(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
let fixture = TestBed.createComponent(TestAppComponent);
|
||||
let parentComp = fixture.componentInstance;
|
||||
component = getChildDebugElement(fixture.debugElement, 'loading-bar').componentInstance;
|
||||
// need to pass update through parent component as ngOnChanges is run only for view changes
|
||||
parentComp.progress = 50;
|
||||
fixture.detectChanges();
|
||||
parentComp.progress = 100;
|
||||
fixture.detectChanges();
|
||||
|
||||
component.display.should.be.equal('block');
|
||||
tick(500);
|
||||
component.display.should.be.equal('none');
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains an ApiInfo. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<loading-bar [progress]="progress"></loading-bar>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
progress = 0;
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
'use strict';
|
||||
import { Input, HostBinding, Component, OnChanges } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'loading-bar',
|
||||
template: `
|
||||
<span [style.width]='progress + "%"'> </span>
|
||||
`,
|
||||
styleUrls: ['loading-bar.css']
|
||||
})
|
||||
export class LoadingBar implements OnChanges {
|
||||
@Input() progress:number = 0;
|
||||
@HostBinding('style.display') display = 'block';
|
||||
|
||||
ngOnChanges(ch) {
|
||||
if (ch.progress.currentValue === 100) {
|
||||
setTimeout(() => {
|
||||
this.display = 'none';
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
<div class="operation" *ngIf="operation">
|
||||
<div class="operation-content">
|
||||
<h2 class="operation-header sharable-header" [class.deprecated]="operation.deprecated">
|
||||
<a class="share-link" href="#{{operation.anchor}}"></a>{{operation.summary}}
|
||||
</h2>
|
||||
<endpoint-link *ngIf="pathInMiddlePanel"
|
||||
[verb]="operation.verb" [path]="operation.path"> </endpoint-link>
|
||||
<div class="operation-tags" *ngIf="operation.info.tags.length">
|
||||
<a *ngFor="let tag of operation.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
|
||||
</div>
|
||||
<p *ngIf="operation.info.description" class="operation-description"
|
||||
[innerHtml]="operation.info.description | marked">
|
||||
</p>
|
||||
<redoc-externalDocs [docs]="operation.externalDocs"></redoc-externalDocs>
|
||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||
</div>
|
||||
<div class="operation-samples">
|
||||
|
||||
<endpoint-link *ngIf="!pathInMiddlePanel"
|
||||
[verb]="operation.verb" [path]="operation.path"> </endpoint-link>
|
||||
|
||||
<div>
|
||||
<request-samples [pointer]="pointer" [schemaPointer]="operation.bodyParam?._pointer">
|
||||
</request-samples>
|
||||
</div>
|
||||
<div>
|
||||
<br>
|
||||
<responses-samples pointer="{{pointer}}/responses"> </responses-samples>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,148 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
|
||||
:host {
|
||||
padding-bottom: 100px;
|
||||
display: block;
|
||||
border-bottom: 1px solid rgba(127, 127, 127, 0.25);
|
||||
margin-top: 1em;
|
||||
transform: translateZ(0);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// :host:last-of-type {
|
||||
// border-bottom: 0;
|
||||
// }
|
||||
|
||||
.operation-header {
|
||||
margin-bottom: calc(1em - 6px);
|
||||
|
||||
&.deprecated {
|
||||
&:after {
|
||||
content: 'Deprecated';
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
margin: 0;
|
||||
background-color: $yellow;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.operation-tags {
|
||||
margin-top: 20px;
|
||||
|
||||
> a {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
display: inline-block;
|
||||
padding: 0 0.5em;
|
||||
text-decoration: none;
|
||||
|
||||
&:before {
|
||||
content: '#';
|
||||
margin-right: -0.4em;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.operation-content, .operation-samples {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.operation-content {
|
||||
width: 100% - $samples-panel-width;
|
||||
padding: $section-spacing;
|
||||
}
|
||||
|
||||
.operation-samples {
|
||||
color: $sample-panel-color;
|
||||
width: 40%;
|
||||
padding: $section-spacing;
|
||||
background: $samples-panel-bg-color;
|
||||
}
|
||||
|
||||
.operation-samples pre {
|
||||
color: $sample-panel-color;
|
||||
}
|
||||
|
||||
.operation-samples header,
|
||||
.operation-samples > h5 {
|
||||
color: $sample-panel-headers-color;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.operation-samples > h5 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.operation-samples schema-sample {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.operation:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
.operation-description {
|
||||
padding: 6px 0 10px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
[select-on-click] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: $right-panel-squash-breakpoint) {
|
||||
.operations:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.operation-samples, .operation-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.operation-samples {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
:host {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.operation-content /deep/ endpoint-link {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.operation-endpoint[class] {
|
||||
padding: 5px 30px 5px 5px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $border-color;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
.operation-api-url-path {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
top: 8px;
|
||||
background-color: $border-color;
|
||||
}
|
||||
|
||||
.servers-overlay {
|
||||
border: 1px solid $border-color;
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
|
||||
import { Operation } from './operation';
|
||||
import { SpecManager } from '../../utils/spec-manager';;
|
||||
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';;
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
describe('Operation Component', () => {
|
||||
let builder;
|
||||
let component: Operation;
|
||||
let specMgr;
|
||||
|
||||
beforeEach(async(inject([SpecManager, LazyTasksService], (_specMgr, lazyTasks) => {
|
||||
lazyTasks.sync = true;
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
let fixture = TestBed.createComponent(TestAppComponent);
|
||||
component = getChildDebugElement(fixture.debugElement, 'operation').componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
||||
it('should init component', () => {
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should init basic component data', () => {
|
||||
component.operation.verb.should.be.equal('put');
|
||||
component.operation.path.should.be.equal('/user/{username}');
|
||||
});
|
||||
|
||||
|
||||
it('should main tag', () => {
|
||||
component.operation.info.tags.should.be.empty();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains a Operation. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<operation pointer='#/paths/~1user~1{username}/put'></operation>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
'use strict';
|
||||
import { Input, HostBinding, Component, OnInit, ChangeDetectionStrategy, ElementRef } from '@angular/core';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
import { OptionsService, MenuService } from '../../services/';
|
||||
import { SwaggerBodyParameter } from '../../utils/swagger-typings';
|
||||
|
||||
export interface OperationInfo {
|
||||
deprecated: boolean;
|
||||
verb: string;
|
||||
path: string;
|
||||
info: {
|
||||
tags: string[];
|
||||
description: string;
|
||||
};
|
||||
bodyParam: any;
|
||||
summary: string;
|
||||
anchor: string;
|
||||
externalDocs?: {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'operation',
|
||||
templateUrl: './operation.html',
|
||||
styleUrls: ['./operation.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class Operation extends BaseComponent implements OnInit {
|
||||
@Input() pointer :string;
|
||||
@Input() parentTagId :string;
|
||||
|
||||
@HostBinding('attr.operation-id') operationId;
|
||||
|
||||
operation: OperationInfo;
|
||||
pathInMiddlePanel: boolean;
|
||||
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
private optionsService: OptionsService,
|
||||
private menu: MenuService) {
|
||||
super(specMgr);
|
||||
|
||||
this.pathInMiddlePanel = optionsService.options.pathInMiddlePanel;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.operationId = this.componentSchema.operationId;
|
||||
|
||||
this.operation = {
|
||||
deprecated: this.componentSchema.deprecated,
|
||||
verb: JsonPointer.baseName(this.pointer),
|
||||
path: JsonPointer.baseName(this.pointer, 2),
|
||||
info: {
|
||||
description: this.componentSchema.description,
|
||||
tags: this.filterMainTags(this.componentSchema.tags)
|
||||
},
|
||||
bodyParam: this.findBodyParam(),
|
||||
summary: SchemaHelper.operationSummary(this.componentSchema),
|
||||
anchor: this.buildAnchor(),
|
||||
externalDocs: this.componentSchema.externalDocs
|
||||
};
|
||||
}
|
||||
|
||||
buildAnchor():string {
|
||||
return this.menu.hashFor(this.pointer,
|
||||
{ type: 'operation', operationId: this.operationId, pointer: this.pointer },
|
||||
this.parentTagId );
|
||||
}
|
||||
|
||||
filterMainTags(tags) {
|
||||
var tagsMap = this.specMgr.getTagsMap();
|
||||
if (!tags) return [];
|
||||
return tags.filter(tag => tagsMap[tag] && tagsMap[tag]['x-traitTag']);
|
||||
}
|
||||
|
||||
findBodyParam():SwaggerBodyParameter {
|
||||
let params = this.specMgr.getOperationParams(this.pointer);
|
||||
let bodyParam = params.find(param => param.in === 'body');
|
||||
return bodyParam;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<div class="operations">
|
||||
<div class="tag" *ngFor="let tag of tags; trackBy:trackByTagName" [attr.section]="tag.id">
|
||||
<div class="tag-info" *ngIf="tag.name">
|
||||
<h1 class="sharable-header"> <a class="share-link" href="#{{tag.anchor}}"></a>{{tag.name}} </h1>
|
||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||
<redoc-externalDocs [docs]="tag.metadata.externalDocs"></redoc-externalDocs>
|
||||
</div>
|
||||
<operation *lazyFor="let operation of tag.items; let ready = ready;"
|
||||
[hidden]="!ready" [pointer]="operation.metadata.pointer"
|
||||
[parentTagId]="tag.id" [attr.section]="operation.id"></operation>
|
||||
</div>
|
||||
</div>
|
|
@ -1,37 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host [hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tag-info {
|
||||
padding: $section-spacing;
|
||||
box-sizing: border-box;
|
||||
width: 60%;
|
||||
|
||||
@media (max-width: $right-panel-squash-breakpoint) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-info:after, .tag-info:before {
|
||||
content: "";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.tag-info h1 {
|
||||
color: $headers-color;
|
||||
text-transform: capitalize;
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.operations {
|
||||
display: block;
|
||||
position: relative;;
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
|
||||
|
||||
import { OperationsList } from './operations-list';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
describe('OperationsList Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let specMgr;
|
||||
|
||||
beforeEach(async(inject([SpecManager], (_specMgr) => {
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load('/tests/schemas/operations-list-component.json').then(done, done.fail);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TestAppComponent);
|
||||
component = getChildDebugElement(fixture.debugElement, 'operations-list').componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
||||
it('should init component and component data', () => {
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should build correct tags list', () => {
|
||||
expect(component.tags).not.toBeNull();
|
||||
component.tags.should.have.lengthOf(2);
|
||||
component.tags[0].name.should.be.equal('traitTag');
|
||||
should.not.exist(component.tags[0].items);
|
||||
component.tags[1].name.should.be.equal('tag1');
|
||||
component.tags[1].items.should.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<operations-list></operations-list>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
'use strict';
|
||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import { MenuService } from '../../services/index';
|
||||
|
||||
@Component({
|
||||
selector: 'operations-list',
|
||||
templateUrl: './operations-list.html',
|
||||
styleUrls: ['./operations-list.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class OperationsList extends BaseComponent implements OnInit {
|
||||
@Input() pointer:string;
|
||||
|
||||
tags:Array<any> = [];
|
||||
|
||||
constructor(specMgr:SpecManager, private menu: MenuService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
init() {
|
||||
let flatMenuItems = this.menu.flatItems;
|
||||
this.tags = [];
|
||||
let emptyTag = {
|
||||
name: '',
|
||||
items: []
|
||||
};
|
||||
flatMenuItems.forEach(menuItem => {
|
||||
// skip items that are not bound to swagger tags/operations
|
||||
if (!menuItem.metadata) return;
|
||||
|
||||
if (menuItem.metadata.type === 'tag') {
|
||||
this.tags.push({
|
||||
...menuItem,
|
||||
anchor: this.buildAnchor(menuItem.id)
|
||||
});
|
||||
}
|
||||
if (menuItem.metadata.type === 'operation' && !menuItem.parent) {
|
||||
emptyTag.items.push(menuItem);
|
||||
}
|
||||
});
|
||||
if (emptyTag.items.length) this.tags.push(emptyTag);
|
||||
}
|
||||
|
||||
buildAnchor(tagId):string {
|
||||
return this.menu.hashFor(tagId,
|
||||
{ type: 'tag'});
|
||||
}
|
||||
|
||||
trackByTagName(_, el) {
|
||||
return el.name;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<h5 class="param-list-header" *ngIf="params.length"> Parameters </h5>
|
||||
<ng-template ngFor [ngForOf]="params" let-paramType="$implicit">
|
||||
<header class="paramType">
|
||||
{{paramType.place}} Parameters
|
||||
<span class="hint--top-right hint--large" [attr.data-hint]="paramType.placeHint">?</span>
|
||||
</header>
|
||||
<div class="params-wrap">
|
||||
<div *ngFor="let param of paramType.params" class="param">
|
||||
<div class="param-name">
|
||||
<span class="param-name-wrap"> {{param.name}} </span>
|
||||
</div>
|
||||
<div class="param-info">
|
||||
<div>
|
||||
<span *ngIf='param.type === "array"'
|
||||
class="param-array-format param-collection-format-{{param.collectionFormat}}">
|
||||
{{param | collectionFormat}}
|
||||
</span>
|
||||
<span class="param-type {{param.type}}" [ngClass]="{'with-hint': param._displayTypeHint}"
|
||||
title="{{param._displayTypeHint}}"> {{param._displayType}} {{param._displayFormat}}</span>
|
||||
<span class="param-range" *ngIf="param._range"> {{param._range}} </span>
|
||||
<span *ngIf="param.required" class="param-required">Required</span>
|
||||
<div class="param-default" *ngIf="param.default != null">
|
||||
<span class="param-default-value">{{param.default | json}}</span>
|
||||
</div>
|
||||
<div class="param-example" *ngIf="param.example != null">
|
||||
<span class="param-example-value">{{param.example | json}}</span>
|
||||
</div>
|
||||
<div *ngIf="param.enum || param._enumItem" class="param-enum">
|
||||
<span *ngFor="let enumItem of param.enum" class="param-enum-value {{enumItem.type}}">
|
||||
{{enumItem.val | json}}
|
||||
</span>
|
||||
<span *ngIf="param._enumItem" class="param-enum-value {{param._enumItem.type}}">
|
||||
{{param._enumItem.val | json}}
|
||||
</span>
|
||||
</div>
|
||||
<span *ngIf="param.pattern" class="param-pattern">{{param.pattern}}</span>
|
||||
</div>
|
||||
<div class="param-description" [innerHtml]="param.description | marked"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="bodyParam">
|
||||
<h5 class="param-list-header" *ngIf="bodyParam"> Request Body </h5>
|
||||
|
||||
<div class="body-param-description" [innerHtml]="bodyParam.description | marked"></div>
|
||||
<div>
|
||||
<br>
|
||||
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{bodyParam._pointer}}/schema">
|
||||
</json-schema-lazy>
|
||||
</div>
|
||||
</div>
|
|
@ -1,99 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
$hint-color: #999999;
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.param-list-header {
|
||||
border-bottom: 1px solid rgba($text-color, .3);
|
||||
// padding: 0.2em 0;
|
||||
margin: 3em 0 1em 0;
|
||||
color: rgba($text-color, .5);
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@import '../JsonSchema/json-schema-common';
|
||||
|
||||
header.paramType {
|
||||
margin: 25px 0 5px 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.param-array-format {
|
||||
color: black;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
// paramters can't be multilevel so table representation works for it without javascript
|
||||
.params-wrap {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.param-name {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.param-info {
|
||||
display: table-cell;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.param {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.param:last-of-type > .param-name {
|
||||
border-left: 0;
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
border-left: $line-border;
|
||||
height: ($param-name-height/2) + $cell-padding + $lines-width;
|
||||
background-color: white;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.param:first-of-type .param-name:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
border-left: $line-border-erase;
|
||||
height: ($param-name-height/2) + $cell-padding;
|
||||
background-color: white;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
[data-hint] {
|
||||
width: 1.2em;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
color: $hint-color;
|
||||
line-height: 1.2;
|
||||
text-transform: none;
|
||||
cursor: help;
|
||||
border: 1px solid $hint-color;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
[data-hint] {
|
||||
float: right;
|
||||
}
|
||||
|
||||
[data-hint]:after {
|
||||
margin-left: 12px;
|
||||
transform: translateX(-100%) translateY(-8px);
|
||||
-moz-transform: translateX(-100%) translateY(-8px);
|
||||
-webkit-transform: translateX(-100%) translateY(-8px);
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { OptionsService } from '../../services/options.service';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
|
||||
function safePush(obj, prop, item) {
|
||||
if (!obj[prop]) obj[prop] = [];
|
||||
obj[prop].push(item);
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'params-list',
|
||||
templateUrl: './params-list.html',
|
||||
styleUrls: ['./params-list.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ParamsList extends BaseComponent implements OnInit {
|
||||
@Input() pointer: string;
|
||||
|
||||
params: Array<any>;
|
||||
empty: boolean;
|
||||
bodyParam: any;
|
||||
|
||||
constructor(specMgr: SpecManager, private options: OptionsService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.params = [];
|
||||
let paramsList = this.specMgr.getOperationParams(this.pointer);
|
||||
|
||||
const igrnoredHeaders =
|
||||
this.specMgr.schema['x-ignoredHeaderParameters'] ||
|
||||
this.options.options.ignoredHeaderParameters ||
|
||||
[];
|
||||
|
||||
paramsList = paramsList
|
||||
.map(paramSchema => {
|
||||
let propPointer = paramSchema._pointer;
|
||||
if (paramSchema.in === 'body') return paramSchema;
|
||||
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
|
||||
})
|
||||
.filter(param => {
|
||||
return param.in !== 'header' || igrnoredHeaders.indexOf(param.name) < 0;
|
||||
});
|
||||
|
||||
let paramsMap = this.orderParams(paramsList);
|
||||
|
||||
if (paramsMap.body && paramsMap.body.length) {
|
||||
let bodyParam = paramsMap.body[0];
|
||||
this.bodyParam = bodyParam;
|
||||
paramsMap.body = undefined;
|
||||
}
|
||||
|
||||
this.empty = !(Object.keys(paramsMap).length || this.bodyParam);
|
||||
|
||||
let paramsPlaces = ['path', 'query', 'formData', 'header', 'body'];
|
||||
let placeHint = {
|
||||
path: `Used together with Path Templating, where the parameter value is actually part
|
||||
of the operation's URL. This does not include the host or base path of the API.
|
||||
For example, in /items/{itemId}, the path parameter is itemId`,
|
||||
query: `Parameters that are appended to the URL.
|
||||
For example, in /items?id=###, the query parameter is id`,
|
||||
formData: `Parameters that are submitted through a form.
|
||||
application/x-www-form-urlencoded, multipart/form-data or both are usually
|
||||
used as the content type of the request`,
|
||||
header: 'Custom headers that are expected as part of the request'
|
||||
};
|
||||
let params = [];
|
||||
paramsPlaces.forEach(place => {
|
||||
if (paramsMap[place] && paramsMap[place].length) {
|
||||
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
|
||||
}
|
||||
});
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
orderParams(params):any {
|
||||
let res = {};
|
||||
params.forEach((param) => safePush(res, param.in, param));
|
||||
return res;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
@import url('//fonts.googleapis.com/css?family=Roboto:300,400,700|Montserrat:400,700');
|
||||
|
||||
redoc.loading {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg); }
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
redoc.loading:before {
|
||||
font-family: Helvetica;
|
||||
content: "Loading";
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
padding-top: 40px;
|
||||
color: #0033a0;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
z-index: 9999;
|
||||
opacity: 1;
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
redoc.loading:after {
|
||||
z-index: 10000;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="512" height="512" viewBox="0 0 512 512"><g></g><path d="M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z" fill="#0033a0"/><path d="M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z" fill="#0033a0"/><path d="M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z" fill="#0033a0"/><path d="M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z" fill="#0033a0"/><path d="M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z" fill="#0033a0"/><path d="M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z" fill="#0033a0"/><path d="M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z" fill="#0033a0"/><path d="M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z" fill="#0033a0"/></svg>');
|
||||
animation: 2s rotate linear infinite;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: 50%;
|
||||
margin-left: -25px;
|
||||
background-size: cover;
|
||||
top: 75px;
|
||||
transition: all 0.6s ease-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
redoc.loading-remove:before, redoc.loading-remove:after {
|
||||
opacity: 0;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<div class="redoc-error" *ngIf="error">
|
||||
<h1>Oops... ReDoc failed to render this spec</h1>
|
||||
<div class='redoc-error-details'>{{error.message}}</div>
|
||||
</div>
|
||||
<loading-bar *ngIf="options.lazyRendering" [progress]="loadingProgress"> </loading-bar>
|
||||
<div class="redoc-wrap" *ngIf="specLoaded && !error">
|
||||
<div class="background">
|
||||
<div class="background-actual"> </div>
|
||||
</div>
|
||||
<div class="menu-content" sticky-sidebar [disable]="specLoading"
|
||||
[scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
<div class="menu-header">
|
||||
<api-logo> </api-logo>
|
||||
<redoc-search> </redoc-search>
|
||||
</div>
|
||||
<side-menu> </side-menu>
|
||||
</div>
|
||||
<div class="api-content">
|
||||
<warnings></warnings>
|
||||
<api-info></api-info>
|
||||
<operations-list> </operations-list>
|
||||
<footer>
|
||||
<div class="powered-by-badge">
|
||||
<a href="https://github.com/Rebilly/ReDoc" title="Swagger-generated API Reference Documentation" target="_blank">
|
||||
Powered by <strong>ReDoc</strong>
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
|
@ -1,362 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-moz-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-ms-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-o-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-smoothing: antialiased;
|
||||
-webkit-osx-font-smoothing: grayscale;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
|
||||
-ms-text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
|
||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
|
||||
text-rendering: optimizeSpeed !important;
|
||||
font-smooth: always;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.redoc-wrap {
|
||||
z-index: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-family: $base-font, $base-font-family;
|
||||
font-size: $em-size;
|
||||
line-height: $base-line-height;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.menu-content {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
side-menu {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[sticky-sidebar] {
|
||||
width: $side-bar-width;
|
||||
background-color: $side-bar-bg-color;
|
||||
overflow-x: hidden;
|
||||
transform: translateZ(0);
|
||||
z-index: 75;
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
width: 100%;
|
||||
bottom: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.api-content {
|
||||
margin-left: $side-bar-width;
|
||||
z-index: 50;
|
||||
position: relative;
|
||||
// height: 100vh;
|
||||
// overflow: scroll;
|
||||
top: 0;
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
padding-top: 3em;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: $side-bar-width;
|
||||
z-index: 1;
|
||||
|
||||
&-actual {
|
||||
background: $samples-panel-bg-color;
|
||||
left: 100% - $samples-panel-width;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media (max-width: $right-panel-squash-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.redoc-error {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: $red;
|
||||
> h2 {
|
||||
color: $red;
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.redoc-error-details {
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* global menu items styles (search results + menu) */
|
||||
:host /deep/ {
|
||||
.menu-item-header > span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.menu-item-header > .operation-type + .menu-item-title {
|
||||
width: calc(100% - 32px); // 32 = 26px image width + 6px margin left
|
||||
}
|
||||
|
||||
.menu-item-header > .operation-type {
|
||||
width: 26px;
|
||||
display: inline-block;
|
||||
height: 13px;
|
||||
background-color: #333;
|
||||
border-radius: 3px;
|
||||
vertical-align: top;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAACgCAMAAADZ0KclAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAZQTFRF////////VXz1bAAAAAJ0Uk5T/wDltzBKAAAA80lEQVR42uSWSwLCIAxEX+5/aa2QZBJw5UIt9QMdRqSPEAAw/TyvqzZf150NzdXL49qreXwXjeqz9bqN1tgJl/KLyaVrrL7K7gx+1vlNMqy+helOO4rfBGYZiEkq1ubQ3DeKvc97Et+d+e01vIZlLZZqb1WNJFd8ZKYsmv4Hh3H2fDgjMUI5WSExjiEZs7rEZ5T+/jQn9lhgsw53j/e9MQtxqPsbZY54M5fNl/MY/f1s7NbRSkYlYjc0KPsWMrmhIU9933ywxDiSE+upYNH8TdusUotllNvcAUzfnE/NC4OSYyklQhpdl9E4Tw0Cm4/G9xBgAO7VCkjWLOMfAAAAAElFTkSuQmCC');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 6px 4px;
|
||||
text-indent: -9000px;
|
||||
margin-right: 6px;
|
||||
margin-top: 2px;
|
||||
|
||||
&.get {
|
||||
background-position: 8px -12px;
|
||||
background-color: $get-color;
|
||||
}
|
||||
|
||||
&.post {
|
||||
background-position: 6px 4px;
|
||||
background-color: $post-color;
|
||||
}
|
||||
|
||||
&.put {
|
||||
background-position: 8px -28px;
|
||||
background-color: $put-color;
|
||||
}
|
||||
|
||||
&.options {
|
||||
background-position: 4px -148px;
|
||||
background-color: $options-color;
|
||||
}
|
||||
|
||||
&.patch {
|
||||
background-position: 4px -114px;
|
||||
background-color: $patch-color;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
background-position: 4px -44px;
|
||||
background-color: $delete-color;
|
||||
}
|
||||
|
||||
&.basic {
|
||||
background-position: 5px -79px;
|
||||
background-color: $basic-color;
|
||||
}
|
||||
|
||||
&.link {
|
||||
background-position: 4px -131px;
|
||||
background-color: $link-color;
|
||||
}
|
||||
|
||||
&.head {
|
||||
background-position: 6px -102px;
|
||||
background-color: $head-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* global redoc styles */
|
||||
|
||||
@for $index from 1 through 5 {
|
||||
:host /deep/ h#{$index} {
|
||||
margin-top: 0;
|
||||
font-family: $headers-font, $headers-font-family;
|
||||
color: $secondary-color;
|
||||
font-weight: $headers-font-weight;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
:host /deep/ {
|
||||
h1 {
|
||||
font-size: $h1;
|
||||
color: $headers-color;
|
||||
}
|
||||
h2 {
|
||||
font-size: $h2;
|
||||
}
|
||||
h3 {
|
||||
font-size: $h3;
|
||||
}
|
||||
h4 {
|
||||
font-size: $h4;
|
||||
}
|
||||
h5 {
|
||||
font-size: $h5;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-family: $base-font, $base-font-family;
|
||||
font-weight: $base-font-weight;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
line-height: $base-line-height;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
p > code {
|
||||
color: $red;
|
||||
border: 1px solid rgba(38, 50, 56, 0.1);
|
||||
}
|
||||
|
||||
.hint--inversed {
|
||||
&:before {
|
||||
border-top-color: #fff;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: #fff;
|
||||
color: #383838;
|
||||
}
|
||||
}
|
||||
|
||||
@import '../../shared/styles/share-link';
|
||||
}
|
||||
|
||||
footer {
|
||||
position: relative;
|
||||
text-align: right;
|
||||
padding: 10px $section-spacing;
|
||||
font-size: 15px;
|
||||
margin-top: -35px;
|
||||
color: white;
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
strong {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* markdown elements */
|
||||
|
||||
:host /deep/ .redoc-markdown-block {
|
||||
pre {
|
||||
font-family: Courier, monospace;
|
||||
white-space: pre-wrap;
|
||||
background-color: #263238;
|
||||
color: white;
|
||||
padding: 12px 14px 15px 14px;
|
||||
overflow-x: auto;
|
||||
line-height: normal;
|
||||
border-radius: $border-radius;
|
||||
border: 1px solid rgba(38, 50, 56, 0.1);
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Courier, monospace;
|
||||
background-color: rgba(38, 50, 56, 0.04);
|
||||
padding: 0.1em 0.2em 0.2em;
|
||||
font-size: 1em;
|
||||
border-radius: $border-radius;
|
||||
color: $red;
|
||||
border: 1px solid rgba(38, 50, 56, 0.1);
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
border-left: 4px solid #ddd;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 2em;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
font-family: $base-font, $base-font-family;
|
||||
font-weight: $base-font-weight;
|
||||
line-height: $base-line-height;
|
||||
> li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table th {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import {
|
||||
inject,
|
||||
async
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Redoc } from './redoc';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import { OptionsService } from '../../services/index';
|
||||
|
||||
let optsMgr:OptionsService;
|
||||
|
||||
describe('Redoc components', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({ declarations: [ TestAppComponent ] });
|
||||
});
|
||||
describe('Redoc Component', () => {
|
||||
let builder;
|
||||
let specMgr;
|
||||
|
||||
beforeEach(async(inject([SpecManager, OptionsService],
|
||||
( _specMgr, _optsMgr) => {
|
||||
optsMgr = _optsMgr;
|
||||
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load('/tests/schemas/extended-petstore.yml').then(done, done.fail);
|
||||
})
|
||||
|
||||
it('should init component', () => {
|
||||
let fixture = TestBed.createComponent(TestAppComponent);
|
||||
let component = getChildDebugElement(fixture.debugElement, 'redoc').componentInstance;
|
||||
expect(component).not.toBeNull();
|
||||
fixture.destroy();
|
||||
});
|
||||
|
||||
it('should init components tree without errors', () => {
|
||||
let fixture = TestBed.createComponent(TestAppComponent);
|
||||
(() => fixture.detectChanges()).should.not.throw();
|
||||
fixture.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/** Test component that contains a Redoc. */
|
||||
@Component({
|
||||
selector: 'test-app',
|
||||
template:
|
||||
`<redoc disable-lazy-schemas></redoc>`
|
||||
})
|
||||
class TestAppComponent {
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { ElementRef,
|
||||
ChangeDetectorRef,
|
||||
Input,
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
HostBinding
|
||||
} from '@angular/core';
|
||||
|
||||
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
|
||||
import { BaseComponent } from '../base';
|
||||
|
||||
import * as detectScollParent from 'scrollparent';
|
||||
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
import {
|
||||
SearchService,
|
||||
OptionsService,
|
||||
Options,
|
||||
Hash,
|
||||
AppStateService,
|
||||
SchemaHelper,
|
||||
MenuService,
|
||||
Marker
|
||||
} from '../../services/';
|
||||
import { LazyTasksService } from '../../shared/components/LazyFor/lazy-for';
|
||||
|
||||
function getPreOptions() {
|
||||
return Redoc._preOptions || {};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'redoc',
|
||||
templateUrl: './redoc.html',
|
||||
styleUrls: ['./redoc.css'],
|
||||
providers: [
|
||||
SpecManager,
|
||||
MenuService,
|
||||
SearchService,
|
||||
LazyTasksService,
|
||||
Marker
|
||||
]
|
||||
//changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class Redoc extends BaseComponent implements OnInit {
|
||||
static _preOptions: any = {};
|
||||
|
||||
error: any;
|
||||
specLoaded: boolean;
|
||||
options: Options;
|
||||
|
||||
loadingProgress: number;
|
||||
|
||||
@Input() specUrl: string;
|
||||
@HostBinding('class.loading') specLoading: boolean = false;
|
||||
@HostBinding('class.loading-remove') specLoadingRemove: boolean = false;
|
||||
|
||||
private element: HTMLElement;
|
||||
private $parent: Element;
|
||||
private $refElem: Element;
|
||||
|
||||
constructor(
|
||||
specMgr: SpecManager,
|
||||
optionsMgr: OptionsService,
|
||||
elementRef: ElementRef,
|
||||
private changeDetector: ChangeDetectorRef,
|
||||
private appState: AppStateService,
|
||||
private lazyTasksService: LazyTasksService,
|
||||
private hash: Hash
|
||||
) {
|
||||
super(specMgr);
|
||||
SchemaHelper.setSpecManager(specMgr);
|
||||
// merge options passed before init
|
||||
optionsMgr.options = getPreOptions();
|
||||
|
||||
this.element = elementRef.nativeElement;
|
||||
this.$parent = this.element.parentNode as Element;
|
||||
this.$refElem = this.element.nextElementSibling;
|
||||
|
||||
//parse options (top level component doesn't support inputs)
|
||||
optionsMgr.parseOptions( this.element );
|
||||
let scrollParent = detectScollParent( this.element );
|
||||
if (scrollParent === (document.scrollingElement || document.documentElement)) scrollParent = window;
|
||||
optionsMgr.options.$scrollParent = scrollParent;
|
||||
this.options = optionsMgr.options;
|
||||
this.lazyTasksService.allSync = !this.options.lazyRendering;
|
||||
}
|
||||
|
||||
hideLoadingAnimation() {
|
||||
if (this.options.hideLoading) {
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
this.specLoadingRemove = true;
|
||||
setTimeout(() => {
|
||||
this.specLoadingRemove = false;
|
||||
this.specLoading = false;
|
||||
}, 400);
|
||||
});
|
||||
}
|
||||
|
||||
showLoadingAnimation() {
|
||||
if (this.options.hideLoading) {
|
||||
return
|
||||
}
|
||||
this.specLoading = true;
|
||||
this.specLoadingRemove = false;
|
||||
}
|
||||
|
||||
load() {
|
||||
// bunlde spec directly if passsed or load by URL
|
||||
this.specMgr.load(this.options.spec || this.options.specUrl).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
this.appState.loading.subscribe(loading => {
|
||||
if (loading) {
|
||||
this.showLoadingAnimation();
|
||||
} else {
|
||||
this.hideLoadingAnimation();
|
||||
}
|
||||
});
|
||||
|
||||
this.specMgr.spec.subscribe((spec) => {
|
||||
if (!spec) {
|
||||
this.appState.startLoading();
|
||||
} else {
|
||||
this.specLoaded = true;
|
||||
this.changeDetector.markForCheck();
|
||||
this.changeDetector.detectChanges();
|
||||
setTimeout(() => {
|
||||
this.hash.start();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.lazyTasksService.loadProgress.subscribe(progress => this.loadingProgress = progress)
|
||||
this.appState.error.subscribe(_err => {
|
||||
if (!_err) return;
|
||||
|
||||
this.appState.stopLoading();
|
||||
|
||||
if (this.loadingProgress === 100) return;
|
||||
this.error = _err;
|
||||
this.changeDetector.markForCheck();
|
||||
});
|
||||
|
||||
if (this.specUrl) {
|
||||
this.options.specUrl = this.specUrl;
|
||||
}
|
||||
this.load();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
let $clone = this.element.cloneNode();
|
||||
this.$parent.insertBefore($clone, this.$refElem);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
<header *ngIf="schemaPointer || samples.length"> Request samples </header>
|
||||
<schema-sample *ngIf="schemaPointer && !samples.length" [skipReadOnly]="true" [pointer]="schemaPointer"> </schema-sample>
|
||||
<tabs *ngIf="samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
|
||||
<tab *ngIf="schemaPointer" tabTitle="JSON">
|
||||
<schema-sample [pointer]="schemaPointer" [skipReadOnly]="true"> </schema-sample>
|
||||
</tab>
|
||||
<tab *ngFor="let sample of samples" [tabTitle]="sample.lang">
|
||||
<div class="code-sample">
|
||||
<div class="action-buttons">
|
||||
<span copy-button [copyText]="sample.source" class="hint--top-left hint--inversed"><a>Copy</a></span>
|
||||
</div>
|
||||
<pre [innerHtml]="sample.source | prism:sample.lang"></pre>
|
||||
</div>
|
||||
</tab>
|
||||
</tabs>
|
|
@ -1,81 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
z-index: 3;
|
||||
position: relative;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
padding-right: 10px;
|
||||
text-align: right;
|
||||
margin-top: -1em;
|
||||
|
||||
> span > a {
|
||||
padding: 2px 10px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($black, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-sample:hover > .action-buttons {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
header {
|
||||
font-family: $headers-font;
|
||||
font-size: $h5;
|
||||
text-transform: uppercase;
|
||||
margin: 0;
|
||||
color: $sample-panel-headers-color;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
:host /deep/ > tabs > ul li {
|
||||
font-family: $headers-font;
|
||||
font-size: .9em;
|
||||
border-radius: $border-radius;
|
||||
margin: 2px 0;
|
||||
padding: 3px 10px 2px 10px;
|
||||
line-height: 16px;
|
||||
color: $sample-panel-headers-color;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(white, .1);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #ffffff;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
:host /deep/ tabs ul {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.code-sample pre {
|
||||
overflow-x: auto;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
margin-top: 0;
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
background-color: #222d32;
|
||||
margin-bottom: 36px;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component, ViewChildren, QueryList, Input,
|
||||
ChangeDetectionStrategy, OnInit, HostBinding, ElementRef, NgZone } from '@angular/core';
|
||||
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { BaseComponent, SpecManager } from '../base';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import { Tabs } from '../../shared/components/index';
|
||||
import { AppStateService, ScrollService } from '../../services/index';
|
||||
|
||||
@Component({
|
||||
selector: 'request-samples',
|
||||
templateUrl: './request-samples.html',
|
||||
styleUrls: ['./request-samples.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class RequestSamples extends BaseComponent implements OnInit {
|
||||
@Input() pointer:string;
|
||||
@Input() schemaPointer:string;
|
||||
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
|
||||
@HostBinding('attr.hidden') hidden;
|
||||
|
||||
childTabs: Tabs;
|
||||
selectedLang: Subject<any>;
|
||||
samples: Array<any>;
|
||||
|
||||
constructor(
|
||||
specMgr:SpecManager,
|
||||
public appState:AppStateService,
|
||||
private scrollService: ScrollService,
|
||||
private el: ElementRef,
|
||||
private zone: NgZone
|
||||
) {
|
||||
super(specMgr);
|
||||
|
||||
this.selectedLang = this.appState.samplesLanguage;
|
||||
}
|
||||
|
||||
changeLangNotify(lang) {
|
||||
let relativeScrollPos = this.scrollService.relativeScrollPos(this.el.nativeElement);
|
||||
this.selectedLang.next(lang);
|
||||
// do scroll in the end of VM turn to have it seamless
|
||||
let subscription = this.zone.onMicrotaskEmpty.subscribe(() => {
|
||||
this.scrollService.scrollTo(this.el.nativeElement, relativeScrollPos);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
this.schemaPointer = this.schemaPointer ? JsonPointer.join(this.schemaPointer, 'schema') : null;
|
||||
this.samples = this.componentSchema['x-code-samples'] || [];
|
||||
if (!this.schemaPointer && !this.samples.length) this.hidden = true;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preinit();
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||
<zippy *ngFor="let response of responses;trackBy:trackByCode" [title]="response.code + ' ' + response.description | marked"
|
||||
[type]="response.type" [(open)]="response.expanded" [empty]="response.empty" (openChange)="lazySchema.load()">
|
||||
<div *ngIf="response.headers" class="response-headers">
|
||||
<header>
|
||||
Headers
|
||||
</header>
|
||||
<div class="header" *ngFor="let header of response.headers">
|
||||
<div class="header-name"> {{header.name}} </div>
|
||||
<div class="header-type {{header.type}}"> {{header._displayType}} {{header._displayFormat}}
|
||||
<span class="header-range" *ngIf="header._range"> {{header._range}} </span>
|
||||
</div>
|
||||
<div *ngIf="header.default" class="header-default"> Default: {{header.default}} </div>
|
||||
<div *ngIf="header.enum" class="header-enum">
|
||||
<span *ngFor="let enumItem of header.enum" class="enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
|
||||
</div>
|
||||
<div class="header-description" [innerHtml]="header.description | marked"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<header *ngIf="response.schema">
|
||||
Response Schema
|
||||
</header>
|
||||
<json-schema-lazy [auto]="response.expanded" #lazySchema
|
||||
pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
|
@ -1,61 +0,0 @@
|
|||
@import '../../shared/styles/variables';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.responses-list-header {
|
||||
font-size: 18px;
|
||||
padding: 0.2em 0;
|
||||
margin: 3em 0 1.1em;
|
||||
color: #253137;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host .zippy-title {
|
||||
font-family: $headers-font, $headers-font-family;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-type {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
header {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-range {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
border-radius: $border-radius;
|
||||
background-color: rgba($primary-color, 0.1);
|
||||
padding: 0 4px;
|
||||
color: rgba($primary-color, 0.7);
|
||||
}
|
||||
|
||||
.header-type.array::before {
|
||||
content: $array-text;
|
||||
color: $black;
|
||||
font-weight: $base-font-weight;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
inject,
|
||||
async,
|
||||
TestBed,
|
||||
ComponentFixture
|
||||
} from '@angular/core/testing';
|
||||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
|
||||
|
||||
import { ResponsesList } from './responses-list';
|
||||
import { SpecManager } from '../../utils/spec-manager';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
|
||||
describe('ResponsesList Component', () => {
|
||||
let builder;
|
||||
let component: ResponsesList;
|
||||
let fixture: ComponentFixture<ResponsesList>
|
||||
let specMgr;
|
||||
|
||||
beforeEach(async(inject([SpecManager], (_specMgr) => {
|
||||
specMgr = _specMgr;
|
||||
})));
|
||||
|
||||
beforeEach(done => {
|
||||
specMgr.load('/tests/schemas/responses-list-component.json').then(done, done.fail);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ResponsesList);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should instantiate without errors', () => {
|
||||
should.exist(component);
|
||||
});
|
||||
|
||||
it('should init repsonses list', () => {
|
||||
component.pointer = '#/paths/~1test1/get/responses';
|
||||
fixture.detectChanges();
|
||||
should.exist(component.responses);
|
||||
component.responses.should.be.lengthOf(2);
|
||||
});
|
||||
|
||||
it('should not overwrite codes for shared schemas', () => {
|
||||
component.pointer = '#/paths/~1test1/get/responses';
|
||||
fixture.detectChanges();
|
||||
let resp1 = component.responses[0];
|
||||
let resp2 = component.responses[1];
|
||||
resp1.code.should.not.be.equal(resp2.code);
|
||||
});
|
||||
|
||||
it('should set type of default as error if other 200-399 response is defined', () => {
|
||||
component.pointer = '#/paths/~1test2/get/responses';
|
||||
fixture.detectChanges();
|
||||
let resp1 = component.responses[1];
|
||||
resp1.type.should.be.equal('error');
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user