Skip to content

Commit 5a2d578

Browse files
authored
Merge branch 'master' into master
2 parents 7462d06 + 4a70ea0 commit 5a2d578

File tree

20 files changed

+317
-55
lines changed

20 files changed

+317
-55
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ To help with the migration, here are the currently known issues with 3.X. This l
6767

6868
- Only part of the [parameters](#parameters) previously supported are available.
6969
- The JSON Form Editor is not implemented.
70-
- Shebang URL support for operations is missing.
7170
- Support for `collectionFormat` is partial.
7271
- l10n (translations) is not implemented.
7372
- Relative path support for external files is not implemented.

docs/deep-linking.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Deep linking
2+
3+
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.
4+
5+
## Usage
6+
7+
👉🏼 Add `deepLinking: true` to your Swagger-UI configuration to enable this functionality.
8+
9+
When you expand a tag or operation, Swagger-UI will automatically update its URL fragment with a deep link to the item.
10+
Conversely, when you collapse a tag or operation, Swagger-UI will clear the URL fragment.
11+
12+
You can also right-click a tag name or operation path in order to copy a link to that tag or operation.
13+
14+
#### Fragment format
15+
16+
The fragment is formatted in one of two ways:
17+
18+
- `#/{tagName}`, to trigger the focus of a specific tag
19+
- `#/{tagName}/{operationId}`, to trigger the focus of a specific operation within a tag
20+
21+
`operationId` is the explicit operationId provided in the spec, if one exists.
22+
Otherwise, Swagger-UI generates an implicit operationId by combining the operation's path and method, and escaping non-alphanumeric characters.
23+
24+
## FAQ
25+
26+
> I'm using Swagger-UI in an application that needs control of the URL fragment. How do I disable deep-linking?
27+
28+
This functionality is disabled by default, but you can pass `deepLinking: false` into Swagger-UI as a configuration item to be sure.
29+
30+
> Can I link to multiple tags or operations?
31+
32+
No, this is not supported.
33+
34+
> Can I collapse everything except the operation or tag I'm linking to?
35+
36+
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.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"redux-logger": "*",
6969
"reselect": "2.5.3",
7070
"sanitize-html": "^1.14.1",
71+
"scroll-to-element": "^2.0.0",
7172
"serialize-error": "2.0.0",
7273
"shallowequal": "0.2.2",
7374
"swagger-client": "3.0.17",

src/core/components/array-model.jsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ export default class ArrayModel extends Component {
1717
render(){
1818
let { getComponent, required, schema, depth, expandDepth } = this.props
1919
let items = schema.get("items")
20+
let title = schema.get("title") || name
2021
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
2122

2223
const ModelCollapse = getComponent("ModelCollapse")
2324
const Model = getComponent("Model")
2425

25-
return <span className="model">
26+
const titleEl = title &&
2627
<span className="model-title">
27-
<span className="model-title__text">{ schema.get("title") }</span>
28+
<span className="model-title__text">{ title }</span>
2829
</span>
29-
<ModelCollapse collapsed={ depth > expandDepth } collapsedContent="[...]">
30+
31+
return <span className="model">
32+
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
3033
[
3134
<span><Model { ...this.props } schema={ items } required={ false }/></span>
3235
]

src/core/components/model-collapse.jsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ export default class ModelCollapse extends Component {
55
static propTypes = {
66
collapsedContent: PropTypes.any,
77
collapsed: PropTypes.bool,
8-
children: PropTypes.any
8+
children: PropTypes.any,
9+
title: PropTypes.element
910
}
1011

1112
static defaultProps = {
1213
collapsedContent: "{...}",
1314
collapsed: true,
15+
title: null
1416
}
1517

1618
constructor(props, context) {
@@ -31,11 +33,15 @@ export default class ModelCollapse extends Component {
3133
}
3234

3335
render () {
34-
return (<span>
35-
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
36-
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
36+
const {title} = this.props
37+
return (
38+
<span>
39+
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
40+
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
41+
<span className={ "model-toggle" + ( this.state.collapsed ? " collapsed" : "" ) }></span>
42+
</span>
43+
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
3744
</span>
38-
{ this.state.collapsed ? this.state.collapsedContent : this.props.children }
39-
</span>)
45+
)
4046
}
4147
}

src/core/components/object-model.jsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,13 @@ export default class ObjectModel extends Component {
3838
}
3939
</span>)
4040

41+
const titleEl = title && <span className="model-title">
42+
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
43+
<span className="model-title__text">{ title }</span>
44+
</span>
4145

4246
return <span className="model">
43-
{
44-
title && <span className="model-title">
45-
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
46-
<span className="model-title__text">{ title }</span>
47-
</span>
48-
}
49-
<ModelCollapse collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
47+
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent={ collapsedContent }>
5048
<span className="brace-open object">{ braceOpen }</span>
5149
{
5250
!isRef ? null : <JumpToPathSection name={ name }/>

src/core/components/operation.jsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ export default class Operation extends PureComponent {
116116
specActions,
117117
specSelectors,
118118
authActions,
119-
authSelectors
119+
authSelectors,
120+
getConfigs
120121
} = this.props
121122

122123
let summary = operation.get("summary")
@@ -141,6 +142,10 @@ export default class Operation extends PureComponent {
141142
const Markdown = getComponent( "Markdown" )
142143
const Schemes = getComponent( "schemes" )
143144

145+
const { deepLinking } = getConfigs()
146+
147+
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
148+
144149
// Merge in Live Response
145150
if(response && response.size > 0) {
146151
let notDocumented = !responses.get(String(response.get("status")))
@@ -152,13 +157,18 @@ export default class Operation extends PureComponent {
152157
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
153158

154159
return (
155-
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey} >
160+
<div className={deprecated ? "opblock opblock-deprecated" : shown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} >
156161
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} >
157-
<span className="opblock-summary-method">{method.toUpperCase()}</span>
158-
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
159-
<span>{path}</span>
160-
<JumpToPath path={jumpToKey} />
161-
</span>
162+
<span className="opblock-summary-method">{method.toUpperCase()}</span>
163+
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
164+
<a
165+
className="nostyle"
166+
onClick={(e) => e.preventDefault()}
167+
href={ isDeepLinkingEnabled ? `#/${isShownKey[1]}/${isShownKey[2]}` : ""} >
168+
<span>{path}</span>
169+
</a>
170+
<JumpToPath path={jumpToKey} />
171+
</span>
162172

163173
{ !showSummary ? null :
164174
<div className="opblock-summary-description">
@@ -191,7 +201,9 @@ export default class Operation extends PureComponent {
191201
<div className="opblock-external-docs-wrapper">
192202
<h4 className="opblock-title_normal">Find more details</h4>
193203
<div className="opblock-external-docs">
194-
<span className="opblock-external-docs__description">{ externalDocs.get("description") }</span>
204+
<span className="opblock-external-docs__description">
205+
<Markdown source={ externalDocs.get("description") } />
206+
</span>
195207
<a className="opblock-external-docs__link" href={ externalDocs.get("url") }>{ externalDocs.get("url") }</a>
196208
</div>
197209
</div> : null

src/core/components/operations.jsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from "react"
22
import PropTypes from "prop-types"
3+
import { helpers } from "swagger-client"
4+
5+
const { opId } = helpers
36

47
export default class Operations extends React.Component {
58

@@ -33,7 +36,15 @@ export default class Operations extends React.Component {
3336
const Collapse = getComponent("Collapse")
3437

3538
let showSummary = layoutSelectors.showSummary()
36-
let { docExpansion, displayOperationId, displayRequestDuration, maxDisplayedTags } = getConfigs()
39+
let {
40+
docExpansion,
41+
displayOperationId,
42+
displayRequestDuration,
43+
maxDisplayedTags,
44+
deepLinking
45+
} = getConfigs()
46+
47+
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
3748

3849
let filter = layoutSelectors.currentFilter()
3950

@@ -62,8 +73,16 @@ export default class Operations extends React.Component {
6273
return (
6374
<div className={showTag ? "opblock-tag-section is-open" : "opblock-tag-section"} key={"operation-" + tag}>
6475

65-
<h4 onClick={() => layoutActions.show(isShownKey, !showTag)} className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }>
66-
<span>{tag}</span>
76+
<h4
77+
onClick={() => layoutActions.show(isShownKey, !showTag)}
78+
className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" }
79+
id={isShownKey.join("-")}>
80+
<a
81+
className="nostyle"
82+
onClick={(e) => e.preventDefault()}
83+
href={ isDeepLinkingEnabled ? `#/${tag}` : ""}>
84+
<span>{tag}</span>
85+
</a>
6786
{ !tagDescription ? null :
6887
<small>
6988
{ tagDescription }
@@ -81,11 +100,14 @@ export default class Operations extends React.Component {
81100
{
82101
operations.map( op => {
83102

84-
const isShownKey = ["operations", op.get("id"), tag]
85103
const path = op.get("path", "")
86104
const method = op.get("method", "")
87105
const jumpToKey = `paths.${path}.${method}`
88106

107+
const operationId =
108+
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id")
109+
const isShownKey = ["operations", tag, operationId]
110+
89111
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
90112
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
91113
const request = specSelectors.requestFor(op.get("path"), op.get("method"))

src/core/components/providers/markdown.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ function Markdown({ source }) {
1818
return null
1919
}
2020

21-
return <Remarkable
22-
options={{html: true, typographer: true, linkify: true, linkTarget: "_blank"}}
23-
source={sanitized}
24-
></Remarkable>
21+
return <div className="markdown">
22+
<Remarkable
23+
options={{html: true, typographer: true, breaks: true, linkify: true, linkTarget: "_blank"}}
24+
source={sanitized}
25+
></Remarkable>
26+
</div>
2527
}
2628

2729
Markdown.propTypes = {

src/core/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const CONFIGS = [
3030
"parameterMacro",
3131
"displayOperationId",
3232
"displayRequestDuration",
33+
"deepLinking",
3334
]
3435

3536
// eslint-disable-next-line no-undef
@@ -61,6 +62,7 @@ module.exports = function SwaggerUI(opts) {
6162
custom: {},
6263
displayOperationId: false,
6364
displayRequestDuration: false,
65+
deepLinking: false,
6466

6567
// Initial set of plugins ( TODO rename this, or refactor - we don't need presets _and_ plugins. Its just there for performance.
6668
// Instead, we can compile the first plugin ( it can be a collection of plugins ), then batch the rest.

0 commit comments

Comments
 (0)