@@ -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 | ||||
@@ -33,7 +33,21 @@ 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 } = getConfigs() | |||||
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> | ||||
@@ -6,7 +6,7 @@ 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" | import { parseSeach, filterConfigs } from "core/utils" | ||||
const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion", | |||||
const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion", "maxDisplayedTags", "filter", | |||||
"apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl", | "apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl", | ||||
"showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"] | "showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"] | ||||
@@ -26,6 +26,8 @@ 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: {}, | ||||
@@ -50,7 +52,9 @@ module.exports = function SwaggerUI(opts) { | |||||
store: { }, | store: { }, | ||||
} | } | ||||
const constructorConfig = deepExtend({}, defaults, opts) | |||||
let queryConfig = parseSeach() | |||||
const constructorConfig = deepExtend({}, defaults, opts, queryConfig) | |||||
const storeConfigs = deepExtend({}, constructorConfig.store, { | const storeConfigs = deepExtend({}, constructorConfig.store, { | ||||
system: { | system: { | ||||
@@ -59,7 +63,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 +85,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 | ||||
@@ -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)) | ||||
@@ -6,6 +6,11 @@ import Logo from "./logo_small.png" | |||||
export default class Topbar extends React.Component { | export default class Topbar extends React.Component { | ||||
static propTypes = { | |||||
layoutSelectors: PropTypes.object.isRequired, | |||||
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,13 +85,19 @@ 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, layoutSelectors } = this.props | |||||
const Button = getComponent("Button") | const Button = getComponent("Button") | ||||
const Link = getComponent("Link") | const Link = getComponent("Link") | ||||
let isLoading = specSelectors.loadingStatus() === "loading" | let isLoading = specSelectors.loadingStatus() === "loading" | ||||
let isFailed = specSelectors.loadingStatus() === "failed" | let isFailed = specSelectors.loadingStatus() === "failed" | ||||
let filter = layoutSelectors.currentFilter() | |||||
let inputStyle = {} | let inputStyle = {} | ||||
if(isFailed) inputStyle.color = "red" | if(isFailed) inputStyle.color = "red" | ||||
@@ -124,6 +135,10 @@ export default class Topbar extends React.Component { | |||||
<img height="30" width="30" src={ Logo } alt="Swagger UX"/> | <img height="30" width="30" src={ Logo } alt="Swagger UX"/> | ||||
<span>swagger</span> | <span>swagger</span> | ||||
</Link> | </Link> | ||||
{ | |||||
filter === null || filter === false ? null : | |||||
<input className="operation-filter-input" placeholder="filter..." type="text" onChange={this.onFilterChange} value={filter === true ? "" : filter} disabled={isLoading} style={inputStyle} /> | |||||
} | |||||
<form className="download-url-wrapper" onSubmit={formOnSubmit}> | <form className="download-url-wrapper" onSubmit={formOnSubmit}> | ||||
{control} | {control} | ||||
</form> | </form> | ||||
@@ -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,12 @@ body | |||||
.opblock-schemes | .opblock-schemes | ||||
{ | { | ||||
padding: 8px 20px; | |||||
padding: 8px 20px; | |||||
.schemes-title | |||||
{ | |||||
padding: 0 10px 0 0; | |||||
} | |||||
.schemes-title | |||||
{ | |||||
padding: 0 10px 0 0; | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -498,13 +500,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 +533,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 +623,12 @@ body | |||||
opacity: 0; | opacity: 0; | ||||
} | } | ||||
} | } | ||||
section | |||||
{ | |||||
h3 | |||||
{ | |||||
@include text_headline(); | |||||
} | |||||
} |
@@ -29,6 +29,12 @@ | |||||
padding: 0 10px; | padding: 0 10px; | ||||
} | } | ||||
} | } | ||||
.operation-filter-input | |||||
{ | |||||
border: 2px solid #547f00; | |||||
border-right: none; | |||||
border-radius: 4px 0 0 4px; | |||||
} | |||||
.download-url-wrapper | .download-url-wrapper | ||||
{ | { | ||||
@@ -43,7 +49,7 @@ | |||||
margin: 0; | margin: 0; | ||||
border: 2px solid #547f00; | border: 2px solid #547f00; | ||||
border-radius: 4px 0 0 4px; | |||||
border-radius: 0 0 0 0; | |||||
outline: none; | outline: none; | ||||
} | } | ||||