mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 14:14:56 +03:00
Merge branch 'master' into external-docs
This commit is contained in:
commit
72d120f422
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -1,3 +1,48 @@
|
||||||
|
<a name="2.0.0-alpha.38"></a>
|
||||||
|
# [2.0.0-alpha.38](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.37...v2.0.0-alpha.38) (2018-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* addd indent to array schema internals ([865f3ce](https://github.com/Rebilly/ReDoc/commit/865f3ce))
|
||||||
|
* fix oneOf/anyOf titles ([39b930d](https://github.com/Rebilly/ReDoc/commit/39b930d)), closes [#618](https://github.com/Rebilly/ReDoc/issues/618) [#621](https://github.com/Rebilly/ReDoc/issues/621)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.0.0-alpha.37"></a>
|
||||||
|
# [2.0.0-alpha.37](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.36...v2.0.0-alpha.37) (2018-08-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not inherit title in allOf ([720e282](https://github.com/Rebilly/ReDoc/commit/720e282)), closes [#601](https://github.com/Rebilly/ReDoc/issues/601)
|
||||||
|
* fix crash on empty media object ([fb21212](https://github.com/Rebilly/ReDoc/commit/fb21212)), closes [#608](https://github.com/Rebilly/ReDoc/issues/608)
|
||||||
|
* make http badges font-based instead of inline png ([5d84bd4](https://github.com/Rebilly/ReDoc/commit/5d84bd4))
|
||||||
|
* use correct parent section for security definition ([f903406](https://github.com/Rebilly/ReDoc/commit/f903406))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.0.0-alpha.36"></a>
|
||||||
|
# [2.0.0-alpha.36](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.35...v2.0.0-alpha.36) (2018-08-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* broken rendering of code blocks with language in markdown ([8218a26](https://github.com/Rebilly/ReDoc/commit/8218a26))
|
||||||
|
* broken rendering of headings with regexp characters ([e660517](https://github.com/Rebilly/ReDoc/commit/e660517))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="2.0.0-alpha.35"></a>
|
||||||
|
# [2.0.0-alpha.35](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.34...v2.0.0-alpha.35) (2018-08-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* crash on any backticks code block without lang specified ([58ae668](https://github.com/Rebilly/ReDoc/commit/58ae668))
|
||||||
|
* fix auth requirements font size ([d13fe13](https://github.com/Rebilly/ReDoc/commit/d13fe13))
|
||||||
|
|
||||||
|
|
||||||
<a name="2.0.0-alpha.34"></a>
|
<a name="2.0.0-alpha.34"></a>
|
||||||
# [2.0.0-alpha.34](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.33...v2.0.0-alpha.34) (2018-08-08)
|
# [2.0.0-alpha.34](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.33...v2.0.0-alpha.34) (2018-08-08)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ Two following commands are available:
|
||||||
|
|
||||||
Some examples:
|
Some examples:
|
||||||
|
|
||||||
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.main=orange`
|
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
||||||
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars`
|
- 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/master/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
- Bundle using custom template (check [default template](https://github.com/Rebilly/ReDoc/blob/master/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.6.1",
|
"version": "0.6.2",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
|
@ -12,11 +12,11 @@
|
||||||
"isarray": "^2.0.4",
|
"isarray": "^2.0.4",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mobx": "^4.2.0",
|
"mobx": "^4.2.0",
|
||||||
"react": "^16.3.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.3.2",
|
"react-dom": "^16.4.2",
|
||||||
"redoc": "^2.0.0-alpha.29",
|
"redoc": "^2.0.0-alpha.37",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.3",
|
||||||
"yargs": "^11.0.0"
|
"yargs": "^12.0.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ci-publish": "ci-publish"
|
"ci-publish": "ci-publish"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/handlebars": "^4.0.36",
|
"@types/handlebars": "^4.0.39",
|
||||||
"@types/mkdirp": "^0.5.2",
|
"@types/mkdirp": "^0.5.2",
|
||||||
"ci-publish": "^1.3.1"
|
"ci-publish": "^1.3.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,8 +29,11 @@ const tsLoader = env => ({
|
||||||
const babelLoader = mode => ({
|
const babelLoader = mode => ({
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
|
generatorOpts: {
|
||||||
|
decoratorsBeforeExport: true,
|
||||||
|
},
|
||||||
plugins: compact([
|
plugins: compact([
|
||||||
'@babel/plugin-syntax-typescript',
|
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||||
'@babel/plugin-syntax-jsx',
|
'@babel/plugin-syntax-jsx',
|
||||||
mode !== 'production' ? 'react-hot-loader/babel' : undefined,
|
mode !== 'production' ? 'react-hot-loader/babel' : undefined,
|
||||||
|
@ -113,6 +116,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
instance: 'ts2js-transpiler-only',
|
instance: 'ts2js-transpiler-only',
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
allowJs: true,
|
allowJs: true,
|
||||||
|
declaration: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,13 +12,14 @@ describe('Menu', () => {
|
||||||
it('should sync active menu items while scroll', () => {
|
it('should sync active menu items while scroll', () => {
|
||||||
cy.contains('h1', 'Introduction')
|
cy.contains('h1', 'Introduction')
|
||||||
.scrollIntoView()
|
.scrollIntoView()
|
||||||
.get('[role=menuitem].active:not(.-depth0)')
|
.get('[role=menuitem].active')
|
||||||
.should('have.text', 'Introduction');
|
.should('have.text', 'Introduction');
|
||||||
|
|
||||||
cy.contains('h2', 'Add a new pet to the store')
|
cy.contains('h2', 'Add a new pet to the store')
|
||||||
.scrollIntoView()
|
.scrollIntoView()
|
||||||
.get('[role=menuitem].active:not(.-depth0)')
|
.wait(100)
|
||||||
.should('have.length', 2)
|
.get('[role=menuitem].active')
|
||||||
|
.children()
|
||||||
.last()
|
.last()
|
||||||
.should('have.text', 'Add a new pet to the store')
|
.should('have.text', 'Add a new pet to the store')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
|
|
98
package.json
98
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-alpha.34",
|
"version": "2.0.0-alpha.38",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -51,71 +51,71 @@
|
||||||
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause' --summary"
|
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause' --summary"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.0.0-beta.47",
|
"@babel/core": "7.0.0-rc.2",
|
||||||
"@babel/plugin-syntax-decorators": "7.0.0-beta.47",
|
"@babel/plugin-syntax-decorators": "7.0.0-rc.2",
|
||||||
"@babel/plugin-syntax-jsx": "7.0.0-beta.47",
|
"@babel/plugin-syntax-jsx": "7.0.0-rc.2",
|
||||||
"@babel/plugin-syntax-typescript": "7.0.0-beta.47",
|
"@babel/plugin-syntax-typescript": "7.0.0-rc.2",
|
||||||
"@cypress/webpack-preprocessor": "2.0.1",
|
"@cypress/webpack-preprocessor": "2.0.1",
|
||||||
"@types/chai": "4.1.4",
|
"@types/chai": "4.1.4",
|
||||||
"@types/dompurify": "^0.0.31",
|
"@types/dompurify": "^0.0.31",
|
||||||
"@types/enzyme": "^3.1.11",
|
"@types/enzyme": "^3.1.13",
|
||||||
"@types/enzyme-to-json": "^1.5.0",
|
"@types/enzyme-to-json": "^1.5.2",
|
||||||
"@types/jest": "^23.1.6",
|
"@types/jest": "^23.3.1",
|
||||||
"@types/json-pointer": "^1.0.30",
|
"@types/json-pointer": "^1.0.30",
|
||||||
"@types/lodash": "^4.14.112",
|
"@types/lodash": "^4.14.116",
|
||||||
"@types/lunr": "^2.1.5",
|
"@types/lunr": "^2.1.6",
|
||||||
"@types/mark.js": "^8.11.1",
|
"@types/mark.js": "^8.11.1",
|
||||||
"@types/marked": "^0.4.0",
|
"@types/marked": "^0.4.1",
|
||||||
"@types/prismjs": "^1.6.4",
|
"@types/prismjs": "^1.6.4",
|
||||||
"@types/prop-types": "^15.5.3",
|
"@types/prop-types": "^15.5.5",
|
||||||
"@types/react": "^16.4.6",
|
"@types/react": "^16.4.11",
|
||||||
"@types/react-dom": "^16.0.6",
|
"@types/react-dom": "^16.0.7",
|
||||||
"@types/react-hot-loader": "^4.1.0",
|
"@types/react-hot-loader": "^4.1.0",
|
||||||
"@types/react-tabs": "^1.0.2",
|
"@types/react-tabs": "^1.0.5",
|
||||||
"@types/tapable": "1.0.4",
|
"@types/tapable": "1.0.4",
|
||||||
"@types/webpack": "^4.4.6",
|
"@types/webpack": "^4.4.11",
|
||||||
"@types/webpack-env": "^1.13.0",
|
"@types/webpack-env": "^1.13.0",
|
||||||
"@types/yargs": "^11.1.0",
|
"@types/yargs": "^11.1.1",
|
||||||
"babel-loader": "8.0.0-beta.2",
|
"babel-loader": "8.0.0-beta.2",
|
||||||
"babel-plugin-styled-components": "^1.5.1",
|
"babel-plugin-styled-components": "^1.5.1",
|
||||||
"beautify-benchmark": "^0.2.4",
|
"beautify-benchmark": "^0.2.4",
|
||||||
"bundlesize": "^0.17.0",
|
"bundlesize": "^0.17.0",
|
||||||
"conventional-changelog-cli": "^2.0.1",
|
"conventional-changelog-cli": "^2.0.5",
|
||||||
"copy-webpack-plugin": "^4.5.2",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"core-js": "^2.5.7",
|
"core-js": "^2.5.7",
|
||||||
"coveralls": "^3.0.2",
|
"coveralls": "^3.0.2",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"cypress": "~3.0.2",
|
"cypress": "~3.1.0",
|
||||||
"deploy-to-gh-pages": "^1.3.6",
|
"deploy-to-gh-pages": "^1.3.6",
|
||||||
"enzyme": "^3.1.1",
|
"enzyme": "^3.4.4",
|
||||||
"enzyme-adapter-react-16": "^1.0.4",
|
"enzyme-adapter-react-16": "^1.2.0",
|
||||||
"enzyme-to-json": "^3.3.4",
|
"enzyme-to-json": "^3.3.4",
|
||||||
"fork-ts-checker-webpack-plugin": "^0.4.3",
|
"fork-ts-checker-webpack-plugin": "0.4.3",
|
||||||
"html-webpack-plugin": "^3.1.0",
|
"html-webpack-plugin": "^3.1.0",
|
||||||
"jest": "^23.4.1",
|
"jest": "^23.5.0",
|
||||||
"license-checker": "^20.1.0",
|
"license-checker": "^20.2.0",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"mobx": "^4.3.1",
|
"mobx": "^4.3.1",
|
||||||
"prettier": "^1.13.7",
|
"prettier": "^1.14.2",
|
||||||
"prettier-eslint": "^8.8.2",
|
"prettier-eslint": "^8.8.2",
|
||||||
"puppeteer": "^1.6.0",
|
"puppeteer": "^1.7.0",
|
||||||
"raf": "^3.4.0",
|
"raf": "^3.4.0",
|
||||||
"react": "^16.4.1",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.4.2",
|
||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"shelljs": "^0.8.1",
|
"shelljs": "^0.8.1",
|
||||||
"source-map-loader": "^0.2.1",
|
"source-map-loader": "^0.2.4",
|
||||||
"style-loader": "^0.21.0",
|
"style-loader": "^0.22.1",
|
||||||
"swagger2openapi": "^3.2.8",
|
"swagger2openapi": "^3.2.8",
|
||||||
"ts-jest": "^23.0.0",
|
"ts-jest": "23.0.1",
|
||||||
"ts-loader": "4.4.2",
|
"ts-loader": "4.5.0",
|
||||||
"ts-node": "^7.0.0",
|
"ts-node": "^7.0.1",
|
||||||
"tslint": "^5.7.0",
|
"tslint": "^5.11.0",
|
||||||
"tslint-react": "^3.4.0",
|
"tslint-react": "^3.4.0",
|
||||||
"typescript": "^3.0.0-dev.20180712",
|
"typescript": "^3.0.1",
|
||||||
"webpack": "^4.16.1",
|
"webpack": "^4.17.1",
|
||||||
"webpack-cli": "^3.0.8",
|
"webpack-cli": "^3.1.0",
|
||||||
"webpack-dev-server": "^3.1.1",
|
"webpack-dev-server": "^3.1.5",
|
||||||
"webpack-node-externals": "^1.6.0",
|
"webpack-node-externals": "^1.6.0",
|
||||||
"workerize-loader": "^1.0.3",
|
"workerize-loader": "^1.0.3",
|
||||||
"yaml-js": "^0.2.3"
|
"yaml-js": "^0.2.3"
|
||||||
|
@ -128,26 +128,26 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"dompurify": "^1.0.6",
|
"dompurify": "^1.0.7",
|
||||||
"eventemitter3": "^3.0.0",
|
"eventemitter3": "^3.0.0",
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^5.1.1",
|
"json-schema-ref-parser": "^5.1.2",
|
||||||
"lunr": "^2.3.0",
|
"lunr": "^2.3.2",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "0.3.18",
|
"marked": "0.3.18",
|
||||||
"memoize-one": "^4.0.0",
|
"memoize-one": "^4.0.0",
|
||||||
"mobx-react": "^5.2.3",
|
"mobx-react": "^5.2.5",
|
||||||
"openapi-sampler": "1.0.0-beta.13",
|
"openapi-sampler": "1.0.0-beta.14",
|
||||||
"perfect-scrollbar": "^1.4.0",
|
"perfect-scrollbar": "^1.4.0",
|
||||||
"polished": "^1.9.3",
|
"polished": "^2.0.2",
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.15.0",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react-dropdown": "^1.3.0",
|
"react-dropdown": "^1.6.2",
|
||||||
"react-hot-loader": "^4.3.3",
|
"react-hot-loader": "^4.3.5",
|
||||||
"react-tabs": "^2.0.0",
|
"react-tabs": "^2.0.0",
|
||||||
"slugify": "^1.2.1",
|
"slugify": "^1.3.1",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
"styled-components": "^3.3.3",
|
"styled-components": "^3.4.5",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.9.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.tsx?$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
|
"^.+\\.tsx?$": "ts-jest"
|
||||||
},
|
},
|
||||||
"setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.ts",
|
"setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.ts",
|
||||||
"testPathIgnorePatterns": [
|
"testPathIgnorePatterns": [
|
||||||
|
|
|
@ -7,8 +7,8 @@ export const PrismDiv = styled.div`
|
||||||
|
|
||||||
code[class*='language-'],
|
code[class*='language-'],
|
||||||
pre[class*='language-'] {
|
pre[class*='language-'] {
|
||||||
color: white;
|
/* color: white;
|
||||||
background: none;
|
background: none; */
|
||||||
text-shadow: 0 -0.1em 0.2em black;
|
text-shadow: 0 -0.1em 0.2em black;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
|
|
@ -93,7 +93,7 @@ export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
|
||||||
}
|
}
|
||||||
` as StyledComponentClass<any, DropdownProps>;
|
` as StyledComponentClass<any, DropdownProps>;
|
||||||
|
|
||||||
export const SimpleDropdown = StyledDropdown.extend`
|
export const SimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
|
|
|
@ -54,11 +54,11 @@ export const PropertyCell = styled.td`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PropertyCellWithInner = PropertyCell.extend`
|
export const PropertyCellWithInner = styled(PropertyCell)`
|
||||||
padding: 0;
|
padding: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PropertyNameCell = withProps<{ kind?: string }>(PropertyCell.extend)`
|
export const PropertyNameCell = withProps<{ kind?: string }>(styled(PropertyCell))`
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import styled, { extensionsHook } from '../styled-components';
|
||||||
import { PropertyNameCell } from './fields-layout';
|
import { PropertyNameCell } from './fields-layout';
|
||||||
import { ShelfIcon } from './shelfs';
|
import { ShelfIcon } from './shelfs';
|
||||||
|
|
||||||
export const ClickablePropertyNameCell = PropertyNameCell.extend`
|
export const ClickablePropertyNameCell = styled(PropertyNameCell)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
${ShelfIcon} {
|
${ShelfIcon} {
|
||||||
|
@ -22,21 +22,21 @@ export const FieldLabel = styled.span`
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypePrefix = FieldLabel.extend`
|
export const TypePrefix = styled(FieldLabel)`
|
||||||
color: ${props => transparentize(0.2, props.theme.schema.typeNameColor)};
|
color: ${props => transparentize(0.2, props.theme.schema.typeNameColor)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeName = FieldLabel.extend`
|
export const TypeName = styled(FieldLabel)`
|
||||||
color: ${props => props.theme.schema.typeNameColor};
|
color: ${props => props.theme.schema.typeNameColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeTitle = FieldLabel.extend`
|
export const TypeTitle = styled(FieldLabel)`
|
||||||
color: ${props => props.theme.schema.typeTitleColor};
|
color: ${props => props.theme.schema.typeTitleColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeFormat = TypeName;
|
export const TypeFormat = TypeName;
|
||||||
|
|
||||||
export const RequiredLabel = FieldLabel.withComponent('div').extend`
|
export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
|
||||||
color: ${props => props.theme.schema.requireLabelColor};
|
color: ${props => props.theme.schema.requireLabelColor};
|
||||||
font-size: ${props => props.theme.schema.labelsTextSize};
|
font-size: ${props => props.theme.schema.labelsTextSize};
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
@ -44,17 +44,17 @@ export const RequiredLabel = FieldLabel.withComponent('div').extend`
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecursiveLabel = FieldLabel.extend`
|
export const RecursiveLabel = styled(FieldLabel)`
|
||||||
color: ${({ theme }) => theme.colors.warning.main};
|
color: ${({ theme }) => theme.colors.warning.main};
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const NullableLabel = FieldLabel.extend`
|
export const NullableLabel = styled(FieldLabel)`
|
||||||
color: #3195a6;
|
color: #3195a6;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const PatternLabel = FieldLabel.extend`
|
export const PatternLabel = styled(FieldLabel)`
|
||||||
color: #3195a6;
|
color: #3195a6;
|
||||||
&::before,
|
&::before,
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -63,7 +63,7 @@ export const PatternLabel = FieldLabel.extend`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExampleValue = FieldLabel.extend`
|
export const ExampleValue = styled(FieldLabel)`
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
${({ theme }) => `
|
${({ theme }) => `
|
||||||
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
||||||
|
@ -79,7 +79,7 @@ export const ExampleValue = FieldLabel.extend`
|
||||||
${extensionsHook('ExampleValue')};
|
${extensionsHook('ExampleValue')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ConstraintItem = FieldLabel.extend`
|
export const ConstraintItem = styled(FieldLabel)`
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
${({ theme }) => `
|
${({ theme }) => `
|
||||||
background-color: ${transparentize(0.95, theme.colors.primary.light)};
|
background-color: ${transparentize(0.95, theme.colors.primary.light)};
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { StoreConsumer } from '../components/StoreBuilder';
|
||||||
import styled, { css } from '../styled-components';
|
import styled, { css } from '../styled-components';
|
||||||
|
|
||||||
|
import { HistoryService } from '../services';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
export const linkifyMixin = className => css`
|
export const linkifyMixin = className => css`
|
||||||
${className} {
|
${className} {
|
||||||
|
@ -27,6 +32,42 @@ export const linkifyMixin = className => css`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ShareLink = styled.a`
|
const isModifiedEvent = event =>
|
||||||
|
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
|
||||||
|
|
||||||
|
export class Link extends React.Component<{ to: string; className?: string; children?: any }> {
|
||||||
|
navigate = (history: HistoryService, event) => {
|
||||||
|
if (
|
||||||
|
!event.defaultPrevented && // onClick prevented default
|
||||||
|
event.button === 0 && // ignore everything but left clicks
|
||||||
|
!isModifiedEvent(event) // ignore clicks with modifier keys
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
history.replace(this.props.to);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<StoreConsumer>
|
||||||
|
{store => (
|
||||||
|
<a
|
||||||
|
className={this.props.className}
|
||||||
|
href={store!.menu.history.linkForId(this.props.to)}
|
||||||
|
onClick={this.navigate.bind(this, store!.menu.history)}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</StoreConsumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const StyledShareLink = styled(Link)`
|
||||||
${linkifyMixin('&')};
|
${linkifyMixin('&')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export function ShareLink(props: { to: string }) {
|
||||||
|
return <StyledShareLink to={props.to} />;
|
||||||
|
}
|
|
@ -1,32 +1,58 @@
|
||||||
import styled, { media } from '../styled-components';
|
import { SECTION_ATTR } from '../services/MenuStore';
|
||||||
|
import styled, { media, withProps } from '../styled-components';
|
||||||
|
|
||||||
export const MiddlePanel = styled.div`
|
export const MiddlePanel = styled.div`
|
||||||
width: calc(100% - ${props => props.theme.rightPanel.width});
|
width: calc(100% - ${props => props.theme.rightPanel.width});
|
||||||
padding: ${props => props.theme.spacing.unit * 8}px;
|
padding: 0 ${props => props.theme.spacing.unit * 8}px;
|
||||||
|
|
||||||
${media.lessThan('medium')`
|
${media.lessThan('medium')`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const Section = withProps<{ underlined?: boolean }>(
|
||||||
|
styled.div.attrs({
|
||||||
|
[SECTION_ATTR]: props => props.id,
|
||||||
|
} as any),
|
||||||
|
)`
|
||||||
|
padding: ${props => props.theme.spacing.unit * 8}px 0;
|
||||||
|
|
||||||
|
${props =>
|
||||||
|
(props.underlined &&
|
||||||
|
`
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:not(:last-of-type):after {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
`) ||
|
||||||
|
''}
|
||||||
|
`;
|
||||||
|
|
||||||
export const RightPanel = styled.div`
|
export const RightPanel = styled.div`
|
||||||
width: ${props => props.theme.rightPanel.width};
|
width: ${props => props.theme.rightPanel.width};
|
||||||
color: #fafbfc;
|
color: #fafbfc;
|
||||||
background-color: ${props => props.theme.rightPanel.backgroundColor};
|
background-color: ${props => props.theme.rightPanel.backgroundColor};
|
||||||
padding: ${props => props.theme.spacing.unit * 8}px;
|
padding: 0 ${props => props.theme.spacing.unit * 8}px;
|
||||||
|
|
||||||
${media.lessThan('medium')`
|
${media.lessThan('medium')`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DarkRightPanel = RightPanel.extend`
|
export const DarkRightPanel = styled(RightPanel)`
|
||||||
background-color: ${props => props.theme.rightPanel.backgroundColor};
|
background-color: ${props => props.theme.rightPanel.backgroundColor};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Row = styled.div`
|
export const Row = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
${media.lessThan('medium')`
|
${media.lessThan('medium')`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -2,6 +2,8 @@ import * as React from 'react';
|
||||||
|
|
||||||
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
|
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
|
||||||
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
|
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
|
||||||
|
|
||||||
|
import { OptionsContext } from '../components/OptionsProvider';
|
||||||
import styled, { injectGlobal } from '../styled-components';
|
import styled, { injectGlobal } from '../styled-components';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -18,11 +20,13 @@ const StyledScrollWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export class PerfectScrollbar extends React.Component<{
|
export interface PerfectScrollbarProps {
|
||||||
options?: PerfectScrollbarType.Options;
|
options?: PerfectScrollbarType.Options;
|
||||||
className?: string;
|
className?: string;
|
||||||
updateFn: (fn) => void;
|
updateFn?: (fn) => void;
|
||||||
}> {
|
}
|
||||||
|
|
||||||
|
export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private inst: PerfectScrollbarType;
|
private inst: PerfectScrollbarType;
|
||||||
|
|
||||||
|
@ -49,7 +53,9 @@ export class PerfectScrollbar extends React.Component<{
|
||||||
render() {
|
render() {
|
||||||
const { children, className, updateFn } = this.props;
|
const { children, className, updateFn } = this.props;
|
||||||
|
|
||||||
updateFn(this.componentDidUpdate.bind(this));
|
if (updateFn) {
|
||||||
|
updateFn(this.componentDidUpdate.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledScrollWrapper className={`scrollbar-container ${className}`} innerRef={this.handleRef}>
|
<StyledScrollWrapper className={`scrollbar-container ${className}`} innerRef={this.handleRef}>
|
||||||
|
@ -58,3 +64,26 @@ export class PerfectScrollbar extends React.Component<{
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PerfectScrollbarWrap(
|
||||||
|
props: PerfectScrollbarProps & { children: JSX.Element[] | JSX.Element },
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<OptionsContext.Consumer>
|
||||||
|
{options =>
|
||||||
|
!options.nativeScrollbars ? (
|
||||||
|
<PerfectScrollbar {...props}>{props.children}</PerfectScrollbar>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflow: 'auto',
|
||||||
|
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</OptionsContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export const Tabs = styled(ReactTabs)`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SmallTabs = Tabs.extend`
|
export const SmallTabs = styled(Tabs)`
|
||||||
> ul {
|
> ul {
|
||||||
display: block;
|
display: block;
|
||||||
> li {
|
> li {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { MiddlePanel, Row } from '../../common-elements/';
|
|
||||||
|
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
|
||||||
|
|
||||||
export interface ApiDescriptionProps {
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ApiDescription extends React.PureComponent<ApiDescriptionProps> {
|
|
||||||
render() {
|
|
||||||
const { description } = this.props;
|
|
||||||
return (
|
|
||||||
<Row>
|
|
||||||
<MiddlePanel>
|
|
||||||
<Markdown source={description} />
|
|
||||||
</MiddlePanel>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,9 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { AppStore } from '../../services/AppStore';
|
import { AppStore } from '../../services/AppStore';
|
||||||
|
|
||||||
import { MiddlePanel, Row } from '../../common-elements/';
|
|
||||||
|
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
|
import { MiddlePanel, Row, Section } from '../../common-elements/';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||||
import {
|
import {
|
||||||
ApiHeader,
|
ApiHeader,
|
||||||
|
@ -71,41 +71,44 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
null;
|
null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Section>
|
||||||
<MiddlePanel className="api-info">
|
<Row>
|
||||||
<ApiHeader>
|
<MiddlePanel className="api-info">
|
||||||
{info.title} <span>({info.version})</span>
|
<ApiHeader>
|
||||||
</ApiHeader>
|
{info.title} <span>({info.version})</span>
|
||||||
{!hideDownloadButton && (
|
</ApiHeader>
|
||||||
<p>
|
{!hideDownloadButton && (
|
||||||
Download OpenAPI specification:
|
<p>
|
||||||
<DownloadButton
|
Download OpenAPI specification:
|
||||||
download={downloadFilename}
|
<DownloadButton
|
||||||
target="_blank"
|
download={downloadFilename}
|
||||||
href={downloadLink}
|
target="_blank"
|
||||||
onClick={this.handleDownloadClick}
|
href={downloadLink}
|
||||||
>
|
onClick={this.handleDownloadClick}
|
||||||
Download
|
>
|
||||||
</DownloadButton>
|
Download
|
||||||
</p>
|
</DownloadButton>
|
||||||
)}
|
</p>
|
||||||
<StyledMarkdownBlock>
|
)}
|
||||||
{((info.license || info.contact || info.termsOfService) && (
|
<StyledMarkdownBlock>
|
||||||
<InfoSpanBoxWrap>
|
{((info.license || info.contact || info.termsOfService) && (
|
||||||
<InfoSpanBox>
|
<InfoSpanBoxWrap>
|
||||||
{email} {website} {license} {terms}
|
<InfoSpanBox>
|
||||||
</InfoSpanBox>
|
{email} {website} {license} {terms}
|
||||||
</InfoSpanBoxWrap>
|
</InfoSpanBox>
|
||||||
)) ||
|
</InfoSpanBoxWrap>
|
||||||
null}
|
)) ||
|
||||||
</StyledMarkdownBlock>
|
null}
|
||||||
{externalDocs && (
|
</StyledMarkdownBlock>
|
||||||
<p>
|
<Markdown source={store.spec.info.description} />
|
||||||
<ExternalDocumentation externalDocs={externalDocs} />
|
{externalDocs && (
|
||||||
</p>
|
<p>
|
||||||
)}
|
<ExternalDocumentation externalDocs={externalDocs} />
|
||||||
</MiddlePanel>
|
</p>
|
||||||
</Row>
|
)}
|
||||||
|
</MiddlePanel>
|
||||||
|
</Row>
|
||||||
|
</Section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export { ApiDescription } from './ApiDescription';
|
|
||||||
export { ApiInfo } from './ApiInfo';
|
export { ApiInfo } from './ApiInfo';
|
||||||
|
|
|
@ -5,7 +5,7 @@ const delimiterWidth = 15;
|
||||||
|
|
||||||
export const ApiInfoWrap = MiddlePanel;
|
export const ApiInfoWrap = MiddlePanel;
|
||||||
|
|
||||||
export const ApiHeader = H1.extend`
|
export const ApiHeader = styled(H1)`
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
|
|
@ -1,48 +1,29 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { SECTION_ATTR } from '../../services/MenuStore';
|
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
|
||||||
|
|
||||||
import { H1, H2, MiddlePanel, Row, ShareLink } from '../../common-elements';
|
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { MDXComponentMeta } from '../../services/MarkdownRenderer';
|
|
||||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
import { ContentItemModel } from '../../services/MenuBuilder';
|
||||||
import { GroupModel, OperationModel } from '../../services/models';
|
import { GroupModel, OperationModel } from '../../services/models';
|
||||||
import { Operation } from '../Operation/Operation';
|
import { Operation } from '../Operation/Operation';
|
||||||
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
|
|
||||||
import { StoreConsumer } from '../StoreBuilder';
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ContentItems extends React.Component<{
|
export class ContentItems extends React.Component<{
|
||||||
items: ContentItemModel[];
|
items: ContentItemModel[];
|
||||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
|
||||||
}> {
|
}> {
|
||||||
static defaultProps = {
|
|
||||||
allowedMdComponents: {
|
|
||||||
'security-definitions': {
|
|
||||||
component: SecurityDefs,
|
|
||||||
propsSelector: _store => ({
|
|
||||||
securitySchemes: _store!.spec.securitySchemes,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const items = this.props.items;
|
const items = this.props.items;
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map(item => (
|
return items.map(item => <ContentItem item={item} key={item.id} />);
|
||||||
<ContentItem item={item} key={item.id} allowedMdComponents={this.props.allowedMdComponents} />
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContentItemProps {
|
export interface ContentItemProps {
|
||||||
item: ContentItemModel;
|
item: ContentItemModel;
|
||||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -68,44 +49,42 @@ export class ContentItem extends React.Component<ContentItemProps> {
|
||||||
throw new Error('Unknown item type');
|
throw new Error('Unknown item type');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return (
|
||||||
<div key="section" {...{ [SECTION_ATTR]: item.id }}>
|
<>
|
||||||
{content}
|
<Section id={item.id} underlined={item.type === 'operation'}>
|
||||||
</div>,
|
{content}
|
||||||
(item as any).items && <ContentItems key="content" items={(item as any).items} />,
|
</Section>
|
||||||
];
|
{item.items && <ContentItems items={item.items} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const middlePanelWrap = component => <MiddlePanel>{component}</MiddlePanel>;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SectionItem extends React.Component<ContentItemProps> {
|
export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
render() {
|
render() {
|
||||||
const { name, description, externalDocs, level } = this.props.item as GroupModel;
|
const { name, description, externalDocs, level } = this.props.item as GroupModel;
|
||||||
const components = this.props.allowedMdComponents;
|
|
||||||
const Header = level === 2 ? H2 : H1;
|
const Header = level === 2 ? H2 : H1;
|
||||||
return (
|
return (
|
||||||
<Row>
|
<>
|
||||||
<MiddlePanel>
|
<Row>
|
||||||
<Header>
|
<MiddlePanel>
|
||||||
<ShareLink href={'#' + this.props.item.id} />
|
<Header>
|
||||||
{name}
|
<ShareLink to={this.props.item.id} />
|
||||||
</Header>
|
{name}
|
||||||
{components ? (
|
</Header>
|
||||||
<StoreConsumer>
|
</MiddlePanel>
|
||||||
{store => (
|
</Row>
|
||||||
<Markdown source={description || ''} allowedComponents={components} store={store} />
|
<AdvancedMarkdown source={description || ''} htmlWrap={middlePanelWrap} />
|
||||||
)}
|
{externalDocs && (
|
||||||
</StoreConsumer>
|
<p>
|
||||||
) : (
|
<ExternalDocumentation externalDocs={externalDocs} />
|
||||||
<Markdown source={description || ''} />
|
</p>
|
||||||
)}
|
)}
|
||||||
{externalDocs && (
|
</>
|
||||||
<p>
|
|
||||||
<ExternalDocumentation externalDocs={externalDocs} />
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</MiddlePanel>
|
|
||||||
</Row>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,14 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
<div>
|
<div>
|
||||||
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
||||||
<TypeName>{schema.displayType}</TypeName>
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
{schema.displayFormat && <TypeFormat> <{schema.displayFormat}> </TypeFormat>}
|
{schema.displayFormat && (
|
||||||
|
<TypeFormat>
|
||||||
|
{' '}
|
||||||
|
<
|
||||||
|
{schema.displayFormat}
|
||||||
|
>{' '}
|
||||||
|
</TypeFormat>
|
||||||
|
)}
|
||||||
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
{schema.nullable && <NullableLabel> Nullable </NullableLabel>}
|
{schema.nullable && <NullableLabel> Nullable </NullableLabel>}
|
||||||
|
|
48
src/components/Markdown/AdvancedMarkdown.tsx
Normal file
48
src/components/Markdown/AdvancedMarkdown.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { AppStore, MarkdownRenderer, RedocNormalizedOptions } from '../../services';
|
||||||
|
import { BaseMarkdownProps } from './Markdown';
|
||||||
|
import { SanitizedMarkdownHTML } from './SanitizedMdBlock';
|
||||||
|
|
||||||
|
import { OptionsConsumer } from '../OptionsProvider';
|
||||||
|
import { StoreConsumer } from '../StoreBuilder';
|
||||||
|
|
||||||
|
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
|
||||||
|
htmlWrap?: (part: JSX.Element) => JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<OptionsConsumer>
|
||||||
|
{options => (
|
||||||
|
<StoreConsumer>{store => this.renderWithOptionsAndStore(options, store)}</StoreConsumer>
|
||||||
|
)}
|
||||||
|
</OptionsConsumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderWithOptionsAndStore(options: RedocNormalizedOptions, store?: AppStore) {
|
||||||
|
const { source, htmlWrap = i => i } = this.props;
|
||||||
|
if (!store) {
|
||||||
|
throw new Error('When using componentes in markdown, store prop must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderer = new MarkdownRenderer(options);
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
|
||||||
|
if (!parts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.map((part, idx) => {
|
||||||
|
if (typeof part === 'string') {
|
||||||
|
return React.cloneElement(
|
||||||
|
htmlWrap(<SanitizedMarkdownHTML html={part} inline={false} dense={false} />),
|
||||||
|
{ key: idx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,103 +1,35 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import * as DOMPurify from 'dompurify';
|
import { MarkdownRenderer } from '../../services';
|
||||||
import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services';
|
import { SanitizedMarkdownHTML } from './SanitizedMdBlock';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
|
||||||
|
|
||||||
import { StyledMarkdownBlock } from './styled.elements';
|
|
||||||
|
|
||||||
const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
|
|
||||||
|
|
||||||
export interface StylingMarkdownProps {
|
export interface StylingMarkdownProps {
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseMarkdownProps extends StylingMarkdownProps {
|
export interface BaseMarkdownProps {
|
||||||
sanitize?: boolean;
|
sanitize?: boolean;
|
||||||
store?: AppStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
|
||||||
|
|
||||||
function SanitizedMarkdownHTML(props: StylingMarkdownProps & { html: string }) {
|
|
||||||
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OptionsContext.Consumer>
|
|
||||||
{options => (
|
|
||||||
<Wrap
|
|
||||||
className={'redoc-markdown'}
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: sanitize(options.untrustedSpec, props.html),
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</OptionsContext.Consumer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarkdownProps extends BaseMarkdownProps {
|
|
||||||
allowedComponents?: Dict<MDXComponentMeta>;
|
|
||||||
source: string;
|
source: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MarkdownProps = BaseMarkdownProps &
|
||||||
|
StylingMarkdownProps & {
|
||||||
|
source: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class Markdown extends React.Component<MarkdownProps> {
|
export class Markdown extends React.Component<MarkdownProps> {
|
||||||
constructor(props: MarkdownProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
if (props.allowedComponents && props.inline) {
|
|
||||||
throw new Error('Markdown Component: "inline" mode doesn\'t support "components"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { source, allowedComponents, store, inline, dense } = this.props;
|
const { source, inline, dense, className } = this.props;
|
||||||
|
|
||||||
if (allowedComponents && !store) {
|
|
||||||
throw new Error('When using componentes in markdown, store prop must be provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderer = new MarkdownRenderer();
|
const renderer = new MarkdownRenderer();
|
||||||
if (allowedComponents) {
|
|
||||||
return (
|
|
||||||
<AdvancedMarkdown
|
|
||||||
parts={renderer.renderMdWithComponents(source, allowedComponents)}
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<SanitizedMarkdownHTML html={renderer.renderMd(source)} inline={inline} dense={dense} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
|
|
||||||
parts: Array<string | MDXComponentMeta>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
|
||||||
render() {
|
|
||||||
const { inline, dense, store, parts } = this.props;
|
|
||||||
|
|
||||||
if (!parts.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SanitizedMarkdownHTML
|
||||||
{parts.map(
|
html={renderer.renderMd(source)}
|
||||||
(part, idx) =>
|
inline={inline}
|
||||||
typeof part === 'string' ? (
|
dense={dense}
|
||||||
<SanitizedMarkdownHTML html={part} inline={inline} dense={dense} key={idx} />
|
className={className}
|
||||||
) : (
|
/>
|
||||||
<part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
src/components/Markdown/SanitizedMdBlock.tsx
Normal file
30
src/components/Markdown/SanitizedMdBlock.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import * as DOMPurify from 'dompurify';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { OptionsConsumer } from '../OptionsProvider';
|
||||||
|
import { StylingMarkdownProps } from './Markdown';
|
||||||
|
import { StyledMarkdownBlock } from './styled.elements';
|
||||||
|
|
||||||
|
const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
|
||||||
|
|
||||||
|
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
||||||
|
|
||||||
|
export function SanitizedMarkdownHTML(
|
||||||
|
props: StylingMarkdownProps & { html: string; className?: string },
|
||||||
|
) {
|
||||||
|
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OptionsConsumer>
|
||||||
|
{options => (
|
||||||
|
<Wrap
|
||||||
|
className={'redoc-markdown ' + (props.className || '')}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: sanitize(options.untrustedSpec, props.html),
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OptionsConsumer>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { headerCommonMixin, linkifyMixin } from '../../common-elements';
|
import { headerCommonMixin, linkifyMixin } from '../../common-elements';
|
||||||
import { PrismDiv } from '../../common-elements/PrismDiv';
|
import { PrismDiv } from '../../common-elements/PrismDiv';
|
||||||
import { css, extensionsHook, withProps } from '../../styled-components';
|
import styled, { css, extensionsHook, withProps } from '../../styled-components';
|
||||||
|
|
||||||
export const linksCss = css`
|
export const linksCss = css`
|
||||||
a {
|
a {
|
||||||
|
@ -18,7 +18,7 @@ export const linksCss = css`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledMarkdownBlock = withProps<{ dense?: boolean; inline?: boolean }>(
|
export const StyledMarkdownBlock = withProps<{ dense?: boolean; inline?: boolean }>(
|
||||||
PrismDiv.extend,
|
styled(PrismDiv),
|
||||||
)`
|
)`
|
||||||
|
|
||||||
font-family: ${props => props.theme.typography.fontFamily};
|
font-family: ${props => props.theme.typography.fontFamily};
|
|
@ -19,25 +19,15 @@ import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
||||||
import { OperationModel as OperationType } from '../../services/models';
|
import { OperationModel as OperationType } from '../../services/models';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
const OperationRow = Row.extend`
|
const OperationRow = styled(Row)`
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Description = styled(Markdown)`
|
const Description = styled(Markdown)`
|
||||||
margin-bottom: ${({ theme }) => theme.spacing.unit * 8};
|
margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface OperationProps {
|
export interface OperationProps {
|
||||||
|
@ -56,7 +46,7 @@ export class Operation extends React.Component<OperationProps> {
|
||||||
<OperationRow>
|
<OperationRow>
|
||||||
<MiddlePanel>
|
<MiddlePanel>
|
||||||
<H2>
|
<H2>
|
||||||
<ShareLink href={'#' + operation.id} />
|
<ShareLink to={operation.id} />
|
||||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||||
</H2>
|
</H2>
|
||||||
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
||||||
|
|
|
@ -34,7 +34,9 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
||||||
return (
|
return (
|
||||||
<SmallTabs>
|
<SmallTabs>
|
||||||
<TabList>
|
<TabList>
|
||||||
{examplesNames.map(name => <Tab key={name}> {examples[name].summary || name} </Tab>)}
|
{examplesNames.map(name => (
|
||||||
|
<Tab key={name}> {examples[name].summary || name} </Tab>
|
||||||
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
{examplesNames.map(name => (
|
{examplesNames.map(name => (
|
||||||
<TabPanel key={name}>{sampleView(examples[name].value)}</TabPanel>
|
<TabPanel key={name}>{sampleView(examples[name].value)}</TabPanel>
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const MimeLabel = styled.div`
|
||||||
color: rgba(255, 255, 255, 0.8);
|
color: rgba(255, 255, 255, 0.8);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const InvertedSimpleDropdown = StyledDropdown.extend`
|
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
|
|
|
@ -2,12 +2,12 @@ import * as PropTypes from 'prop-types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { ThemeProvider } from '../../styled-components';
|
import { ThemeProvider } from '../../styled-components';
|
||||||
|
import { OptionsProvider } from '../OptionsProvider';
|
||||||
|
|
||||||
import { AppStore } from '../../services';
|
import { AppStore } from '../../services';
|
||||||
import { ApiDescription, ApiInfo } from '../ApiInfo/';
|
import { ApiInfo } from '../ApiInfo/';
|
||||||
import { ApiLogo } from '../ApiLogo/ApiLogo';
|
import { ApiLogo } from '../ApiLogo/ApiLogo';
|
||||||
import { ContentItems } from '../ContentItems/ContentItems';
|
import { ContentItems } from '../ContentItems/ContentItems';
|
||||||
import { OptionsProvider } from '../OptionsProvider';
|
|
||||||
import { SideMenu } from '../SideMenu/SideMenu';
|
import { SideMenu } from '../SideMenu/SideMenu';
|
||||||
import { StickyResponsiveSidebar } from '../StickySidebar/StickyResponsiveSidebar';
|
import { StickyResponsiveSidebar } from '../StickySidebar/StickyResponsiveSidebar';
|
||||||
import { ApiContentWrap, BackgroundStub, RedocWrap } from './styled.elements';
|
import { ApiContentWrap, BackgroundStub, RedocWrap } from './styled.elements';
|
||||||
|
@ -57,7 +57,6 @@ export class Redoc extends React.Component<RedocProps> {
|
||||||
</StickyResponsiveSidebar>
|
</StickyResponsiveSidebar>
|
||||||
<ApiContentWrap className="api-content">
|
<ApiContentWrap className="api-content">
|
||||||
<ApiInfo store={store} />
|
<ApiInfo store={store} />
|
||||||
<ApiDescription description={store.spec.info.description} />
|
|
||||||
<ContentItems items={menu.items as any} />
|
<ContentItems items={menu.items as any} />
|
||||||
</ApiContentWrap>
|
</ApiContentWrap>
|
||||||
<BackgroundStub />
|
<BackgroundStub />
|
||||||
|
|
|
@ -33,7 +33,8 @@ export class RedocStandalone extends React.PureComponent<RedocStandaloneProps> {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
options: PropTypes.object,
|
options: PropTypes.any,
|
||||||
|
onLoaded: PropTypes.any,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const ResponseDetailsWrap = styled.div`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const HeadersCaption = UnderlinedHeader.withComponent('caption').extend`
|
export const HeadersCaption = styled(UnderlinedHeader.withComponent('caption'))`
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
caption-side: top;
|
caption-side: top;
|
||||||
|
|
|
@ -3,6 +3,11 @@ import * as React from 'react';
|
||||||
import { Schema, SchemaProps } from './Schema';
|
import { Schema, SchemaProps } from './Schema';
|
||||||
|
|
||||||
import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
|
import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
const PaddedSchema = styled.div`
|
||||||
|
padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
|
||||||
|
`;
|
||||||
|
|
||||||
export class ArraySchema extends React.PureComponent<SchemaProps> {
|
export class ArraySchema extends React.PureComponent<SchemaProps> {
|
||||||
render() {
|
render() {
|
||||||
|
@ -10,7 +15,9 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ArrayOpenningLabel> Array </ArrayOpenningLabel>
|
<ArrayOpenningLabel> Array </ArrayOpenningLabel>
|
||||||
<Schema {...this.props} schema={itemsSchema} />
|
<PaddedSchema>
|
||||||
|
<Schema {...this.props} schema={itemsSchema} />
|
||||||
|
</PaddedSchema>
|
||||||
<ArrayClosingLabel />
|
<ArrayClosingLabel />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { MenuItem } from '../SideMenu/MenuItem';
|
||||||
import { MarkerService } from '../../services/MarkerService';
|
import { MarkerService } from '../../services/MarkerService';
|
||||||
import { SearchResult } from '../../services/SearchWorker.worker';
|
import { SearchResult } from '../../services/SearchWorker.worker';
|
||||||
|
|
||||||
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
import {
|
import {
|
||||||
ClearIcon,
|
ClearIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
|
@ -135,21 +136,27 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
onChange={this.search}
|
onChange={this.search}
|
||||||
/>
|
/>
|
||||||
{results.length > 0 && (
|
{results.length > 0 && (
|
||||||
<SearchResultsBox data-role="search:results">
|
<PerfectScrollbarWrap
|
||||||
{results.map((res, idx) => (
|
options={{
|
||||||
<MenuItem
|
wheelPropagation: false,
|
||||||
item={Object.create(res.item, {
|
}}
|
||||||
active: {
|
>
|
||||||
value: idx === activeItemIdx,
|
<SearchResultsBox data-role="search:results">
|
||||||
},
|
{results.map((res, idx) => (
|
||||||
})}
|
<MenuItem
|
||||||
onActivate={this.props.onActivate}
|
item={Object.create(res.item, {
|
||||||
withoutChildren={true}
|
active: {
|
||||||
key={res.item.id}
|
value: idx === activeItemIdx,
|
||||||
data-role="search:result"
|
},
|
||||||
/>
|
})}
|
||||||
))}
|
onActivate={this.props.onActivate}
|
||||||
</SearchResultsBox>
|
withoutChildren={true}
|
||||||
|
key={res.item.id}
|
||||||
|
data-role="search:result"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SearchResultsBox>
|
||||||
|
</PerfectScrollbarWrap>
|
||||||
)}
|
)}
|
||||||
</SearchWrap>
|
</SearchWrap>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const SearchInput = styled.input.attrs({
|
||||||
outline: none;
|
outline: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SearchIcon = styled((props: any) => (
|
export const SearchIcon = styled((props: { className?: string }) => (
|
||||||
<svg
|
<svg
|
||||||
className={props.className}
|
className={props.className}
|
||||||
version="1.1"
|
version="1.1"
|
||||||
|
@ -58,7 +58,6 @@ export const SearchResultsBox = styled.div`
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
overflow: auto;
|
|
||||||
|
|
||||||
${MenuItemLabel} {
|
${MenuItemLabel} {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
import { UnderlinedHeader } from '../../common-elements/headers';
|
import { Link, UnderlinedHeader } from '../../common-elements/';
|
||||||
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
||||||
import { linksCss } from '../Markdown/styled.elements';
|
import { linksCss } from '../Markdown/styled.elements';
|
||||||
|
|
||||||
|
@ -70,9 +70,11 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
|
||||||
{security.schemes.map(scheme => {
|
{security.schemes.map(scheme => {
|
||||||
return (
|
return (
|
||||||
<SecurityRequirementAndWrap key={scheme.id}>
|
<SecurityRequirementAndWrap key={scheme.id}>
|
||||||
<a href={'#' + scheme.sectionId}>{scheme.id}</a>
|
<Link to={scheme.sectionId}>{scheme.id}</Link>
|
||||||
{scheme.scopes.length > 0 && ' ('}
|
{scheme.scopes.length > 0 && ' ('}
|
||||||
{scheme.scopes.map(scope => <ScopeName key={scope}>{scope}</ScopeName>)}
|
{scheme.scopes.map(scope => (
|
||||||
|
<ScopeName key={scope}>{scope}</ScopeName>
|
||||||
|
))}
|
||||||
{scheme.scopes.length > 0 && ') '}
|
{scheme.scopes.length > 0 && ') '}
|
||||||
</SecurityRequirementAndWrap>
|
</SecurityRequirementAndWrap>
|
||||||
);
|
);
|
||||||
|
@ -82,18 +84,23 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthHeaderColumn = styled.td``;
|
const AuthHeaderColumn = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
const SecuritiesColumn = styled.td`
|
const SecuritiesColumn = styled.div`
|
||||||
width: ${props => props.theme.schema.defaultDetailsWidth};
|
width: ${props => props.theme.schema.defaultDetailsWidth};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AuthHeader = UnderlinedHeader.extend`
|
const AuthHeader = styled(UnderlinedHeader)`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Table = styled.table`
|
const Wrap = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin: 1em 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface SecurityRequirementsProps {
|
export interface SecurityRequirementsProps {
|
||||||
|
@ -107,20 +114,16 @@ export class SecurityRequirements extends React.PureComponent<SecurityRequiremen
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Wrap>
|
||||||
<tbody>
|
<AuthHeaderColumn>
|
||||||
<tr>
|
<AuthHeader>Authorizations: </AuthHeader>
|
||||||
<AuthHeaderColumn>
|
</AuthHeaderColumn>
|
||||||
<AuthHeader>Authorizations: </AuthHeader>
|
<SecuritiesColumn>
|
||||||
</AuthHeaderColumn>
|
{securities.map((security, idx) => (
|
||||||
<SecuritiesColumn>
|
<SecurityRequirement key={idx} security={security} />
|
||||||
{securities.map((security, idx) => (
|
))}
|
||||||
<SecurityRequirement key={idx} security={security} />
|
</SecuritiesColumn>
|
||||||
))}
|
</Wrap>
|
||||||
</SecuritiesColumn>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</Table>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { SecuritySchemesModel } from '../../services/models';
|
import { SecuritySchemesModel } from '../../services/models';
|
||||||
|
|
||||||
import { H2, ShareLink } from '../../common-elements';
|
import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { OpenAPISecurityScheme } from '../../types';
|
import { OpenAPISecurityScheme } from '../../types';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||||
|
@ -66,12 +66,12 @@ export interface SecurityDefsProps {
|
||||||
|
|
||||||
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return this.props.securitySchemes.schemes.map(scheme => (
|
||||||
<div>
|
<Section id={scheme.sectionId} key={scheme.id}>
|
||||||
{this.props.securitySchemes.schemes.map(scheme => (
|
<Row>
|
||||||
<div data-section-id={scheme.sectionId} key={scheme.id}>
|
<MiddlePanel>
|
||||||
<H2>
|
<H2>
|
||||||
<ShareLink href={'#' + scheme.sectionId} />
|
<ShareLink to={scheme.sectionId} />
|
||||||
{scheme.id}
|
{scheme.id}
|
||||||
</H2>
|
</H2>
|
||||||
<Markdown source={scheme.description || ''} />
|
<Markdown source={scheme.description || ''} />
|
||||||
|
@ -118,9 +118,9 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</StyledMarkdownBlock>
|
</StyledMarkdownBlock>
|
||||||
</div>
|
</MiddlePanel>
|
||||||
))}
|
</Row>
|
||||||
</div>
|
</Section>
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { ShelfIcon } from '../../common-elements/shelfs';
|
import { ShelfIcon } from '../../common-elements/shelfs';
|
||||||
import { IMenuItem, OperationModel } from '../../services';
|
import { IMenuItem, OperationModel } from '../../services';
|
||||||
|
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
{item.type === 'operation' ? (
|
{item.type === 'operation' ? (
|
||||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||||
) : (
|
) : (
|
||||||
<MenuItemLabel depth={item.depth} active={item.active || item.expanded} type={item.type}>
|
<MenuItemLabel depth={item.depth} active={item.active} type={item.type}>
|
||||||
<MenuItemTitle title={item.name}>
|
<MenuItemTitle title={item.name}>
|
||||||
{item.name}
|
{item.name}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
@ -93,7 +94,7 @@ class OperationMenuItemContent extends React.Component<OperationMenuItemContentP
|
||||||
active={item.active}
|
active={item.active}
|
||||||
deprecated={item.deprecated}
|
deprecated={item.deprecated}
|
||||||
>
|
>
|
||||||
<OperationBadge type={item.httpVerb} />
|
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||||
<MenuItemTitle width="calc(100% - 32px)">
|
<MenuItemTitle width="calc(100% - 32px)">
|
||||||
{item.name}
|
{item.name}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
|
||||||
|
|
||||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
|
|
||||||
import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar';
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
import { RedocAttribution } from './styled.elements';
|
import { RedocAttribution } from './styled.elements';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -15,31 +14,20 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
render() {
|
render() {
|
||||||
const store = this.props.menu;
|
const store = this.props.menu;
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<PerfectScrollbarWrap
|
||||||
{options =>
|
updateFn={this.saveScrollUpdate}
|
||||||
options.nativeScrollbars ? (
|
className={this.props.className}
|
||||||
<MenuItems
|
options={{
|
||||||
className={this.props.className}
|
wheelPropagation: false,
|
||||||
style={{
|
}}
|
||||||
overflow: 'auto',
|
>
|
||||||
msOverflowStyle: '-ms-autohiding-scrollbar',
|
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
||||||
}}
|
<RedocAttribution>
|
||||||
items={store.items}
|
<a target="_blank" href="https://github.com/Rebilly/ReDoc">
|
||||||
onActivate={this.activate}
|
Documentation Powered by ReDoc
|
||||||
root={true}
|
</a>
|
||||||
/>
|
</RedocAttribution>
|
||||||
) : (
|
</PerfectScrollbarWrap>
|
||||||
<PerfectScrollbar updateFn={this.saveScrollUpdate} className={this.props.className}>
|
|
||||||
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
|
||||||
<RedocAttribution>
|
|
||||||
<a target="_blank" href="https://github.com/Rebilly/ReDoc">
|
|
||||||
Documentation Powered by ReDoc
|
|
||||||
</a>
|
|
||||||
</RedocAttribution>
|
|
||||||
</PerfectScrollbar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</OptionsContext.Consumer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,59 +8,55 @@ export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({
|
||||||
})`
|
})`
|
||||||
width: 26px;
|
width: 26px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: ${props => props.theme.typography.code.fontSize};;
|
height: ${props => props.theme.typography.code.fontSize};
|
||||||
|
line-height: ${props => props.theme.typography.code.fontSize};
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
border-radius: 3px;
|
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-repeat: no-repeat;
|
||||||
background-position: 6px 4px;
|
background-position: 6px 4px;
|
||||||
text-indent: -9000px;
|
font-size: 7px;
|
||||||
|
font-family: Verdana; // web-safe
|
||||||
|
color: white;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
vertical-align: middle;
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
|
||||||
&.get {
|
&.get {
|
||||||
background-position: 8px -12px;
|
|
||||||
background-color: ${props => props.theme.colors.http.get};
|
background-color: ${props => props.theme.colors.http.get};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.post {
|
&.post {
|
||||||
background-position: 6px 4px;
|
|
||||||
background-color: ${props => props.theme.colors.http.post};
|
background-color: ${props => props.theme.colors.http.post};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.put {
|
&.put {
|
||||||
background-position: 8px -28px;
|
|
||||||
background-color: ${props => props.theme.colors.http.put};
|
background-color: ${props => props.theme.colors.http.put};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.options {
|
&.options {
|
||||||
background-position: 4px -148px;
|
|
||||||
background-color: ${props => props.theme.colors.http.options};
|
background-color: ${props => props.theme.colors.http.options};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.patch {
|
&.patch {
|
||||||
background-position: 4px -114px;
|
|
||||||
background-color: ${props => props.theme.colors.http.patch};
|
background-color: ${props => props.theme.colors.http.patch};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.delete {
|
&.delete {
|
||||||
background-position: 4px -44px;
|
|
||||||
background-color: ${props => props.theme.colors.http.delete};
|
background-color: ${props => props.theme.colors.http.delete};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.basic {
|
&.basic {
|
||||||
background-position: 5px -79px;
|
|
||||||
background-color: ${props => props.theme.colors.http.basic};
|
background-color: ${props => props.theme.colors.http.basic};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.link {
|
&.link {
|
||||||
background-position: 4px -131px;
|
|
||||||
background-color: ${props => props.theme.colors.http.link};
|
background-color: ${props => props.theme.colors.http.link};
|
||||||
}
|
}
|
||||||
|
|
||||||
&.head {
|
&.head {
|
||||||
background-position: 6px -102px;
|
|
||||||
background-color: ${props => props.theme.colors.http.head};
|
background-color: ${props => props.theme.colors.http.head};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -120,13 +116,15 @@ export const MenuItemLabel = withProps<{
|
||||||
active: boolean;
|
active: boolean;
|
||||||
deprecated?: boolean;
|
deprecated?: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
}>(styled.label).attrs({
|
}>(
|
||||||
role: 'menuitem',
|
styled.label.attrs({
|
||||||
className: props =>
|
role: 'menuitem',
|
||||||
classnames('-depth' + props.depth, {
|
className: props =>
|
||||||
active: props.active,
|
classnames('-depth' + props.depth, {
|
||||||
}),
|
active: props.active,
|
||||||
})`
|
}),
|
||||||
|
}),
|
||||||
|
)`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${props =>
|
color: ${props =>
|
||||||
props.active ? props.theme.colors.primary.main : props.theme.colors.text.primary};
|
props.active ? props.theme.colors.primary.main : props.theme.colors.text.primary};
|
||||||
|
@ -164,9 +162,10 @@ export const MenuItemTitle = withProps<{ width?: string }>(styled.span)`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RedocAttribution = styled.div`
|
export const RedocAttribution = styled.div`
|
||||||
|
${({ theme }) => `
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
margin-top: ${({ theme }) => `${theme.spacing.unit * 2}px`};
|
margin-top: ${theme.spacing.unit * 2}px;
|
||||||
padding: ${({ theme }) => `0 ${theme.spacing.unit * 4}px`};
|
padding: 0 ${theme.spacing.unit * 4}px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
@ -174,9 +173,10 @@ export const RedocAttribution = styled.div`
|
||||||
a,
|
a,
|
||||||
a:visited,
|
a:visited,
|
||||||
a:hover {
|
a:hover {
|
||||||
color: ${({ theme }) => theme.colors.text.primary} !important;
|
color: ${theme.colors.text.primary} !important;
|
||||||
border-top: 1px solid #e1e1e1;
|
border-top: 1px solid #e1e1e1;
|
||||||
padding-top: 10px;
|
padding: ${theme.spacing.unit}px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -4,8 +4,9 @@ import { highlight } from '../../utils';
|
||||||
import { SampleControls, SampleControlsWrap } from '../../common-elements';
|
import { SampleControls, SampleControlsWrap } from '../../common-elements';
|
||||||
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
||||||
import { PrismDiv } from '../../common-elements/PrismDiv';
|
import { PrismDiv } from '../../common-elements/PrismDiv';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
const StyledPre = PrismDiv.withComponent('pre').extend`
|
const StyledPre = styled(PrismDiv.withComponent('pre'))`
|
||||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||||
font-size: ${props => props.theme.typography.code.fontSize};
|
font-size: ${props => props.theme.typography.code.fontSize};
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
export * from './RedocStandalone';
|
export * from './RedocStandalone';
|
||||||
export * from './Redoc/Redoc';
|
export * from './Redoc/Redoc';
|
||||||
export * from './ApiInfo/ApiInfo';
|
export * from './ApiInfo/ApiInfo';
|
||||||
export * from './ApiInfo/ApiDescription';
|
|
||||||
export * from './ApiLogo/ApiLogo';
|
export * from './ApiLogo/ApiLogo';
|
||||||
export * from './ContentItems/ContentItems';
|
export * from './ContentItems/ContentItems';
|
||||||
export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements';
|
export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * from './components';
|
export * from './components';
|
||||||
export { MiddlePanel, Row, RightPanel } from './common-elements/';
|
export { MiddlePanel, Row, RightPanel, Section } from './common-elements/';
|
||||||
export * from './services';
|
export * from './services';
|
||||||
export * from './utils';
|
export * from './utils';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { observe } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPISpec } from '../types';
|
import { OpenAPISpec } from '../types';
|
||||||
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
|
||||||
import { HistoryService } from './HistoryService';
|
import { history } from './HistoryService';
|
||||||
import { MarkerService } from './MarkerService';
|
import { MarkerService } from './MarkerService';
|
||||||
import { MenuStore } from './MenuStore';
|
import { MenuStore } from './MenuStore';
|
||||||
import { SpecStore } from './models';
|
import { SpecStore } from './models';
|
||||||
|
@ -10,6 +10,9 @@ import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOption
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
import { SearchStore } from './SearchStore';
|
import { SearchStore } from './SearchStore';
|
||||||
|
|
||||||
|
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
||||||
|
import { SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
menu: {
|
menu: {
|
||||||
activeItemIdx: number;
|
activeItemIdx: number;
|
||||||
|
@ -64,14 +67,14 @@ export class AppStore {
|
||||||
createSearchIndex: boolean = true,
|
createSearchIndex: boolean = true,
|
||||||
) {
|
) {
|
||||||
this.rawOptions = options;
|
this.rawOptions = options;
|
||||||
this.options = new RedocNormalizedOptions(options);
|
this.options = new RedocNormalizedOptions(options, DEFAULT_OPTIONS);
|
||||||
this.scroll = new ScrollService(this.options);
|
this.scroll = new ScrollService(this.options);
|
||||||
|
|
||||||
// update position statically based on hash (in case of SSR)
|
// update position statically based on hash (in case of SSR)
|
||||||
MenuStore.updateOnHash(HistoryService.hash, this.scroll);
|
MenuStore.updateOnHistory(history.currentId, this.scroll);
|
||||||
|
|
||||||
this.spec = new SpecStore(spec, specUrl, this.options);
|
this.spec = new SpecStore(spec, specUrl, this.options);
|
||||||
this.menu = new MenuStore(this.spec, this.scroll);
|
this.menu = new MenuStore(this.spec, this.scroll, history);
|
||||||
|
|
||||||
if (!this.options.disableSearch) {
|
if (!this.options.disableSearch) {
|
||||||
this.search = new SearchStore();
|
this.search = new SearchStore();
|
||||||
|
@ -86,7 +89,7 @@ export class AppStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDidMount() {
|
onDidMount() {
|
||||||
this.menu.updateOnHash();
|
this.menu.updateOnHistory();
|
||||||
this.updateMarkOnMenu(this.menu.activeItemIdx);
|
this.updateMarkOnMenu(this.menu.activeItemIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,3 +140,14 @@ export class AppStore {
|
||||||
this.marker.mark();
|
this.marker.mark();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS: RedocRawOptions = {
|
||||||
|
allowedMdComponents: {
|
||||||
|
[SECURITY_DEFINITIONS_COMPONENT_NAME]: {
|
||||||
|
component: SecurityDefs,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
securitySchemes: store.spec.securitySchemes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -4,11 +4,7 @@ import { IS_BROWSER } from '../utils/';
|
||||||
|
|
||||||
const EVENT = 'hashchange';
|
const EVENT = 'hashchange';
|
||||||
|
|
||||||
function isSameHash(a: string, b: string): boolean {
|
export class HistoryService {
|
||||||
return a === b || '#' + a === b || a === '#' + b;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IntHistoryService {
|
|
||||||
private _emiter;
|
private _emiter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -16,8 +12,15 @@ export class IntHistoryService {
|
||||||
this.bind();
|
this.bind();
|
||||||
}
|
}
|
||||||
|
|
||||||
get hash(): string {
|
get currentId(): string {
|
||||||
return IS_BROWSER ? window.location.hash : '';
|
return IS_BROWSER ? window.location.hash.substring(1) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
linkForId(id: string) {
|
||||||
|
if (!id) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return '#' + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(cb): () => void {
|
subscribe(cb): () => void {
|
||||||
|
@ -26,7 +29,7 @@ export class IntHistoryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
emit = () => {
|
emit = () => {
|
||||||
this._emiter.emit(EVENT, this.hash);
|
this._emiter.emit(EVENT, this.currentId);
|
||||||
};
|
};
|
||||||
|
|
||||||
bind() {
|
bind() {
|
||||||
|
@ -43,26 +46,32 @@ export class IntHistoryService {
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
@debounce
|
@debounce
|
||||||
update(hash: string | null, rewriteHistory: boolean = false) {
|
replace(id: string | null, rewriteHistory: boolean = false) {
|
||||||
if (hash == null || isSameHash(hash, this.hash)) {
|
if (!IS_BROWSER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == null || id === this.currentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (rewriteHistory) {
|
if (rewriteHistory) {
|
||||||
if (IS_BROWSER) {
|
window.history.replaceState(
|
||||||
window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash);
|
null,
|
||||||
}
|
'',
|
||||||
|
window.location.href.split('#')[0] + this.linkForId(id),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (IS_BROWSER) {
|
window.history.pushState(null, '', window.location.href.split('#')[0] + this.linkForId(id));
|
||||||
window.history.pushState(null, '', window.location.href.split('#')[0] + '#' + hash);
|
this.emit();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HistoryService = new IntHistoryService();
|
export const history = new HistoryService();
|
||||||
|
|
||||||
if (module.hot) {
|
if (module.hot) {
|
||||||
module.hot.dispose(() => {
|
module.hot.dispose(() => {
|
||||||
HistoryService.dispose();
|
history.dispose();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as marked from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify } from '../utils';
|
import { highlight, safeSlugify } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
const renderer = new marked.Renderer();
|
const renderer = new marked.Renderer();
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ marked.setOptions({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const LEGACY_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?{component}\\s+?-->\\s*$';
|
export const LEGACY_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?<{component}\\s*?/?>\\s+?-->\\s*$';
|
||||||
export const MDX_COMPONENT_REGEXP = '^\\s*<{component}\\s*?/>\\s*$';
|
export const MDX_COMPONENT_REGEXP = '^\\s*<{component}\\s*?/>\\s*$';
|
||||||
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';
|
||||||
|
|
||||||
|
@ -35,13 +36,21 @@ export function buildComponentComment(name: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MarkdownRenderer {
|
export class MarkdownRenderer {
|
||||||
|
static containsComponent(rawText: string, componentName: string) {
|
||||||
|
const anyCompRegexp = new RegExp(
|
||||||
|
COMPONENT_REGEXP.replace(/{component}/g, componentName),
|
||||||
|
'gmi',
|
||||||
|
);
|
||||||
|
return anyCompRegexp.test(rawText);
|
||||||
|
}
|
||||||
|
|
||||||
headings: MarkdownHeading[] = [];
|
headings: MarkdownHeading[] = [];
|
||||||
currentTopHeading: MarkdownHeading;
|
currentTopHeading: MarkdownHeading;
|
||||||
|
|
||||||
private headingEnhanceRenderer: marked.Renderer;
|
private headingEnhanceRenderer: marked.Renderer;
|
||||||
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
||||||
|
|
||||||
constructor() {
|
constructor(public options?: RedocNormalizedOptions) {
|
||||||
this.headingEnhanceRenderer = new marked.Renderer();
|
this.headingEnhanceRenderer = new marked.Renderer();
|
||||||
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
||||||
this.headingEnhanceRenderer,
|
this.headingEnhanceRenderer,
|
||||||
|
@ -78,7 +87,9 @@ export class MarkdownRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = heading => new RegExp(`##?\\s+${heading.name}`);
|
const buildRegexp = heading => {
|
||||||
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
||||||
|
};
|
||||||
|
|
||||||
const flatHeadings = this.flattenHeadings(this.headings);
|
const flatHeadings = this.flattenHeadings(this.headings);
|
||||||
if (flatHeadings.length < 1) {
|
if (flatHeadings.length < 1) {
|
||||||
|
@ -138,15 +149,17 @@ export class MarkdownRenderer {
|
||||||
|
|
||||||
// TODO: rewrite this completelly! Regexp-based 👎
|
// TODO: rewrite this completelly! Regexp-based 👎
|
||||||
// Use marked ecosystem
|
// Use marked ecosystem
|
||||||
renderMdWithComponents(
|
renderMdWithComponents(rawText: string): Array<string | MDXComponentMeta> {
|
||||||
rawText: string,
|
const components = this.options && this.options.allowedMdComponents;
|
||||||
components: Dict<MDXComponentMeta>,
|
if (!components || Object.keys(components).length === 0) {
|
||||||
): Array<string | MDXComponentMeta> {
|
return [this.renderMd(rawText)];
|
||||||
|
}
|
||||||
|
|
||||||
const componentDefs: string[] = [];
|
const componentDefs: string[] = [];
|
||||||
const names = '(?:' + Object.keys(components).join('|') + ')';
|
const names = '(?:' + Object.keys(components).join('|') + ')';
|
||||||
|
|
||||||
const anyCompRegexp = new RegExp(
|
const anyCompRegexp = new RegExp(
|
||||||
COMPONENT_REGEXP.replace(/{component}/g, '(<?' + names + '.*?)'),
|
COMPONENT_REGEXP.replace(/{component}/g, '(' + names + '.*?)'),
|
||||||
'gmi',
|
'gmi',
|
||||||
);
|
);
|
||||||
let match = anyCompRegexp.exec(rawText);
|
let match = anyCompRegexp.exec(rawText);
|
||||||
|
@ -187,10 +200,6 @@ function parseComponent(
|
||||||
componentName?: string;
|
componentName?: string;
|
||||||
attrs: any;
|
attrs: any;
|
||||||
} {
|
} {
|
||||||
if (htmlTag.startsWith('<')) {
|
|
||||||
return legacyParse(htmlTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = /([\w_-]+)(\s+[\w_-]+\s*={[^}]*?})*/.exec(htmlTag);
|
const match = /([\w_-]+)(\s+[\w_-]+\s*={[^}]*?})*/.exec(htmlTag);
|
||||||
if (match === null || match.length <= 1) {
|
if (match === null || match.length <= 1) {
|
||||||
return { componentName: undefined, attrs: {} };
|
return { componentName: undefined, attrs: {} };
|
||||||
|
@ -214,20 +223,3 @@ function parseComponent(
|
||||||
attrs,
|
attrs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function legacyParse(
|
|
||||||
htmlTag: string,
|
|
||||||
): {
|
|
||||||
componentName?: string;
|
|
||||||
attrs: any;
|
|
||||||
} {
|
|
||||||
const match = /<([\w_-]+).*?>/.exec(htmlTag);
|
|
||||||
if (match === null || match.length <= 1) {
|
|
||||||
return { componentName: undefined, attrs: {} };
|
|
||||||
}
|
|
||||||
const componentName = match[1];
|
|
||||||
return {
|
|
||||||
componentName,
|
|
||||||
attrs: {}, // TODO
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
|
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
|
||||||
import { isOperationName } from '../utils';
|
import {
|
||||||
|
isOperationName,
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
setSecuritySchemePrefix,
|
||||||
|
} from '../utils';
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { GroupModel, OperationModel } from './models';
|
import { GroupModel, OperationModel } from './models';
|
||||||
import { OpenAPIParser } from './OpenAPIParser';
|
import { OpenAPIParser } from './OpenAPIParser';
|
||||||
|
@ -38,7 +42,7 @@ export class MenuBuilder {
|
||||||
|
|
||||||
const items: ContentItemModel[] = [];
|
const items: ContentItemModel[] = [];
|
||||||
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
||||||
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || ''));
|
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', options));
|
||||||
if (spec['x-tagGroups']) {
|
if (spec['x-tagGroups']) {
|
||||||
items.push(
|
items.push(
|
||||||
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
||||||
|
@ -53,8 +57,11 @@ export class MenuBuilder {
|
||||||
* extracts items from markdown description
|
* extracts items from markdown description
|
||||||
* @param description - markdown source
|
* @param description - markdown source
|
||||||
*/
|
*/
|
||||||
static addMarkdownItems(description: string): ContentItemModel[] {
|
static addMarkdownItems(
|
||||||
const renderer = new MarkdownRenderer();
|
description: string,
|
||||||
|
options: RedocNormalizedOptions,
|
||||||
|
): ContentItemModel[] {
|
||||||
|
const renderer = new MarkdownRenderer(options);
|
||||||
const headings = renderer.extractHeadings(description || '');
|
const headings = renderer.extractHeadings(description || '');
|
||||||
|
|
||||||
const mapHeadingsDeep = (parent, items, depth = 1) =>
|
const mapHeadingsDeep = (parent, items, depth = 1) =>
|
||||||
|
@ -64,6 +71,14 @@ export class MenuBuilder {
|
||||||
if (heading.items) {
|
if (heading.items) {
|
||||||
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
MarkdownRenderer.containsComponent(
|
||||||
|
group.description || '',
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setSecuritySchemePrefix(group.id + '/');
|
||||||
|
}
|
||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { action, observable } from 'mobx';
|
||||||
import { querySelector } from '../utils/dom';
|
import { querySelector } from '../utils/dom';
|
||||||
import { SpecStore } from './models';
|
import { SpecStore } from './models';
|
||||||
|
|
||||||
import { HistoryService } from './HistoryService';
|
import { history as historyInst, HistoryService } from './HistoryService';
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
|
|
||||||
import { flattenByProp, normalizeHash } from '../utils';
|
import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||||
import { GROUP_DEPTH } from './MenuBuilder';
|
import { GROUP_DEPTH } from './MenuBuilder';
|
||||||
|
|
||||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||||
|
@ -42,22 +42,24 @@ export class MenuStore {
|
||||||
* Statically try update scroll position
|
* Statically try update scroll position
|
||||||
* Used before hydrating from server-side rendered html to scroll page faster
|
* Used before hydrating from server-side rendered html to scroll page faster
|
||||||
*/
|
*/
|
||||||
static updateOnHash(hash: string = HistoryService.hash, scroll: ScrollService) {
|
static updateOnHistory(id: string = historyInst.currentId, scroll: ScrollService) {
|
||||||
if (!hash) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${normalizeHash(hash)}"]`);
|
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* active item absolute index (when flattened). -1 means nothing is selected
|
* active item absolute index (when flattened). -1 means nothing is selected
|
||||||
*/
|
*/
|
||||||
@observable activeItemIdx: number = -1;
|
@observable
|
||||||
|
activeItemIdx: number = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* whether sidebar with menu is opened or not
|
* whether sidebar with menu is opened or not
|
||||||
*/
|
*/
|
||||||
@observable sideBarOpened: boolean = false;
|
@observable
|
||||||
|
sideBarOpened: boolean = false;
|
||||||
|
|
||||||
items: IMenuItem[];
|
items: IMenuItem[];
|
||||||
flatItems: IMenuItem[];
|
flatItems: IMenuItem[];
|
||||||
|
@ -73,8 +75,8 @@ export class MenuStore {
|
||||||
* @param spec [SpecStore](#SpecStore) which contains page content structure
|
* @param spec [SpecStore](#SpecStore) which contains page content structure
|
||||||
* @param scroll scroll service instance used by this menu
|
* @param scroll scroll service instance used by this menu
|
||||||
*/
|
*/
|
||||||
constructor(spec: SpecStore, public scroll: ScrollService) {
|
constructor(spec: SpecStore, public scroll: ScrollService, public history: HistoryService) {
|
||||||
this.items = spec.operationGroups;
|
this.items = spec.contentItems;
|
||||||
|
|
||||||
this.flatItems = flattenByProp(this.items || [], 'items');
|
this.flatItems = flattenByProp(this.items || [], 'items');
|
||||||
this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx));
|
this.flatItems.forEach((item, idx) => (item.absoluteIdx = idx));
|
||||||
|
@ -84,7 +86,7 @@ export class MenuStore {
|
||||||
|
|
||||||
subscribe() {
|
subscribe() {
|
||||||
this._unsubscribe = this.scroll.subscribe(this.updateOnScroll);
|
this._unsubscribe = this.scroll.subscribe(this.updateOnScroll);
|
||||||
this._hashUnsubscribe = HistoryService.subscribe(this.updateOnHash);
|
this._hashUnsubscribe = this.history.subscribe(this.updateOnHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -132,22 +134,24 @@ export class MenuStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update active items on hash change
|
* update active items on hash change
|
||||||
* @param hash current hash
|
* @param id current hash
|
||||||
*/
|
*/
|
||||||
updateOnHash = (hash: string = HistoryService.hash): boolean => {
|
updateOnHistory = (id: string = this.history.currentId) => {
|
||||||
if (!hash) {
|
if (!id) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
let item: IMenuItem | undefined;
|
let item: IMenuItem | undefined;
|
||||||
hash = normalizeHash(hash);
|
|
||||||
|
|
||||||
item = this.flatItems.find(i => i.id === hash);
|
item = this.flatItems.find(i => i.id === id);
|
||||||
if (item) {
|
if (item) {
|
||||||
this.activateAndScroll(item, false);
|
this.activateAndScroll(item, false);
|
||||||
} else {
|
} else {
|
||||||
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${hash}"]`);
|
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
|
||||||
|
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
|
||||||
|
this.activate(item);
|
||||||
|
}
|
||||||
|
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
||||||
}
|
}
|
||||||
return item !== undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -173,13 +177,13 @@ export class MenuStore {
|
||||||
/**
|
/**
|
||||||
* activate menu item
|
* activate menu item
|
||||||
* @param item item to activate
|
* @param item item to activate
|
||||||
* @param updateHash [true] whether to update location hash
|
* @param updateLocation [true] whether to update location
|
||||||
* @param rewriteHistory [false] whether to rewrite browser history (do not create new enrty)
|
* @param rewriteHistory [false] whether to rewrite browser history (do not create new enrty)
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
activate(
|
activate(
|
||||||
item: IMenuItem | undefined,
|
item: IMenuItem | undefined,
|
||||||
updateHash: boolean = true,
|
updateLocation: boolean = true,
|
||||||
rewriteHistory: boolean = false,
|
rewriteHistory: boolean = false,
|
||||||
) {
|
) {
|
||||||
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
||||||
|
@ -187,7 +191,7 @@ export class MenuStore {
|
||||||
}
|
}
|
||||||
this.deactivate(this.activeItem);
|
this.deactivate(this.activeItem);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
HistoryService.update('', rewriteHistory);
|
this.history.replace('', rewriteHistory);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,8 +202,8 @@ export class MenuStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeItemIdx = item.absoluteIdx!;
|
this.activeItemIdx = item.absoluteIdx!;
|
||||||
if (updateHash) {
|
if (updateLocation) {
|
||||||
HistoryService.update(item.id, rewriteHistory);
|
this.history.replace(item.id, rewriteHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.activate();
|
item.activate();
|
||||||
|
@ -226,10 +230,14 @@ export class MenuStore {
|
||||||
* @see MenuStore.activate
|
* @see MenuStore.activate
|
||||||
*/
|
*/
|
||||||
@action.bound
|
@action.bound
|
||||||
activateAndScroll(item: IMenuItem | undefined, updateHash?: boolean, rewriteHistory?: boolean) {
|
activateAndScroll(
|
||||||
|
item: IMenuItem | undefined,
|
||||||
|
updateLocation?: boolean,
|
||||||
|
rewriteHistory?: boolean,
|
||||||
|
) {
|
||||||
// item here can be a copy from search results so find corresponding item from menu
|
// item here can be a copy from search results so find corresponding item from menu
|
||||||
const menuItem = (item && this.getItemById(item.id)) || item;
|
const menuItem = (item && this.getItemById(item.id)) || item;
|
||||||
this.activate(menuItem, updateHash, rewriteHistory);
|
this.activate(menuItem, updateLocation, rewriteHistory);
|
||||||
this.scrollToActive();
|
this.scrollToActive();
|
||||||
if (!menuItem || !menuItem.items.length) {
|
if (!menuItem || !menuItem.items.length) {
|
||||||
this.closeSidebar();
|
this.closeSidebar();
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
||||||
|
|
||||||
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { isNamedDefinition } from '../utils/openapi';
|
import { isNamedDefinition, SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
||||||
import { buildComponentComment, COMPONENT_REGEXP, MDX_COMPONENT_REGEXP } from './MarkdownRenderer';
|
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
|
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
|
||||||
|
@ -74,16 +74,8 @@ export class OpenAPIParser {
|
||||||
) {
|
) {
|
||||||
// Automatically inject Authentication section with SecurityDefinitions component
|
// Automatically inject Authentication section with SecurityDefinitions component
|
||||||
const description = spec.info.description || '';
|
const description = spec.info.description || '';
|
||||||
const legacySecurityRegexp = new RegExp(
|
if (!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME)) {
|
||||||
COMPONENT_REGEXP.replace('{component}', '<security-definitions>'),
|
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
|
||||||
'mi',
|
|
||||||
);
|
|
||||||
const securityRegexp = new RegExp(
|
|
||||||
MDX_COMPONENT_REGEXP.replace('{component}', 'security-definitions'),
|
|
||||||
'mi',
|
|
||||||
);
|
|
||||||
if (!legacySecurityRegexp.test(description) && !securityRegexp.test(description)) {
|
|
||||||
const comment = buildComponentComment('security-definitions');
|
|
||||||
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,7 +256,9 @@ export class OpenAPIParser {
|
||||||
if (subSchemaRef) {
|
if (subSchemaRef) {
|
||||||
receiver.parentRefs!.push(subSchemaRef);
|
receiver.parentRefs!.push(subSchemaRef);
|
||||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
||||||
receiver.title = JsonPointer.baseName(subSchemaRef);
|
// this is not so correct behaviour. comented out for now
|
||||||
|
// ref: https://github.com/Rebilly/ReDoc/issues/601
|
||||||
|
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ import defaultTheme, { ResolvedThemeInterface, resolveTheme, ThemeInterface } fr
|
||||||
import { querySelector } from '../utils/dom';
|
import { querySelector } from '../utils/dom';
|
||||||
import { isNumeric, mergeObjects } from '../utils/helpers';
|
import { isNumeric, mergeObjects } from '../utils/helpers';
|
||||||
|
|
||||||
|
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||||
|
|
||||||
export interface RedocRawOptions {
|
export interface RedocRawOptions {
|
||||||
theme?: ThemeInterface;
|
theme?: ThemeInterface;
|
||||||
scrollYOffset?: number | string | (() => number);
|
scrollYOffset?: number | string | (() => number);
|
||||||
|
@ -17,6 +19,8 @@ export interface RedocRawOptions {
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
|
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function argValueToBoolean(val?: string | boolean): boolean {
|
function argValueToBoolean(val?: string | boolean): boolean {
|
||||||
|
@ -98,9 +102,11 @@ export class RedocNormalizedOptions {
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
allowedMdComponents: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
constructor(raw: RedocRawOptions) {
|
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||||
let hook;
|
let hook;
|
||||||
|
raw = { ...defaults, ...raw };
|
||||||
if (raw.theme && raw.theme.extensionsHook) {
|
if (raw.theme && raw.theme.extensionsHook) {
|
||||||
hook = raw.theme.extensionsHook;
|
hook = raw.theme.extensionsHook;
|
||||||
raw.theme.extensionsHook = undefined;
|
raw.theme.extensionsHook = undefined;
|
||||||
|
@ -120,5 +126,7 @@ export class RedocNormalizedOptions {
|
||||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
this.allowedMdComponents = raw.allowedMdComponents || {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class SpecStore {
|
||||||
|
|
||||||
info: ApiInfoModel;
|
info: ApiInfoModel;
|
||||||
externalDocs?: OpenAPIExternalDocumentation;
|
externalDocs?: OpenAPIExternalDocumentation;
|
||||||
operationGroups: ContentItemModel[];
|
contentItems: ContentItemModel[];
|
||||||
securitySchemes: SecuritySchemesModel;
|
securitySchemes: SecuritySchemesModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -24,8 +24,7 @@ export class SpecStore {
|
||||||
this.parser = new OpenAPIParser(spec, specUrl, options);
|
this.parser = new OpenAPIParser(spec, specUrl, options);
|
||||||
this.info = new ApiInfoModel(this.parser);
|
this.info = new ApiInfoModel(this.parser);
|
||||||
this.externalDocs = this.parser.spec.externalDocs;
|
this.externalDocs = this.parser.spec.externalDocs;
|
||||||
this.operationGroups = MenuBuilder.buildStructure(this.parser, this.options);
|
this.contentItems = MenuBuilder.buildStructure(this.parser, this.options);
|
||||||
|
|
||||||
this.securitySchemes = new SecuritySchemesModel(this.parser);
|
this.securitySchemes = new SecuritySchemesModel(this.parser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
import { MarkdownRenderer } from '../MarkdownRenderer';
|
import { MarkdownRenderer, MDXComponentMeta } from '../MarkdownRenderer';
|
||||||
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
|
const TestComponent = () => null;
|
||||||
|
|
||||||
describe('Markdown renderer', () => {
|
describe('Markdown renderer', () => {
|
||||||
let renderer: MarkdownRenderer;
|
let renderer: MarkdownRenderer;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
renderer = new MarkdownRenderer();
|
renderer = new MarkdownRenderer(
|
||||||
|
new RedocNormalizedOptions({
|
||||||
|
allowedMdComponents: {
|
||||||
|
'security-definitions': {
|
||||||
|
component: TestComponent,
|
||||||
|
propsSelector: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return a level-1 heading even though only level-2 is present', () => {
|
test('should return a level-1 heading even though only level-2 is present', () => {
|
||||||
|
@ -19,4 +31,33 @@ describe('Markdown renderer', () => {
|
||||||
expect(headings[0].items).toBeDefined();
|
expect(headings[0].items).toBeDefined();
|
||||||
expect(headings[0].items).toHaveLength(1);
|
expect(headings[0].items).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should work with legacy syntax', () => {
|
||||||
|
const source = 'Hello!\n<!-- ReDoc-Inject: <security-definitions> -->\nBye';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(3);
|
||||||
|
expect(parts[0]).toEqual('<p>Hello!</p>\n');
|
||||||
|
expect(typeof parts[1]).toEqual('object');
|
||||||
|
expect((parts[1] as MDXComponentMeta).component).toEqual(TestComponent);
|
||||||
|
expect(parts[2]).toEqual('<p>Bye</p>\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should work with mdx-like syntax', () => {
|
||||||
|
const source = 'Hello!\n<security-definitions/>\nBye';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(3);
|
||||||
|
expect(parts[0]).toEqual('<p>Hello!</p>\n');
|
||||||
|
expect(typeof parts[1]).toEqual('object');
|
||||||
|
expect((parts[1] as MDXComponentMeta).component).toBe(TestComponent);
|
||||||
|
expect(parts[2]).toEqual('<p>Bye</p>\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renderMdWithComponents should parse attribute names', () => {
|
||||||
|
const source = '<security-definitions pointer={"test"}/>';
|
||||||
|
const parts = renderer.renderMdWithComponents(source);
|
||||||
|
expect(parts).toHaveLength(1);
|
||||||
|
const part = parts[0] as MDXComponentMeta;
|
||||||
|
expect(part.component).toBe(TestComponent);
|
||||||
|
expect(part.attrs).toEqual({ pointer: 'test' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
79
src/services/__tests__/fixtures/oneOfTitles.json
Normal file
79
src/services/__tests__/fixtures/oneOfTitles.json
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
{
|
||||||
|
"openapi": "3.0.0",
|
||||||
|
"info": {
|
||||||
|
"version": "1.0",
|
||||||
|
"title": "Foo"
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Test": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"any": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Foo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Bar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"one": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Foo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Bar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"all": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Foo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/Bar"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Foo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"foo": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Bar": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"bar": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WithArray": {
|
||||||
|
"oneOf": [{
|
||||||
|
"type" : "array",
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"type": "string"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,38 @@
|
||||||
import { HistoryService } from '../HistoryService';
|
import { history } from '../HistoryService';
|
||||||
|
|
||||||
describe('History service', () => {
|
describe('History service', () => {
|
||||||
test('should be an instance', () => {
|
test('should be an instance', () => {
|
||||||
expect(typeof HistoryService).not.toBe('function');
|
expect(typeof history).not.toBe('function');
|
||||||
expect(HistoryService.subscribe).toBeDefined();
|
expect(history.subscribe).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('History subscribe', () => {
|
test('History subscribe', () => {
|
||||||
const fn = jest.fn();
|
const fn = jest.fn();
|
||||||
HistoryService.subscribe(fn);
|
history.subscribe(fn);
|
||||||
HistoryService.emit();
|
history.emit();
|
||||||
expect(fn).toHaveBeenCalled();
|
expect(fn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('History subscribe should return unsubsribe function', () => {
|
test('History subscribe should return unsubsribe function', () => {
|
||||||
const fn = jest.fn();
|
const fn = jest.fn();
|
||||||
const unsubscribe = HistoryService.subscribe(fn);
|
const unsubscribe = history.subscribe(fn);
|
||||||
HistoryService.emit();
|
history.emit();
|
||||||
expect(fn).toHaveBeenCalled();
|
expect(fn).toHaveBeenCalled();
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
HistoryService.emit();
|
history.emit();
|
||||||
expect(fn).toHaveBeenCalledTimes(1);
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('currentId should return correct id', () => {
|
||||||
|
window.location.hash = '#testid';
|
||||||
|
expect(history.currentId).toEqual('testid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return correct link for id', () => {
|
||||||
|
expect(history.linkForId('testid')).toEqual('#testid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return empty link for empty id', () => {
|
||||||
|
expect(history.linkForId('')).toEqual('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,5 +15,29 @@ describe('Models', () => {
|
||||||
expect(schema.oneOf).toHaveLength(1);
|
expect(schema.oneOf).toHaveLength(1);
|
||||||
expect(schema.discriminatorProp).toEqual('type');
|
expect(schema.discriminatorProp).toEqual('type');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('oneOf/allOf titles', () => {
|
||||||
|
const spec = require('../fixtures/oneOfTitles.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.Test, '', opts);
|
||||||
|
expect(schema.fields).toHaveLength(3);
|
||||||
|
const oneOfField = schema.fields[0];
|
||||||
|
expect(oneOfField.schema.displayType).toBe('Foo (object) or Bar (object)');
|
||||||
|
expect(oneOfField.schema.oneOf[0].title).toBe('Foo');
|
||||||
|
expect(oneOfField.schema.oneOf[1].title).toBe('Bar');
|
||||||
|
|
||||||
|
const anyOfField = schema.fields[1];
|
||||||
|
expect(anyOfField.schema.displayType).toBe('Foo (object) or Bar (object)');
|
||||||
|
expect(anyOfField.schema.oneOf[0].title).toBe('Foo');
|
||||||
|
expect(anyOfField.schema.oneOf[1].title).toBe('Bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('oneOf/allOf schema complex displayType', () => {
|
||||||
|
const spec = require('../fixtures/oneOfTitles.json');
|
||||||
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
const schema = new SchemaModel(parser, spec.components.schemas.WithArray, '', opts);
|
||||||
|
expect(schema.oneOf).toHaveLength(2);
|
||||||
|
expect(schema.displayType).toBe('(Array of string or number) or string');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,4 +16,8 @@ describe('prism.js helpers', () => {
|
||||||
test('highlight raw text should just return text', () => {
|
test('highlight raw text should just return text', () => {
|
||||||
expect(highlight('Hello world', 'clike')).toBe('Hello world');
|
expect(highlight('Hello world', 'clike')).toBe('Hello world');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('highlight should not throw with lang undefined', () => {
|
||||||
|
expect(highlight('Hello world', undefined)).toBe('Hello world');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,8 @@ import { SchemaModel } from './Schema';
|
||||||
* Field or Parameter model ready to be used by components
|
* Field or Parameter model ready to be used by components
|
||||||
*/
|
*/
|
||||||
export class FieldModel {
|
export class FieldModel {
|
||||||
@observable expanded: boolean = false;
|
@observable
|
||||||
|
expanded: boolean = false;
|
||||||
|
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -21,8 +21,10 @@ export class GroupModel implements IMenuItem {
|
||||||
parent?: GroupModel;
|
parent?: GroupModel;
|
||||||
externalDocs?: OpenAPIExternalDocumentation;
|
externalDocs?: OpenAPIExternalDocumentation;
|
||||||
|
|
||||||
@observable active: boolean = false;
|
@observable
|
||||||
@observable expanded: boolean = false;
|
active: boolean = false;
|
||||||
|
@observable
|
||||||
|
expanded: boolean = false;
|
||||||
|
|
||||||
depth: number;
|
depth: number;
|
||||||
level: number;
|
level: number;
|
||||||
|
|
|
@ -14,7 +14,8 @@ import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
export class MediaContentModel {
|
export class MediaContentModel {
|
||||||
mediaTypes: MediaTypeModel[];
|
mediaTypes: MediaTypeModel[];
|
||||||
|
|
||||||
@observable activeMimeIdx = 0;
|
@observable
|
||||||
|
activeMimeIdx = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class MediaTypeModel {
|
||||||
value: sample,
|
value: sample,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else if (this.schema) {
|
||||||
this.examples = {
|
this.examples = {
|
||||||
default: new ExampleModel(parser, {
|
default: new ExampleModel(parser, {
|
||||||
value: Sampler.sample(
|
value: Sampler.sample(
|
||||||
|
|
|
@ -40,9 +40,12 @@ export class OperationModel implements IMenuItem {
|
||||||
|
|
||||||
depth: number;
|
depth: number;
|
||||||
|
|
||||||
@observable ready?: boolean = true;
|
@observable
|
||||||
@observable active: boolean = false;
|
ready?: boolean = true;
|
||||||
@observable expanded: boolean = false;
|
@observable
|
||||||
|
active: boolean = false;
|
||||||
|
@observable
|
||||||
|
expanded: boolean = false;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
pointer: string;
|
pointer: string;
|
||||||
|
|
|
@ -9,7 +9,8 @@ import { FieldModel } from './Field';
|
||||||
import { MediaContentModel } from './MediaContent';
|
import { MediaContentModel } from './MediaContent';
|
||||||
|
|
||||||
export class ResponseModel {
|
export class ResponseModel {
|
||||||
@observable expanded: boolean;
|
@observable
|
||||||
|
expanded: boolean;
|
||||||
|
|
||||||
content?: MediaContentModel;
|
content?: MediaContentModel;
|
||||||
code: string;
|
code: string;
|
||||||
|
|
|
@ -49,7 +49,8 @@ export class SchemaModel {
|
||||||
oneOf?: SchemaModel[];
|
oneOf?: SchemaModel[];
|
||||||
oneOfType: string;
|
oneOfType: string;
|
||||||
discriminatorProp: string;
|
discriminatorProp: string;
|
||||||
@observable activeOneOf: number = 0;
|
@observable
|
||||||
|
activeOneOf: number = 0;
|
||||||
|
|
||||||
rawSchema: OpenAPISchema;
|
rawSchema: OpenAPISchema;
|
||||||
schema: MergedOpenAPISchema;
|
schema: MergedOpenAPISchema;
|
||||||
|
@ -162,13 +163,24 @@ export class SchemaModel {
|
||||||
parser,
|
parser,
|
||||||
{
|
{
|
||||||
// merge base schema into each of oneOf's subschemas
|
// merge base schema into each of oneOf's subschemas
|
||||||
allOf: [variant, { ...this.schema, oneOf: undefined, anyOf: undefined }],
|
...variant,
|
||||||
|
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
|
||||||
} as OpenAPISchema,
|
} as OpenAPISchema,
|
||||||
this.pointer + '/oneOf/' + idx,
|
this.pointer + '/oneOf/' + idx,
|
||||||
this.options,
|
this.options,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.displayType = this.oneOf.map(schema => schema.displayType).join(' or ');
|
this.displayType = this.oneOf
|
||||||
|
.map(schema => {
|
||||||
|
let name =
|
||||||
|
schema.typePrefix +
|
||||||
|
(schema.title ? `${schema.title} (${schema.displayType})` : schema.displayType);
|
||||||
|
if (name.indexOf(' or ') > -1) {
|
||||||
|
name = `(${name})`;
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
})
|
||||||
|
.join(' or ');
|
||||||
}
|
}
|
||||||
|
|
||||||
private initDiscriminator(
|
private initDiscriminator(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
import { OpenAPISecurityRequirement, OpenAPISecurityScheme } from '../../types';
|
||||||
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
|
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
|
||||||
export interface SecurityScheme extends OpenAPISecurityScheme {
|
export interface SecurityScheme extends OpenAPISecurityScheme {
|
||||||
|
@ -27,7 +27,7 @@ export class SecurityRequirementModel {
|
||||||
return {
|
return {
|
||||||
...scheme,
|
...scheme,
|
||||||
id,
|
id,
|
||||||
sectionId: SECURITY_SCHEMES_SECTION + id,
|
sectionId: SECURITY_SCHEMES_SECTION_PREFIX + id,
|
||||||
scopes,
|
scopes,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { OpenAPISecurityScheme, Referenced } from '../../types';
|
import { OpenAPISecurityScheme, Referenced } from '../../types';
|
||||||
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
|
import { SECURITY_SCHEMES_SECTION_PREFIX } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
|
||||||
export class SecuritySchemeModel {
|
export class SecuritySchemeModel {
|
||||||
|
@ -25,7 +25,7 @@ export class SecuritySchemeModel {
|
||||||
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
constructor(parser: OpenAPIParser, id: string, scheme: Referenced<OpenAPISecurityScheme>) {
|
||||||
const info = parser.deref(scheme);
|
const info = parser.deref(scheme);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.sectionId = SECURITY_SCHEMES_SECTION + id;
|
this.sectionId = SECURITY_SCHEMES_SECTION_PREFIX + id;
|
||||||
this.type = info.type;
|
this.type = info.type;
|
||||||
this.description = info.description || '';
|
this.description = info.description || '';
|
||||||
if (info.type === 'apiKey') {
|
if (info.type === 'apiKey') {
|
||||||
|
|
|
@ -24,10 +24,6 @@ export function html2Str(html: string): string {
|
||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeHash(hash: string): string {
|
|
||||||
return hash.startsWith('#') ? hash.substr(1) : hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
// scrollIntoViewIfNeeded polyfill
|
// scrollIntoViewIfNeeded polyfill
|
||||||
|
|
||||||
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import 'prismjs/components/prism-php.js';
|
||||||
import 'prismjs/components/prism-python.js';
|
import 'prismjs/components/prism-python.js';
|
||||||
import 'prismjs/components/prism-ruby.js';
|
import 'prismjs/components/prism-ruby.js';
|
||||||
import 'prismjs/components/prism-scala.js';
|
import 'prismjs/components/prism-scala.js';
|
||||||
|
import 'prismjs/components/prism-sql.js';
|
||||||
import 'prismjs/components/prism-swift.js';
|
import 'prismjs/components/prism-swift.js';
|
||||||
|
|
||||||
const DEFAULT_LANG = 'clike';
|
const DEFAULT_LANG = 'clike';
|
||||||
|
@ -42,7 +43,7 @@ export function mapLang(lang: string): string {
|
||||||
* @param lang highlight language
|
* @param lang highlight language
|
||||||
* @return highlighted souce code as **html string**
|
* @return highlighted souce code as **html string**
|
||||||
*/
|
*/
|
||||||
export function highlight(source: string, lang: string): string {
|
export function highlight(source: string, lang: string = DEFAULT_LANG): string {
|
||||||
lang = lang.toLowerCase();
|
lang = lang.toLowerCase();
|
||||||
let grammar = Prism.languages[lang];
|
let grammar = Prism.languages[lang];
|
||||||
if (!grammar) {
|
if (!grammar) {
|
||||||
|
|
|
@ -275,4 +275,14 @@ export function normalizeServers(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECURITY_SCHEMES_SECTION = 'section/Authentication/';
|
export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions';
|
||||||
|
export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/';
|
||||||
|
export function setSecuritySchemePrefix(prefix: string) {
|
||||||
|
SECURITY_SCHEMES_SECTION_PREFIX = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const shortenHTTPVerb = verb =>
|
||||||
|
({
|
||||||
|
delete: 'del',
|
||||||
|
options: 'opts',
|
||||||
|
}[verb] || verb);
|
||||||
|
|
|
@ -88,8 +88,11 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
||||||
{
|
{
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
options: {
|
options: {
|
||||||
|
generatorOpts: {
|
||||||
|
decoratorsBeforeExport: true,
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
'@babel/plugin-syntax-typescript',
|
['@babel/plugin-syntax-typescript', { isTSX: true }],
|
||||||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||||
'@babel/plugin-syntax-jsx',
|
'@babel/plugin-syntax-jsx',
|
||||||
[
|
[
|
||||||
|
@ -114,6 +117,7 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
||||||
transpileOnly: true,
|
transpileOnly: true,
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
allowJs: true,
|
allowJs: true,
|
||||||
|
declaration: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user