diff --git a/README.md b/README.md index c872e4c5..28943fab 100644 --- a/README.md +++ b/README.md @@ -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'. 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`. +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 diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx index 6c20d963..679af129 100644 --- a/src/core/components/operations.jsx +++ b/src/core/components/operations.jsx @@ -33,7 +33,21 @@ export default class Operations extends React.Component { const Collapse = getComponent("Collapse") 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 (
diff --git a/src/core/index.js b/src/core/index.js index 80335da5..2796730f 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -6,7 +6,7 @@ import ApisPreset from "core/presets/apis" 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", +const CONFIGS = [ "url", "urls", "urls.primaryName", "spec", "validatorUrl", "onComplete", "onFailure", "authorizations", "docExpansion", "maxDisplayedTags", "filter", "apisSorter", "operationsSorter", "supportedSubmitMethods", "dom_id", "defaultModelRendering", "oauth2RedirectUrl", "showRequestHeaders", "custom", "modelPropertyMacro", "parameterMacro", "displayOperationId" , "displayRequestDuration"] @@ -26,6 +26,8 @@ module.exports = function SwaggerUI(opts) { urls: null, layout: "BaseLayout", docExpansion: "list", + maxDisplayedTags: null, + filter: null, validatorUrl: "https://online.swagger.io/validator", configs: {}, custom: {}, @@ -50,7 +52,9 @@ module.exports = function SwaggerUI(opts) { store: { }, } - const constructorConfig = deepExtend({}, defaults, opts) + let queryConfig = parseSeach() + + const constructorConfig = deepExtend({}, defaults, opts, queryConfig) const storeConfigs = deepExtend({}, constructorConfig.store, { system: { @@ -59,7 +63,8 @@ module.exports = function SwaggerUI(opts) { plugins: constructorConfig.presets, state: { layout: { - layout: constructorConfig.layout + layout: constructorConfig.layout, + filter: constructorConfig.filter }, spec: { spec: "", @@ -80,7 +85,6 @@ module.exports = function SwaggerUI(opts) { store.register([constructorConfig.plugins, inlinePlugin]) var system = store.getSystem() - let queryConfig = parseSeach() system.initOAuth = system.authActions.configureAuth diff --git a/src/core/plugins/layout/actions.js b/src/core/plugins/layout/actions.js index b8cba718..d65d34e3 100644 --- a/src/core/plugins/layout/actions.js +++ b/src/core/plugins/layout/actions.js @@ -1,6 +1,7 @@ import { normalizeArray } from "core/utils" export const UPDATE_LAYOUT = "layout_update_layout" +export const UPDATE_FILTER = "layout_update_filter" export const UPDATE_MODE = "layout_update_mode" 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) { thing = normalizeArray(thing) return { diff --git a/src/core/plugins/layout/reducers.js b/src/core/plugins/layout/reducers.js index 44e3c480..4b561015 100644 --- a/src/core/plugins/layout/reducers.js +++ b/src/core/plugins/layout/reducers.js @@ -1,5 +1,6 @@ import { UPDATE_LAYOUT, + UPDATE_FILTER, UPDATE_MODE, SHOW } from "./actions" @@ -8,6 +9,8 @@ export default { [UPDATE_LAYOUT]: (state, action) => state.set("layout", action.payload), + [UPDATE_FILTER]: (state, action) => state.set("filter", action.payload), + [SHOW]: (state, action) => { let thing = action.payload.thing let shown = action.payload.shown diff --git a/src/core/plugins/layout/selectors.js b/src/core/plugins/layout/selectors.js index 0eb0c522..42a3bcbb 100644 --- a/src/core/plugins/layout/selectors.js +++ b/src/core/plugins/layout/selectors.js @@ -5,6 +5,8 @@ const state = state => state export const current = state => state.get("layout") +export const currentFilter = state => state.get("filter") + export const isShown = (state, thing, def) => { thing = normalizeArray(thing) return Boolean(state.getIn(["shown", ...thing], def)) diff --git a/src/plugins/topbar/topbar.jsx b/src/plugins/topbar/topbar.jsx index 11b5c7eb..314e1d4e 100644 --- a/src/plugins/topbar/topbar.jsx +++ b/src/plugins/topbar/topbar.jsx @@ -6,6 +6,11 @@ import Logo from "./logo_small.png" export default class Topbar extends React.Component { + static propTypes = { + layoutSelectors: PropTypes.object.isRequired, + layoutActions: PropTypes.object.isRequired + } + constructor(props, context) { super(props, context) 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() { - let { getComponent, specSelectors, getConfigs } = this.props + let { getComponent, specSelectors, getConfigs, layoutSelectors } = this.props const Button = getComponent("Button") const Link = getComponent("Link") let isLoading = specSelectors.loadingStatus() === "loading" let isFailed = specSelectors.loadingStatus() === "failed" + let filter = layoutSelectors.currentFilter() let inputStyle = {} if(isFailed) inputStyle.color = "red" @@ -124,6 +135,10 @@ export default class Topbar extends React.Component { Swagger UX swagger + { + filter === null || filter === false ? null : + + }
{control}
diff --git a/src/standalone/layout.jsx b/src/standalone/layout.jsx index 20ca50d1..dc36b46e 100644 --- a/src/standalone/layout.jsx +++ b/src/standalone/layout.jsx @@ -29,7 +29,7 @@ export default class StandaloneLayout extends React.Component { return ( - { Topbar ? : null } + { Topbar ? : null } { loadingStatus === "loading" &&

Loading...

@@ -45,7 +45,7 @@ export default class StandaloneLayout extends React.Component {

Failed to load config.

} - { !loadingStatus || loadingStatus === "success" && } + { !loadingStatus || loadingStatus === "success" && } diff --git a/src/style/_layout.scss b/src/style/_layout.scss index 955e4d54..78e09249 100644 --- a/src/style/_layout.scss +++ b/src/style/_layout.scss @@ -45,6 +45,7 @@ body .opblock-tag { display: flex; + align-items: center; padding: 10px 20px 10px 10px; @@ -53,8 +54,6 @@ body border-bottom: 1px solid rgba(#3b4151, .3); - align-items: center; - &:hover { background: rgba(#000,.02); @@ -106,9 +105,10 @@ body font-size: 14px; font-weight: normal; + flex: 1; + padding: 0 10px; - flex: 1; @include text_body(); } } @@ -134,6 +134,8 @@ body transition: all .5s; } + + .opblock { margin: 0 0 15px 0; @@ -154,24 +156,23 @@ body .opblock-section-header { display: flex; + align-items: center; padding: 8px 20px; background: rgba(#fff,.8); box-shadow: 0 1px 2px rgba(#000,.1); - align-items: center; - label { font-size: 12px; font-weight: bold; display: flex; + align-items: center; margin: 0; - align-items: center; @include text_headline(); span @@ -184,9 +185,10 @@ body { font-size: 14px; + flex: 1; + margin: 0; - flex: 1; @include text_headline(); } } @@ -215,11 +217,11 @@ body font-size: 16px; display: flex; + align-items: center; padding: 0 10px; @include text_code(); - align-items: center; .view-line-link { @@ -258,18 +260,18 @@ body font-size: 13px; flex: 1; + @include text_body(); } .opblock-summary { display: flex; + align-items: center; padding: 5px; cursor: pointer; - - align-items: center; } &.opblock-post @@ -316,12 +318,12 @@ body .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; padding: 10px; - + white-space: pre-wrap; word-wrap: break-word; word-break: break-all; word-break: break-word; hyphens: auto; - white-space: pre-wrap; - border-radius: 4px; background: #41444e; @@ -533,10 +533,9 @@ body .schemes { display: flex; - align-items: center; - > label + > label { font-size: 12px; font-weight: bold; @@ -624,3 +623,12 @@ body opacity: 0; } } + + +section +{ + h3 + { + @include text_headline(); + } +} diff --git a/src/style/_topbar.scss b/src/style/_topbar.scss index 4bded694..b1427c78 100644 --- a/src/style/_topbar.scss +++ b/src/style/_topbar.scss @@ -29,6 +29,12 @@ padding: 0 10px; } } + .operation-filter-input + { + border: 2px solid #547f00; + border-right: none; + border-radius: 4px 0 0 4px; + } .download-url-wrapper { @@ -43,7 +49,7 @@ margin: 0; border: 2px solid #547f00; - border-radius: 4px 0 0 4px; + border-radius: 0 0 0 0; outline: none; }