# Conflicts: # make-webpack-config.jsbubble
@@ -5,3 +5,4 @@ node_modules | |||||
npm-debug.log* | npm-debug.log* | ||||
.eslintcache | .eslintcache | ||||
package-lock.json | package-lock.json | ||||
*.iml |
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 4 revisions since initial creation in 20 | |||||
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status | Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status | ||||
------------------ | ------------ | -------------------------- | ----- | ------ | ------------------ | ------------ | -------------------------- | ----- | ------ | ||||
3.0.18 | 2017-07-07 | 2.0 | [tag v3.0.18](https://github.com/swagger-api/swagger-ui/tree/v3.0.18) | | |||||
3.0.19 | 2017-07-14 | 2.0 | [tag v3.0.19](https://github.com/swagger-api/swagger-ui/tree/v3.0.19) | | |||||
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) | | 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) | | ||||
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) | | 2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) | | ||||
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) | | 2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) | | ||||
@@ -67,7 +67,6 @@ To help with the migration, here are the currently known issues with 3.X. This l | |||||
- Only part of the [parameters](#parameters) previously supported are available. | - Only part of the [parameters](#parameters) previously supported are available. | ||||
- The JSON Form Editor is not implemented. | - The JSON Form Editor is not implemented. | ||||
- Shebang URL support for operations is missing. | |||||
- Support for `collectionFormat` is partial. | - Support for `collectionFormat` is partial. | ||||
- l10n (translations) is not implemented. | - l10n (translations) is not implemented. | ||||
- Relative path support for external files is not implemented. | - Relative path support for external files is not implemented. | ||||
@@ -82,17 +81,17 @@ To use swagger-ui's bundles, you should take a look at the [source of swagger-ui | |||||
```javascript | ```javascript | ||||
const ui = SwaggerUIBundle({ | const ui = SwaggerUIBundle({ | ||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
SwaggerUIStandalonePreset | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
SwaggerUIStandalonePreset | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
``` | ``` | ||||
#### OAuth2 configuration | #### OAuth2 configuration | ||||
@@ -137,6 +136,7 @@ spec | A JSON object describing the OpenAPI Specification. When used, the `url` | |||||
validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation. | validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation. | ||||
dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger. | dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger. | ||||
oauth2RedirectUrl | OAuth redirect URL | oauth2RedirectUrl | OAuth redirect URL | ||||
tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI. | |||||
operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. | operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. | ||||
configUrl | Configs URL | configUrl | Configs URL | ||||
parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable | parameterMacro | MUST be a function. Function to set default value to parameters. Accepts two arguments parameterMacro(operation, parameter). Operation and parameter are objects passed for context, both remain immutable | ||||
@@ -144,6 +144,8 @@ modelPropertyMacro | MUST be a function. Function to set default values to each | |||||
docExpansion | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default is 'list'. | docExpansion | Controls the default expansion setting for the operations and tags. It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing). The default is 'list'. | ||||
displayOperationId | Controls the display of operationId in operations list. The default is `false`. | displayOperationId | Controls the display of operationId in operations list. The default is `false`. | ||||
displayRequestDuration | Controls the display of the request duration (in milliseconds) for `Try it out` requests. The default is `false`. | displayRequestDuration | Controls the display of the request duration (in milliseconds) for `Try it out` requests. The default is `false`. | ||||
maxDisplayedTags | If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations. | |||||
filter | If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations that are shown. Can be true/false to enable or disable, or an explicit filter string in which case filtering will be enabled using that string as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag. | |||||
### Plugins | ### Plugins | ||||
@@ -11,15 +11,15 @@ | |||||
<style> | <style> | ||||
html | html | ||||
{ | { | ||||
box-sizing: border-box; | |||||
overflow: -moz-scrollbars-vertical; | |||||
overflow-y: scroll; | |||||
box-sizing: border-box; | |||||
overflow: -moz-scrollbars-vertical; | |||||
overflow-y: scroll; | |||||
} | } | ||||
*, | *, | ||||
*:before, | *:before, | ||||
*:after | *:after | ||||
{ | { | ||||
box-sizing: inherit; | |||||
box-sizing: inherit; | |||||
} | } | ||||
body { | body { | ||||
@@ -34,7 +34,7 @@ | |||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0"> | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0"> | ||||
<defs> | <defs> | ||||
<symbol viewBox="0 0 20 20" id="unlocked"> | <symbol viewBox="0 0 20 20" id="unlocked"> | ||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path> | |||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path> | |||||
</symbol> | </symbol> | ||||
<symbol viewBox="0 0 20 20" id="locked"> | <symbol viewBox="0 0 20 20" id="locked"> | ||||
@@ -70,34 +70,34 @@ | |||||
<script src="./swagger-ui-bundle.js"> </script> | <script src="./swagger-ui-bundle.js"> </script> | ||||
<script src="./swagger-ui-standalone-preset.js"> </script> | <script src="./swagger-ui-standalone-preset.js"> </script> | ||||
<script> | <script> | ||||
window.onload = function() { | |||||
window["SwaggerUIBundle"] = window["swagger-ui-bundle"] | |||||
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"] | |||||
// Build a system | |||||
const ui = SwaggerUIBundle({ | |||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
SwaggerUIStandalonePreset | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
window.ui = ui | |||||
ui.initOAuth({ | |||||
clientId: "your-client-id", | |||||
clientSecret: "your-client-secret-if-required", | |||||
realm: "your-realms", | |||||
appName: "your-app-name", | |||||
scopeSeparator: " ", | |||||
additionalQueryStringParams: {} | |||||
}) | |||||
} | |||||
window.onload = function() { | |||||
window["SwaggerUIBundle"] = window["swagger-ui-bundle"] | |||||
window["SwaggerUIStandalonePreset"] = window["swagger-ui-standalone-preset"] | |||||
// Build a system | |||||
const ui = SwaggerUIBundle({ | |||||
url: "http://petstore.swagger.io/v2/swagger.json", | |||||
dom_id: '#swagger-ui', | |||||
presets: [ | |||||
SwaggerUIBundle.presets.apis, | |||||
SwaggerUIStandalonePreset | |||||
], | |||||
plugins: [ | |||||
SwaggerUIBundle.plugins.DownloadUrl | |||||
], | |||||
layout: "StandaloneLayout" | |||||
}) | |||||
window.ui = ui | |||||
ui.initOAuth({ | |||||
clientId: "your-client-id", | |||||
clientSecret: "your-client-secret-if-required", | |||||
realm: "your-realms", | |||||
appName: "your-app-name", | |||||
scopeSeparator: " ", | |||||
additionalQueryStringParams: {} | |||||
}) | |||||
} | |||||
</script> | </script> | ||||
</body> | </body> | ||||
@@ -1 +1 @@ | |||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAsyKA;;;;;;AA8qEA;;;;;;;;;;;;;;;;;;;;;;;;;;AA6nTA;;;;;;;;;;;;;;AAu8JA;;;;;;;;;AAkgnBA;;;;;AAigQA;;;;;;AAqpVA","sourceRoot":""} | |||||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAsyKA;;;;;;AAktEA;;;;;;;;;;;;;;;;;;;;;;;;;;AAkqTA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA4tnBA;;;;;AAmpQA;;;;;;AA+mXA","sourceRoot":""} |
@@ -1 +1 @@ | |||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA40CA;;;;;;AA4kFA","sourceRoot":""} | |||||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA40CA;;;;;;AAqlFA","sourceRoot":""} |
@@ -1 +1 @@ | |||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAopaA","sourceRoot":""} | |||||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAk/aA","sourceRoot":""} |
@@ -0,0 +1,36 @@ | |||||
# Deep linking | |||||
Swagger-UI allows you to deeply link into tags and operations within a spec. When Swagger-UI is provided a URL fragment at runtime, it will automatically expand and scroll to a specified tag or operation. | |||||
## Usage | |||||
👉🏼 Add `deepLinking: true` to your Swagger-UI configuration to enable this functionality. | |||||
When you expand a tag or operation, Swagger-UI will automatically update its URL fragment with a deep link to the item. | |||||
Conversely, when you collapse a tag or operation, Swagger-UI will clear the URL fragment. | |||||
You can also right-click a tag name or operation path in order to copy a link to that tag or operation. | |||||
#### Fragment format | |||||
The fragment is formatted in one of two ways: | |||||
- `#/{tagName}`, to trigger the focus of a specific tag | |||||
- `#/{tagName}/{operationId}`, to trigger the focus of a specific operation within a tag | |||||
`operationId` is the explicit operationId provided in the spec, if one exists. | |||||
Otherwise, Swagger-UI generates an implicit operationId by combining the operation's path and method, and escaping non-alphanumeric characters. | |||||
## FAQ | |||||
> I'm using Swagger-UI in an application that needs control of the URL fragment. How do I disable deep-linking? | |||||
This functionality is disabled by default, but you can pass `deepLinking: false` into Swagger-UI as a configuration item to be sure. | |||||
> Can I link to multiple tags or operations? | |||||
No, this is not supported. | |||||
> Can I collapse everything except the operation or tag I'm linking to? | |||||
Sure - use `docExpansion: none` to collapse all tags and operations. Your deep link will take precedence over the setting, so only the tag or operation you've specified will be expanded. |
@@ -4,6 +4,7 @@ var webpack = require("webpack") | |||||
var ExtractTextPlugin = require("extract-text-webpack-plugin") | var ExtractTextPlugin = require("extract-text-webpack-plugin") | ||||
var deepExtend = require("deep-extend") | var deepExtend = require("deep-extend") | ||||
const {gitDescribeSync} = require("git-describe") | const {gitDescribeSync} = require("git-describe") | ||||
const os = require("os") | |||||
var pkg = require("./package.json") | var pkg = require("./package.json") | ||||
@@ -84,7 +85,9 @@ module.exports = function(rules, options) { | |||||
"buildInfo": JSON.stringify({ | "buildInfo": JSON.stringify({ | ||||
PACKAGE_VERSION: (pkg.version), | PACKAGE_VERSION: (pkg.version), | ||||
GIT_COMMIT: gitInfo.hash, | GIT_COMMIT: gitInfo.hash, | ||||
GIT_DIRTY: gitInfo.dirty | |||||
GIT_DIRTY: gitInfo.dirty, | |||||
HOSTNAME: os.hostname(), | |||||
BUILD_TIME: new Date().toUTCString() | |||||
}) | }) | ||||
})) | })) | ||||
@@ -1,6 +1,6 @@ | |||||
{ | { | ||||
"name": "swagger-ui", | "name": "swagger-ui", | ||||
"version": "3.0.18", | |||||
"version": "3.0.19", | |||||
"main": "dist/swagger-ui.js", | "main": "dist/swagger-ui.js", | ||||
"repository": "git@github.com:swagger-api/swagger-ui.git", | "repository": "git@github.com:swagger-api/swagger-ui.git", | ||||
"contributors": [ | "contributors": [ | ||||
@@ -68,9 +68,10 @@ | |||||
"redux-logger": "*", | "redux-logger": "*", | ||||
"reselect": "2.5.3", | "reselect": "2.5.3", | ||||
"sanitize-html": "^1.14.1", | "sanitize-html": "^1.14.1", | ||||
"scroll-to-element": "^2.0.0", | |||||
"serialize-error": "2.0.0", | "serialize-error": "2.0.0", | ||||
"shallowequal": "0.2.2", | "shallowequal": "0.2.2", | ||||
"swagger-client": "3.0.17", | |||||
"swagger-client": "3.0.18", | |||||
"url-parse": "^1.1.8", | "url-parse": "^1.1.8", | ||||
"whatwg-fetch": "0.11.1", | "whatwg-fetch": "0.11.1", | ||||
"worker-loader": "^0.7.1", | "worker-loader": "^0.7.1", | ||||
@@ -17,16 +17,19 @@ export default class ArrayModel extends Component { | |||||
render(){ | render(){ | ||||
let { getComponent, required, schema, depth, expandDepth } = this.props | let { getComponent, required, schema, depth, expandDepth } = this.props | ||||
let items = schema.get("items") | let items = schema.get("items") | ||||
let title = schema.get("title") || name | |||||
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 ) | let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 ) | ||||
const ModelCollapse = getComponent("ModelCollapse") | const ModelCollapse = getComponent("ModelCollapse") | ||||
const Model = getComponent("Model") | const Model = getComponent("Model") | ||||
return <span className="model"> | |||||
const titleEl = title && | |||||
<span className="model-title"> | <span className="model-title"> | ||||
<span className="model-title__text">{ schema.get("title") }</span> | |||||
<span className="model-title__text">{ title }</span> | |||||
</span> | </span> | ||||
<ModelCollapse collapsed={ depth > expandDepth } collapsedContent="[...]"> | |||||
return <span className="model"> | |||||
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> | |||||
[ | [ | ||||
<span><Model { ...this.props } schema={ items } required={ false }/></span> | <span><Model { ...this.props } schema={ items } required={ false }/></span> | ||||
] | ] | ||||
@@ -13,8 +13,13 @@ export default class BaseLayout extends React.Component { | |||||
getComponent: PropTypes.func.isRequired | getComponent: PropTypes.func.isRequired | ||||
} | } | ||||
onFilterChange =(e) => { | |||||
let {target: {value}} = e | |||||
this.props.layoutActions.updateFilter(value) | |||||
} | |||||
render() { | render() { | ||||
let { specSelectors, specActions, getComponent } = this.props | |||||
let { specSelectors, specActions, getComponent, layoutSelectors } = this.props | |||||
let info = specSelectors.info() | let info = specSelectors.info() | ||||
let url = specSelectors.url() | let url = specSelectors.url() | ||||
@@ -31,6 +36,15 @@ export default class BaseLayout extends React.Component { | |||||
let Row = getComponent("Row") | let Row = getComponent("Row") | ||||
let Col = getComponent("Col") | let Col = getComponent("Col") | ||||
let Errors = getComponent("errors", true) | let Errors = getComponent("errors", true) | ||||
let isLoading = specSelectors.loadingStatus() === "loading" | |||||
let isFailed = specSelectors.loadingStatus() === "failed" | |||||
let filter = layoutSelectors.currentFilter() | |||||
let inputStyle = {} | |||||
if(isFailed) inputStyle.color = "red" | |||||
if(isLoading) inputStyle.color = "#aaa" | |||||
const Schemes = getComponent("schemes") | const Schemes = getComponent("schemes") | ||||
const isSpecEmpty = !specSelectors.specStr() | const isSpecEmpty = !specSelectors.specStr() | ||||
@@ -57,6 +71,7 @@ export default class BaseLayout extends React.Component { | |||||
{ schemes && schemes.size ? ( | { schemes && schemes.size ? ( | ||||
<Schemes schemes={ schemes } specActions={ specActions } /> | <Schemes schemes={ schemes } specActions={ specActions } /> | ||||
) : null } | ) : null } | ||||
{ securityDefinitions ? ( | { securityDefinitions ? ( | ||||
<AuthorizeBtn /> | <AuthorizeBtn /> | ||||
) : null } | ) : null } | ||||
@@ -64,6 +79,15 @@ export default class BaseLayout extends React.Component { | |||||
</div> | </div> | ||||
) : null } | ) : null } | ||||
{ | |||||
filter === null || filter === false ? null : | |||||
<div className="filter-container"> | |||||
<Col className="filter wrapper" mobile={12}> | |||||
<input className="operation-filter-input" placeholder="Filter by tag" type="text" onChange={this.onFilterChange} value={filter === true || filter === "true" ? "" : filter} disabled={isLoading} style={inputStyle} /> | |||||
</Col> | |||||
</div> | |||||
} | |||||
<Row> | <Row> | ||||
<Col mobile={12} desktop={12} > | <Col mobile={12} desktop={12} > | ||||
<Operations/> | <Operations/> | ||||
@@ -5,12 +5,14 @@ export default class ModelCollapse extends Component { | |||||
static propTypes = { | static propTypes = { | ||||
collapsedContent: PropTypes.any, | collapsedContent: PropTypes.any, | ||||
collapsed: PropTypes.bool, | collapsed: PropTypes.bool, | ||||
children: PropTypes.any | |||||
children: PropTypes.any, | |||||
title: PropTypes.element | |||||
} | } | ||||
static defaultProps = { | static defaultProps = { | ||||
collapsedContent: "{...}", | collapsedContent: "{...}", | ||||
collapsed: true, | collapsed: true, | ||||
title: null | |||||
} | } | ||||
constructor(props, context) { | constructor(props, context) { | ||||
@@ -31,11 +33,15 @@ export default class ModelCollapse extends Component { | |||||
} | } | ||||
render () { | render () { | ||||
return (<span> | |||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}> | |||||
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span> | |||||
const {title} = this.props | |||||
return ( | |||||
<span> | |||||
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> } | |||||
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}> | |||||
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span> | |||||
</span> | |||||
{ this.state.collapsed ? this.state.collapsedContent : this.props.children } | |||||
</span> | </span> | ||||
{ this.state.collapsed ? this.state.collapsedContent : this.props.children } | |||||
</span>) | |||||
) | |||||
} | } | ||||
} | } |
@@ -28,7 +28,7 @@ export default class Models extends Component { | |||||
<use xlinkHref="#large-arrow" /> | <use xlinkHref="#large-arrow" /> | ||||
</svg> | </svg> | ||||
</h4> | </h4> | ||||
<Collapse isOpened={showModels} animated> | |||||
<Collapse isOpened={showModels}> | |||||
{ | { | ||||
definitions.entrySeq().map( ( [ name, model ])=>{ | definitions.entrySeq().map( ( [ name, model ])=>{ | ||||
return <div className="model-container" key={ `models-section-${name}` }> | return <div className="model-container" key={ `models-section-${name}` }> | ||||
@@ -38,15 +38,13 @@ export default class ObjectModel extends Component { | |||||
} | } | ||||
</span>) | </span>) | ||||
const titleEl = title && <span className="model-title"> | |||||
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } | |||||
<span className="model-title__text">{ title }</span> | |||||
</span> | |||||
return <span className="model"> | return <span className="model"> | ||||
{ | |||||
title && <span className="model-title"> | |||||
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } | |||||
<span className="model-title__text">{ title }</span> | |||||
</span> | |||||
} | |||||
<ModelCollapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }> | |||||
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }> | |||||
<span className="brace-open object">{ braceOpen }</span> | <span className="brace-open object">{ braceOpen }</span> | ||||
{ | { | ||||
!isRef ? null : <JumpToPathSection name={ name }/> | !isRef ? null : <JumpToPathSection name={ name }/> | ||||
@@ -116,7 +116,8 @@ export default class Operation extends PureComponent { | |||||
specActions, | specActions, | ||||
specSelectors, | specSelectors, | ||||
authActions, | authActions, | ||||
authSelectors | |||||
authSelectors, | |||||
getConfigs | |||||
} = this.props | } = this.props | ||||
let summary = operation.get("summary") | let summary = operation.get("summary") | ||||
@@ -141,6 +142,10 @@ export default class Operation extends PureComponent { | |||||
const Markdown = getComponent( "Markdown" ) | const Markdown = getComponent( "Markdown" ) | ||||
const Schemes = getComponent( "schemes" ) | const Schemes = getComponent( "schemes" ) | ||||
const { deepLinking } = getConfigs() | |||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" | |||||
// Merge in Live Response | // Merge in Live Response | ||||
if(response && response.size > 0) { | if(response && response.size > 0) { | ||||
let notDocumented = !responses.get(String(response.get("status"))) | let notDocumented = !responses.get(String(response.get("status"))) | ||||
@@ -152,13 +157,18 @@ export default class Operation extends PureComponent { | |||||
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) | let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) | ||||
return ( | return ( | ||||
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} > | |||||
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} > | |||||
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > | <div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > | ||||
<span className="opblock-summary-method">{method.toUpperCase()}</span> | |||||
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > | |||||
<span>{path}</span> | |||||
<JumpToPath path={jumpToKey} /> | |||||
</span> | |||||
<span className="opblock-summary-method">{method.toUpperCase()}</span> | |||||
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > | |||||
<a | |||||
className="nostyle" | |||||
onClick={(e) => e.preventDefault()} | |||||
href={ isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : ""} > | |||||
<span>{path}</span> | |||||
</a> | |||||
<JumpToPath path={jumpToKey} /> | |||||
</span> | |||||
{ !showSummary ? null : | { !showSummary ? null : | ||||
<div className="opblock-summary-description"> | <div className="opblock-summary-description"> | ||||
@@ -191,7 +201,9 @@ export default class Operation extends PureComponent { | |||||
<div className="opblock-external-docs-wrapper"> | <div className="opblock-external-docs-wrapper"> | ||||
<h4 className="opblock-title_normal">Find more details</h4> | <h4 className="opblock-title_normal">Find more details</h4> | ||||
<div className="opblock-external-docs"> | <div className="opblock-external-docs"> | ||||
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span> | |||||
<span className="opblock-external-docs__description"> | |||||
<Markdown source={ externalDocs.get("description") } /> | |||||
</span> | |||||
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a> | <a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a> | ||||
</div> | </div> | ||||
</div> : null | </div> : null | ||||
@@ -1,5 +1,8 @@ | |||||
import React from "react" | import React from "react" | ||||
import PropTypes from "prop-types" | import PropTypes from "prop-types" | ||||
import { helpers } from "swagger-client" | |||||
const { opId } = helpers | |||||
export default class Operations extends React.Component { | export default class Operations extends React.Component { | ||||
@@ -33,7 +36,29 @@ export default class Operations extends React.Component { | |||||
const Collapse = getComponent("Collapse") | const Collapse = getComponent("Collapse") | ||||
let showSummary = layoutSelectors.showSummary() | let showSummary = layoutSelectors.showSummary() | ||||
let { docExpansion, displayOperationId, displayRequestDuration } = getConfigs() | |||||
let { | |||||
docExpansion, | |||||
displayOperationId, | |||||
displayRequestDuration, | |||||
maxDisplayedTags, | |||||
deepLinking | |||||
} = getConfigs() | |||||
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" | |||||
let filter = layoutSelectors.currentFilter() | |||||
if (filter) { | |||||
if (filter !== true) { | |||||
taggedOps = taggedOps.filter((tagObj, tag) => { | |||||
return tag.indexOf(filter) !== -1 | |||||
}) | |||||
} | |||||
} | |||||
if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) { | |||||
taggedOps = taggedOps.slice(0, maxDisplayedTags) | |||||
} | |||||
return ( | return ( | ||||
<div> | <div> | ||||
@@ -48,8 +73,16 @@ export default class Operations extends React.Component { | |||||
return ( | return ( | ||||
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}> | <div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}> | ||||
<h4 onClick={() => layoutActions.show(isShownKey, !showTag)} className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }> | |||||
<span>{tag}</span> | |||||
<h4 | |||||
onClick={() => layoutActions.show(isShownKey, !showTag)} | |||||
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" } | |||||
id={isShownKey.join("-")}> | |||||
<a | |||||
className="nostyle" | |||||
onClick={(e) => e.preventDefault()} | |||||
href={ isDeepLinkingEnabled ? `#/${tag}` : ""}> | |||||
<span>{tag}</span> | |||||
</a> | |||||
{ !tagDescription ? null : | { !tagDescription ? null : | ||||
<small> | <small> | ||||
{ tagDescription } | { tagDescription } | ||||
@@ -67,11 +100,14 @@ export default class Operations extends React.Component { | |||||
{ | { | ||||
operations.map( op => { | operations.map( op => { | ||||
const isShownKey = ["operations", op.get("id"), tag] | |||||
const path = op.get("path", "") | const path = op.get("path", "") | ||||
const method = op.get("method", "") | const method = op.get("method", "") | ||||
const jumpToKey = `paths.${path}.${method}` | const jumpToKey = `paths.${path}.${method}` | ||||
const operationId = | |||||
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id") | |||||
const isShownKey = ["operations", tag, operationId] | |||||
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method")) | const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method")) | ||||
const response = specSelectors.responseFor(op.get("path"), op.get("method")) | const response = specSelectors.responseFor(op.get("path"), op.get("method")) | ||||
const request = specSelectors.requestFor(op.get("path"), op.get("method")) | const request = specSelectors.requestFor(op.get("path"), op.get("method")) | ||||
@@ -18,10 +18,12 @@ function Markdown({ source }) { | |||||
return null | return null | ||||
} | } | ||||
return <Remarkable | |||||
options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}} | |||||
source={sanitized} | |||||
></Remarkable> | |||||
return <div className="markdown"> | |||||
<Remarkable | |||||
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}} | |||||
source={sanitized} | |||||
></Remarkable> | |||||
</div> | |||||
} | } | ||||
Markdown.propTypes = { | Markdown.propTypes = { | ||||
@@ -4,19 +4,48 @@ import System from "core/system" | |||||
import win from "core/window" | import win from "core/window" | ||||
import ApisPreset from "core/presets/apis" | import ApisPreset from "core/presets/apis" | ||||
import * as AllPlugins from "core/plugins/all" | import * as AllPlugins from "core/plugins/all" | ||||
import { parseSeach, filterConfigs } from "core/utils" | |||||
const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion", | |||||
"apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl", | |||||
"showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"] | |||||
import { parseSearch, filterConfigs } from "core/utils" | |||||
const CONFIGS = [ | |||||
"url", | |||||
"urls", | |||||
"urls.primaryName", | |||||
"spec", | |||||
"validatorUrl", | |||||
"onComplete", | |||||
"onFailure", | |||||
"authorizations", | |||||
"docExpansion", | |||||
"tagsSorter", | |||||
"maxDisplayedTags", | |||||
"filter", | |||||
"operationsSorter", | |||||
"supportedSubmitMethods", | |||||
"dom_id", | |||||
"defaultModelRendering", | |||||
"oauth2RedirectUrl", | |||||
"showRequestHeaders", | |||||
"custom", | |||||
"modelPropertyMacro", | |||||
"parameterMacro", | |||||
"displayOperationId", | |||||
"displayRequestDuration", | |||||
"deepLinking", | |||||
] | |||||
// eslint-disable-next-line no-undef | // eslint-disable-next-line no-undef | ||||
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION } = buildInfo | |||||
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION, HOSTNAME, BUILD_TIME } = buildInfo | |||||
module.exports = function SwaggerUI(opts) { | module.exports = function SwaggerUI(opts) { | ||||
win.versions = win.versions || {} | win.versions = win.versions || {} | ||||
win.versions.swaggerUi = `${PACKAGE_VERSION}/${GIT_COMMIT || "unknown"}${GIT_DIRTY ? "-dirty" : ""}` | |||||
win.versions.swaggerUi = { | |||||
version: PACKAGE_VERSION, | |||||
gitRevision: GIT_COMMIT, | |||||
gitDirty: GIT_DIRTY, | |||||
buildTimestamp: BUILD_TIME, | |||||
machine: HOSTNAME | |||||
} | |||||
const defaults = { | const defaults = { | ||||
// Some general settings, that we floated to the top | // Some general settings, that we floated to the top | ||||
@@ -26,15 +55,19 @@ module.exports = function SwaggerUI(opts) { | |||||
urls: null, | urls: null, | ||||
layout: "BaseLayout", | layout: "BaseLayout", | ||||
docExpansion: "list", | docExpansion: "list", | ||||
maxDisplayedTags: null, | |||||
filter: null, | |||||
validatorUrl: "https://online.swagger.io/validator", | validatorUrl: "https://online.swagger.io/validator", | ||||
configs: {}, | configs: {}, | ||||
custom: {}, | custom: {}, | ||||
displayOperationId: false, | displayOperationId: false, | ||||
displayRequestDuration: false, | displayRequestDuration: false, | ||||
deepLinking: false, | |||||
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance. | // Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance. | ||||
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest. | // Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest. | ||||
presets: [ | presets: [ | ||||
ApisPreset | |||||
], | ], | ||||
// Plugins; ( loaded after presets ) | // Plugins; ( loaded after presets ) | ||||
@@ -50,7 +83,9 @@ module.exports = function SwaggerUI(opts) { | |||||
store: { }, | store: { }, | ||||
} | } | ||||
const constructorConfig = deepExtend({}, defaults, opts) | |||||
let queryConfig = parseSearch() | |||||
const constructorConfig = deepExtend({}, defaults, opts, queryConfig) | |||||
const storeConfigs = deepExtend({}, constructorConfig.store, { | const storeConfigs = deepExtend({}, constructorConfig.store, { | ||||
system: { | system: { | ||||
@@ -59,7 +94,8 @@ module.exports = function SwaggerUI(opts) { | |||||
plugins: constructorConfig.presets, | plugins: constructorConfig.presets, | ||||
state: { | state: { | ||||
layout: { | layout: { | ||||
layout: constructorConfig.layout | |||||
layout: constructorConfig.layout, | |||||
filter: constructorConfig.filter | |||||
}, | }, | ||||
spec: { | spec: { | ||||
spec: "", | spec: "", | ||||
@@ -80,7 +116,6 @@ module.exports = function SwaggerUI(opts) { | |||||
store.register([constructorConfig.plugins, inlinePlugin]) | store.register([constructorConfig.plugins, inlinePlugin]) | ||||
var system = store.getSystem() | var system = store.getSystem() | ||||
let queryConfig = parseSeach() | |||||
system.initOAuth = system.authActions.configureAuth | system.initOAuth = system.authActions.configureAuth | ||||
@@ -0,0 +1 @@ | |||||
See `docs/deep-linking.md`. |
@@ -0,0 +1,7 @@ | |||||
export const setHash = (value) => { | |||||
if(value) { | |||||
return history.pushState(null, null, `#${value}`) | |||||
} else { | |||||
return window.location.hash = "" | |||||
} | |||||
} |
@@ -0,0 +1,18 @@ | |||||
// import reducers from "./reducers" | |||||
// import * as actions from "./actions" | |||||
// import * as selectors from "./selectors" | |||||
import * as specWrapActions from "./spec-wrap-actions" | |||||
import * as layoutWrapActions from "./layout-wrap-actions" | |||||
export default function() { | |||||
return { | |||||
statePlugins: { | |||||
spec: { | |||||
wrapActions: specWrapActions | |||||
}, | |||||
layout: { | |||||
wrapActions: layoutWrapActions | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
import { setHash } from "./helpers" | |||||
export const show = (ori, { getConfigs }) => (...args) => { | |||||
ori(...args) | |||||
const isDeepLinkingEnabled = getConfigs().deepLinking | |||||
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") { | |||||
return | |||||
} | |||||
try { | |||||
let [thing, shown] = args | |||||
let [type] = thing | |||||
if(type === "operations-tag" || type === "operations") { | |||||
if(!shown) { | |||||
return setHash("/") | |||||
} | |||||
if(type === "operations") { | |||||
let [, tag, operationId] = thing | |||||
setHash(`/${tag}/${operationId}`) | |||||
} | |||||
if(type === "operations-tag") { | |||||
let [, tag] = thing | |||||
setHash(`/${tag}`) | |||||
} | |||||
} | |||||
} catch(e) { | |||||
// This functionality is not mission critical, so if something goes wrong | |||||
// we'll just move on | |||||
console.error(e) | |||||
} | |||||
} |
@@ -0,0 +1,51 @@ | |||||
import scrollTo from "scroll-to-element" | |||||
const SCROLL_OFFSET = -5 | |||||
let hasHashBeenParsed = false | |||||
export const updateResolved = (ori, { layoutActions, getConfigs }) => (...args) => { | |||||
ori(...args) | |||||
const isDeepLinkingEnabled = getConfigs().deepLinking | |||||
if(!isDeepLinkingEnabled || isDeepLinkingEnabled === "false") { | |||||
return | |||||
} | |||||
if(window.location.hash && !hasHashBeenParsed ) { | |||||
let hash = window.location.hash.slice(1) // # is first character | |||||
if(hash[0] === "!") { | |||||
// Parse UI 2.x shebangs | |||||
hash = hash.slice(1) | |||||
} | |||||
if(hash[0] === "/") { | |||||
// "/pet/addPet" => "pet/addPet" | |||||
// makes the split result cleaner | |||||
// also handles forgotten leading slash | |||||
hash = hash.slice(1) | |||||
} | |||||
let [tag, operationId] = hash.split("/") | |||||
if(tag && operationId) { | |||||
// Pre-expand and scroll to the operation | |||||
layoutActions.show(["operations-tag", tag], true) | |||||
layoutActions.show(["operations", tag, operationId], true) | |||||
scrollTo(`#operations-${tag}-${operationId}`, { | |||||
offset: SCROLL_OFFSET | |||||
}) | |||||
} else if(tag) { | |||||
// Pre-expand and scroll to the tag | |||||
layoutActions.show(["operations-tag", tag], true) | |||||
scrollTo(`#operations-tag-${tag}`, { | |||||
offset: SCROLL_OFFSET | |||||
}) | |||||
} | |||||
} | |||||
hasHashBeenParsed = true | |||||
} |
@@ -1,6 +1,7 @@ | |||||
import { normalizeArray } from "core/utils" | import { normalizeArray } from "core/utils" | ||||
export const UPDATE_LAYOUT = "layout_update_layout" | export const UPDATE_LAYOUT = "layout_update_layout" | ||||
export const UPDATE_FILTER = "layout_update_filter" | |||||
export const UPDATE_MODE = "layout_update_mode" | export const UPDATE_MODE = "layout_update_mode" | ||||
export const SHOW = "layout_show" | export const SHOW = "layout_show" | ||||
@@ -13,6 +14,13 @@ export function updateLayout(layout) { | |||||
} | } | ||||
} | } | ||||
export function updateFilter(filter) { | |||||
return { | |||||
type: UPDATE_FILTER, | |||||
payload: filter | |||||
} | |||||
} | |||||
export function show(thing, shown=true) { | export function show(thing, shown=true) { | ||||
thing = normalizeArray(thing) | thing = normalizeArray(thing) | ||||
return { | return { | ||||
@@ -1,5 +1,6 @@ | |||||
import { | import { | ||||
UPDATE_LAYOUT, | UPDATE_LAYOUT, | ||||
UPDATE_FILTER, | |||||
UPDATE_MODE, | UPDATE_MODE, | ||||
SHOW | SHOW | ||||
} from "./actions" | } from "./actions" | ||||
@@ -8,6 +9,8 @@ export default { | |||||
[UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload), | [UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload), | ||||
[UPDATE_FILTER]: (state, action) => state.set("filter", action.payload), | |||||
[SHOW]: (state, action) => { | [SHOW]: (state, action) => { | ||||
let thing = action.payload.thing | let thing = action.payload.thing | ||||
let shown = action.payload.shown | let shown = action.payload.shown | ||||
@@ -5,6 +5,8 @@ const state = state => state | |||||
export const current = state => state.get("layout") | export const current = state => state.get("layout") | ||||
export const currentFilter = state => state.get("filter") | |||||
export const isShown = (state, thing, def) => { | export const isShown = (state, thing, def) => { | ||||
thing = normalizeArray(thing) | thing = normalizeArray(thing) | ||||
return Boolean(state.getIn(["shown", ...thing], def)) | return Boolean(state.getIn(["shown", ...thing], def)) | ||||
@@ -200,15 +200,22 @@ export const operationsWithTags = createSelector( | |||||
} | } | ||||
) | ) | ||||
export const taggedOperations = ( state ) =>( { getConfigs } ) => { | |||||
let { operationsSorter }= getConfigs() | |||||
return operationsWithTags(state).map((ops, tag) => { | |||||
let sortFn = typeof operationsSorter === "function" ? operationsSorter | |||||
: sorters.operationsSorter[operationsSorter] | |||||
let operations = !sortFn ? ops : ops.sort(sortFn) | |||||
export const taggedOperations = (state) => ({ getConfigs }) => { | |||||
let { tagsSorter, operationsSorter } = getConfigs() | |||||
return operationsWithTags(state) | |||||
.sortBy( | |||||
(val, key) => key, // get the name of the tag to be passed to the sorter | |||||
(tagA, tagB) => { | |||||
let sortFn = (typeof tagsSorter === "function" ? tagsSorter : sorters.tagsSorter[ tagsSorter ]) | |||||
return (!sortFn ? null : sortFn(tagA, tagB)) | |||||
} | |||||
) | |||||
.map((ops, tag) => { | |||||
let sortFn = (typeof operationsSorter === "function" ? operationsSorter : sorters.operationsSorter[ operationsSorter ]) | |||||
let operations = (!sortFn ? ops : ops.sort(sortFn)) | |||||
return Map({tagDetails: tagDetails(state, tag), operations: operations})}) | |||||
return Map({ tagDetails: tagDetails(state, tag), operations: operations }) | |||||
}) | |||||
} | } | ||||
export const responses = createSelector( | export const responses = createSelector( | ||||
@@ -277,13 +284,14 @@ export function parametersIncludeType(parameters, typeValue="") { | |||||
export function contentTypeValues(state, pathMethod) { | export function contentTypeValues(state, pathMethod) { | ||||
let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) | let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) | ||||
const parameters = op.get("parameters") || new List() | const parameters = op.get("parameters") || new List() | ||||
const requestContentType = ( | const requestContentType = ( | ||||
parametersIncludeType(parameters, "file") ? "multipart/form-data" | |||||
: parametersIncludeIn(parameters, "formData") ? "application/x-www-form-urlencoded" | |||||
: op.get("consumes_value") | |||||
op.get("consumes_value") ? op.get("consumes_value") | |||||
: parametersIncludeType(parameters, "file") ? "multipart/form-data" | |||||
: parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded" | |||||
: undefined | |||||
) | ) | ||||
return fromJS({ | return fromJS({ | ||||
requestContentType, | requestContentType, | ||||
responseContentType: op.get("produces_value") | responseContentType: op.get("produces_value") | ||||
@@ -10,6 +10,7 @@ import auth from "core/plugins/auth" | |||||
import util from "core/plugins/util" | import util from "core/plugins/util" | ||||
import SplitPaneModePlugin from "core/plugins/split-pane-mode" | import SplitPaneModePlugin from "core/plugins/split-pane-mode" | ||||
import downloadUrlPlugin from "core/plugins/download-url" | import downloadUrlPlugin from "core/plugins/download-url" | ||||
import deepLinkingPlugin from "core/plugins/deep-linking" | |||||
import App from "core/components/app" | import App from "core/components/app" | ||||
import AuthorizationPopup from "core/components/auth/authorization-popup" | import AuthorizationPopup from "core/components/auth/authorization-popup" | ||||
@@ -131,6 +132,7 @@ export default function() { | |||||
auth, | auth, | ||||
ast, | ast, | ||||
SplitPaneModePlugin, | SplitPaneModePlugin, | ||||
downloadUrlPlugin | |||||
downloadUrlPlugin, | |||||
deepLinkingPlugin | |||||
] | ] | ||||
} | } |
@@ -228,13 +228,13 @@ export function highlight (el) { | |||||
var reset = function(el) { | var reset = function(el) { | ||||
var text = el.textContent, | var text = el.textContent, | ||||
pos = 0, // current position | |||||
pos = 0, // current position | |||||
next1 = text[0], // next character | next1 = text[0], // next character | ||||
chr = 1, // current character | |||||
prev1, // previous character | |||||
prev2, // the one before the previous | |||||
token = // current token content | |||||
el.innerHTML = "", // (and cleaning the node) | |||||
chr = 1, // current character | |||||
prev1, // previous character | |||||
prev2, // the one before the previous | |||||
token = // current token content | |||||
el.innerHTML = "", // (and cleaning the node) | |||||
// current token type: | // current token type: | ||||
// 0: anything else (whitespaces / newlines) | // 0: anything else (whitespaces / newlines) | ||||
@@ -274,11 +274,11 @@ export function highlight (el) { | |||||
(tokenType > 8 && chr == "\n") || | (tokenType > 8 && chr == "\n") || | ||||
[ // finalize conditions for other token types | [ // finalize conditions for other token types | ||||
// 0: whitespaces | // 0: whitespaces | ||||
/\S/[test](chr), // merged together | |||||
/\S/[test](chr), // merged together | |||||
// 1: operators | // 1: operators | ||||
1, // consist of a single character | |||||
1, // consist of a single character | |||||
// 2: braces | // 2: braces | ||||
1, // consist of a single character | |||||
1, // consist of a single character | |||||
// 3: (key)word | // 3: (key)word | ||||
!/[$\w]/[test](chr), | !/[$\w]/[test](chr), | ||||
// 4: regex | // 4: regex | ||||
@@ -341,12 +341,12 @@ export function highlight (el) { | |||||
// condition) | // condition) | ||||
tokenType = 11 | tokenType = 11 | ||||
while (![ | while (![ | ||||
1, // 0: whitespace | |||||
1, // 0: whitespace | |||||
// 1: operator or braces | // 1: operator or braces | ||||
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape | |||||
/[\])]/[test](chr), // 2: closing brace | |||||
/[$\w]/[test](chr), // 3: (key)word | |||||
chr == "/" && // 4: regex | |||||
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape | |||||
/[\])]/[test](chr), // 2: closing brace | |||||
/[$\w]/[test](chr), // 3: (key)word | |||||
chr == "/" && // 4: regex | |||||
// previous token was an | // previous token was an | ||||
// opening brace or an | // opening brace or an | ||||
// operator (otherwise | // operator (otherwise | ||||
@@ -355,13 +355,13 @@ export function highlight (el) { | |||||
// workaround for xml | // workaround for xml | ||||
// closing tags | // closing tags | ||||
prev1 != "<", | prev1 != "<", | ||||
chr == "\"", // 5: string with " | |||||
chr == "'", // 6: string with ' | |||||
chr == "\"", // 5: string with " | |||||
chr == "'", // 6: string with ' | |||||
// 7: xml comment | // 7: xml comment | ||||
chr+next1+text[pos+1]+text[pos+2] == "<!--", | chr+next1+text[pos+1]+text[pos+2] == "<!--", | ||||
chr+next1 == "/*", // 8: multiline comment | |||||
chr+next1 == "//", // 9: single-line comment | |||||
chr == "#" // 10: hash-style comment | |||||
chr+next1 == "/*", // 8: multiline comment | |||||
chr+next1 == "//", // 9: single-line comment | |||||
chr == "#" // 10: hash-style comment | |||||
][--tokenType]); | ][--tokenType]); | ||||
} | } | ||||
@@ -451,13 +451,13 @@ export const propChecker = (props, nextProps, objectList=[], ignoreList=[]) => { | |||||
} | } | ||||
export const validateNumber = ( val ) => { | export const validateNumber = ( val ) => { | ||||
if ( !/^-?\d+(\.?\d+)?$/.test(val)) { | |||||
if (!/^-?\d+(\.?\d+)?$/.test(val)) { | |||||
return "Value must be a number" | return "Value must be a number" | ||||
} | } | ||||
} | } | ||||
export const validateInteger = ( val ) => { | export const validateInteger = ( val ) => { | ||||
if ( !/^-?\d+$/.test(val)) { | |||||
if (!/^-?\d+$/.test(val)) { | |||||
return "Value must be an integer" | return "Value must be an integer" | ||||
} | } | ||||
} | } | ||||
@@ -485,6 +485,10 @@ export const validateParam = (param, isXml) => { | |||||
return errors | return errors | ||||
} | } | ||||
if ( value === null || value === undefined ) { | |||||
return errors | |||||
} | |||||
if ( type === "number" ) { | if ( type === "number" ) { | ||||
let err = validateNumber(value) | let err = validateNumber(value) | ||||
if (!err) return errors | if (!err) return errors | ||||
@@ -542,7 +546,7 @@ export const getSampleSchema = (schema, contentType="", config={}) => { | |||||
return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2) | return JSON.stringify(memoizedSampleFromSchema(schema, config), null, 2) | ||||
} | } | ||||
export const parseSeach = () => { | |||||
export const parseSearch = () => { | |||||
let map = {} | let map = {} | ||||
let search = window.location.search | let search = window.location.search | ||||
@@ -574,6 +578,9 @@ export const sorters = { | |||||
operationsSorter: { | operationsSorter: { | ||||
alpha: (a, b) => a.get("path").localeCompare(b.get("path")), | alpha: (a, b) => a.get("path").localeCompare(b.get("path")), | ||||
method: (a, b) => a.get("method").localeCompare(b.get("method")) | method: (a, b) => a.get("method").localeCompare(b.get("method")) | ||||
}, | |||||
tagsSorter: { | |||||
alpha: (a, b) => a.localeCompare(b) | |||||
} | } | ||||
} | } | ||||
@@ -6,6 +6,10 @@ import Logo from "./logo_small.png" | |||||
export default class Topbar extends React.Component { | export default class Topbar extends React.Component { | ||||
static propTypes = { | |||||
layoutActions: PropTypes.object.isRequired | |||||
} | |||||
constructor(props, context) { | constructor(props, context) { | ||||
super(props, context) | super(props, context) | ||||
this.state = { url: props.specSelectors.url(), selectedIndex: 0 } | this.state = { url: props.specSelectors.url(), selectedIndex: 0 } | ||||
@@ -80,6 +84,11 @@ export default class Topbar extends React.Component { | |||||
} | } | ||||
} | } | ||||
onFilterChange =(e) => { | |||||
let {target: {value}} = e | |||||
this.props.layoutActions.updateFilter(value) | |||||
} | |||||
render() { | render() { | ||||
let { getComponent, specSelectors, getConfigs } = this.props | let { getComponent, specSelectors, getConfigs } = this.props | ||||
const Button = getComponent("Button") | const Button = getComponent("Button") | ||||
@@ -29,7 +29,7 @@ export default class StandaloneLayout extends React.Component { | |||||
return ( | return ( | ||||
<Container className='swagger-ui'> | <Container className='swagger-ui'> | ||||
{ Topbar ? <Topbar/> : null } | |||||
{ Topbar ? <Topbar /> : null } | |||||
{ loadingStatus === "loading" && | { loadingStatus === "loading" && | ||||
<div className="info"> | <div className="info"> | ||||
<h4 className="title">Loading...</h4> | <h4 className="title">Loading...</h4> | ||||
@@ -45,7 +45,7 @@ export default class StandaloneLayout extends React.Component { | |||||
<h4 className="title">Failed to load config.</h4> | <h4 className="title">Failed to load config.</h4> | ||||
</div> | </div> | ||||
} | } | ||||
{ !loadingStatus || loadingStatus === "success" && <BaseLayout/> } | |||||
{ !loadingStatus || loadingStatus === "success" && <BaseLayout /> } | |||||
<Row> | <Row> | ||||
<Col> | <Col> | ||||
<OnlineValidatorBadge /> | <OnlineValidatorBadge /> | ||||
@@ -45,6 +45,7 @@ body | |||||
.opblock-tag | .opblock-tag | ||||
{ | { | ||||
display: flex; | display: flex; | ||||
align-items: center; | |||||
padding: 10px 20px 10px 10px; | padding: 10px 20px 10px 10px; | ||||
@@ -53,8 +54,6 @@ body | |||||
border-bottom: 1px solid rgba(#3b4151, .3); | border-bottom: 1px solid rgba(#3b4151, .3); | ||||
align-items: center; | |||||
&:hover | &:hover | ||||
{ | { | ||||
background: rgba(#000,.02); | background: rgba(#000,.02); | ||||
@@ -106,9 +105,10 @@ body | |||||
font-size: 14px; | font-size: 14px; | ||||
font-weight: normal; | font-weight: normal; | ||||
flex: 1; | |||||
padding: 0 10px; | padding: 0 10px; | ||||
flex: 1; | |||||
@include text_body(); | @include text_body(); | ||||
} | } | ||||
} | } | ||||
@@ -134,6 +134,8 @@ body | |||||
transition: all .5s; | transition: all .5s; | ||||
} | } | ||||
.opblock | .opblock | ||||
{ | { | ||||
margin: 0 0 15px 0; | margin: 0 0 15px 0; | ||||
@@ -154,24 +156,23 @@ body | |||||
.opblock-section-header | .opblock-section-header | ||||
{ | { | ||||
display: flex; | display: flex; | ||||
align-items: center; | |||||
padding: 8px 20px; | padding: 8px 20px; | ||||
background: rgba(#fff,.8); | background: rgba(#fff,.8); | ||||
box-shadow: 0 1px 2px rgba(#000,.1); | box-shadow: 0 1px 2px rgba(#000,.1); | ||||
align-items: center; | |||||
label | label | ||||
{ | { | ||||
font-size: 12px; | font-size: 12px; | ||||
font-weight: bold; | font-weight: bold; | ||||
display: flex; | display: flex; | ||||
align-items: center; | |||||
margin: 0; | margin: 0; | ||||
align-items: center; | |||||
@include text_headline(); | @include text_headline(); | ||||
span | span | ||||
@@ -184,9 +185,10 @@ body | |||||
{ | { | ||||
font-size: 14px; | font-size: 14px; | ||||
flex: 1; | |||||
margin: 0; | margin: 0; | ||||
flex: 1; | |||||
@include text_headline(); | @include text_headline(); | ||||
} | } | ||||
} | } | ||||
@@ -215,11 +217,11 @@ body | |||||
font-size: 16px; | font-size: 16px; | ||||
display: flex; | display: flex; | ||||
align-items: center; | |||||
padding: 0 10px; | padding: 0 10px; | ||||
@include text_code(); | @include text_code(); | ||||
align-items: center; | |||||
.view-line-link | .view-line-link | ||||
{ | { | ||||
@@ -258,18 +260,18 @@ body | |||||
font-size: 13px; | font-size: 13px; | ||||
flex: 1; | flex: 1; | ||||
@include text_body(); | @include text_body(); | ||||
} | } | ||||
.opblock-summary | .opblock-summary | ||||
{ | { | ||||
display: flex; | display: flex; | ||||
align-items: center; | |||||
padding: 5px; | padding: 5px; | ||||
cursor: pointer; | cursor: pointer; | ||||
align-items: center; | |||||
} | } | ||||
&.opblock-post | &.opblock-post | ||||
@@ -316,12 +318,24 @@ body | |||||
.opblock-schemes | .opblock-schemes | ||||
{ | { | ||||
padding: 8px 20px; | |||||
padding: 8px 20px; | |||||
.schemes-title | |||||
{ | |||||
padding: 0 10px 0 0; | |||||
} | |||||
} | |||||
} | |||||
.filter | |||||
{ | |||||
.operation-filter-input | |||||
{ | |||||
width: 100%; | |||||
margin: 20px 0; | |||||
padding: 10px 10px; | |||||
.schemes-title | |||||
{ | |||||
padding: 0 10px 0 0; | |||||
} | |||||
border: 2px solid #d8dde7; | |||||
} | } | ||||
} | } | ||||
@@ -376,6 +390,7 @@ body | |||||
} | } | ||||
.opblock-description-wrapper, | .opblock-description-wrapper, | ||||
.opblock-external-docs-wrapper, | |||||
.opblock-title_normal | .opblock-title_normal | ||||
{ | { | ||||
font-size: 12px; | font-size: 12px; | ||||
@@ -404,6 +419,12 @@ body | |||||
} | } | ||||
} | } | ||||
.opblock-external-docs-wrapper { | |||||
h4 { | |||||
padding-left: 0px; | |||||
} | |||||
} | |||||
.execute-wrapper | .execute-wrapper | ||||
{ | { | ||||
padding: 20px; | padding: 20px; | ||||
@@ -498,13 +519,11 @@ body | |||||
margin: 0; | margin: 0; | ||||
padding: 10px; | padding: 10px; | ||||
white-space: pre-wrap; | |||||
word-wrap: break-word; | word-wrap: break-word; | ||||
word-break: break-all; | word-break: break-all; | ||||
word-break: break-word; | word-break: break-word; | ||||
hyphens: auto; | hyphens: auto; | ||||
white-space: pre-wrap; | |||||
border-radius: 4px; | border-radius: 4px; | ||||
background: #41444e; | background: #41444e; | ||||
@@ -533,10 +552,9 @@ body | |||||
.schemes | .schemes | ||||
{ | { | ||||
display: flex; | display: flex; | ||||
align-items: center; | align-items: center; | ||||
> label | |||||
> label | |||||
{ | { | ||||
font-size: 12px; | font-size: 12px; | ||||
font-weight: bold; | font-weight: bold; | ||||
@@ -624,3 +642,25 @@ body | |||||
opacity: 0; | opacity: 0; | ||||
} | } | ||||
} | } | ||||
section | |||||
{ | |||||
h3 | |||||
{ | |||||
@include text_headline(); | |||||
} | |||||
} | |||||
a.nostyle { | |||||
text-decoration: inherit; | |||||
color: inherit; | |||||
cursor: auto; | |||||
display: inline; | |||||
&:visited { | |||||
text-decoration: inherit; | |||||
color: inherit; | |||||
cursor: auto; | |||||
} | |||||
} |
@@ -79,6 +79,10 @@ | |||||
border-radius: 4px; | border-radius: 4px; | ||||
background: rgba(#000,.7); | background: rgba(#000,.7); | ||||
} | } | ||||
p { | |||||
margin: 0 0 1em 0; | |||||
} | |||||
} | } | ||||
@@ -43,7 +43,6 @@ | |||||
margin: 0; | margin: 0; | ||||
border: 2px solid #547f00; | border: 2px solid #547f00; | ||||
border-radius: 4px 0 0 4px; | |||||
outline: none; | outline: none; | ||||
} | } | ||||
@@ -10,4 +10,8 @@ try { | |||||
// for more information. | // for more information. | ||||
} | } | ||||
// `absolutePath` and `getAbsoluteFSPath` are both here because at one point, | |||||
// we documented having one and actually implemented the other. | |||||
// They were both retained so we don't break anyone's code. | |||||
module.exports.absolutePath = require("./absolute-path.js") | module.exports.absolutePath = require("./absolute-path.js") | ||||
module.exports.getAbsoluteFSPath = require("./absolute-path.js") |
@@ -52,7 +52,6 @@ describe("spec plugin - selectors", function(){ | |||||
}) | }) | ||||
describe("contentTypeValues", function(){ | describe("contentTypeValues", function(){ | ||||
it("should return { requestContentType, responseContentType } from an operation", function(){ | it("should return { requestContentType, responseContentType } from an operation", function(){ | ||||
// Given | // Given | ||||
let state = fromJS({ | let state = fromJS({ | ||||
@@ -77,6 +76,73 @@ describe("spec plugin - selectors", function(){ | |||||
}) | }) | ||||
}) | }) | ||||
it("should prioritize consumes value first from an operation", function(){ | |||||
// Given | |||||
let state = fromJS({ | |||||
resolved: { | |||||
paths: { | |||||
"/one": { | |||||
get: { | |||||
"consumes_value": "one", | |||||
"parameters": [{ | |||||
"type": "file" | |||||
}], | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
// When | |||||
let contentTypes = contentTypeValues(state, [ "/one", "get" ]) | |||||
// Then | |||||
expect(contentTypes.toJS().requestContentType).toEqual("one") | |||||
}) | |||||
it("should fallback to multipart/form-data if there is no consumes value but there is a file parameter", function(){ | |||||
// Given | |||||
let state = fromJS({ | |||||
resolved: { | |||||
paths: { | |||||
"/one": { | |||||
get: { | |||||
"parameters": [{ | |||||
"type": "file" | |||||
}], | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
// When | |||||
let contentTypes = contentTypeValues(state, [ "/one", "get" ]) | |||||
// Then | |||||
expect(contentTypes.toJS().requestContentType).toEqual("multipart/form-data") | |||||
}) | |||||
it("should fallback to application/x-www-form-urlencoded if there is no consumes value, no file parameter, but there is a formData parameter", function(){ | |||||
// Given | |||||
let state = fromJS({ | |||||
resolved: { | |||||
paths: { | |||||
"/one": { | |||||
get: { | |||||
"parameters": [{ | |||||
"type": "formData" | |||||
}], | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}) | |||||
// When | |||||
let contentTypes = contentTypeValues(state, [ "/one", "get" ]) | |||||
// Then | |||||
expect(contentTypes.toJS().requestContentType).toEqual("application/x-www-form-urlencoded") | |||||
}) | |||||
it("should be ok, if no operation found", function(){ | it("should be ok, if no operation found", function(){ | ||||
// Given | // Given | ||||
let state = fromJS({ }) | let state = fromJS({ }) | ||||
@@ -214,6 +214,7 @@ describe("utils", function(){ | |||||
}) | }) | ||||
it("validates numbers", function() { | it("validates numbers", function() { | ||||
// string instead of a number | |||||
param = fromJS({ | param = fromJS({ | ||||
required: false, | required: false, | ||||
type: "number", | type: "number", | ||||
@@ -221,9 +222,28 @@ describe("utils", function(){ | |||||
}) | }) | ||||
result = validateParam( param, false ) | result = validateParam( param, false ) | ||||
expect( result ).toEqual( ["Value must be a number"] ) | expect( result ).toEqual( ["Value must be a number"] ) | ||||
// undefined value | |||||
param = fromJS({ | |||||
required: false, | |||||
type: "number", | |||||
value: undefined | |||||
}) | |||||
result = validateParam( param, false ) | |||||
expect( result ).toEqual( [] ) | |||||
// null value | |||||
param = fromJS({ | |||||
required: false, | |||||
type: "number", | |||||
value: null | |||||
}) | |||||
result = validateParam( param, false ) | |||||
expect( result ).toEqual( [] ) | |||||
}) | }) | ||||
it("validates integers", function() { | it("validates integers", function() { | ||||
// string instead of integer | |||||
param = fromJS({ | param = fromJS({ | ||||
required: false, | required: false, | ||||
type: "integer", | type: "integer", | ||||
@@ -231,6 +251,24 @@ describe("utils", function(){ | |||||
}) | }) | ||||
result = validateParam( param, false ) | result = validateParam( param, false ) | ||||
expect( result ).toEqual( ["Value must be an integer"] ) | expect( result ).toEqual( ["Value must be an integer"] ) | ||||
// undefined value | |||||
param = fromJS({ | |||||
required: false, | |||||
type: "integer", | |||||
value: undefined | |||||
}) | |||||
result = validateParam( param, false ) | |||||
expect( result ).toEqual( [] ) | |||||
// null value | |||||
param = fromJS({ | |||||
required: false, | |||||
type: "integer", | |||||
value: null | |||||
}) | |||||
result = validateParam( param, false ) | |||||
expect( result ).toEqual( [] ) | |||||
}) | }) | ||||
it("validates arrays", function() { | it("validates arrays", function() { | ||||