feat: expose plugins and presets on SwaggerUI global symbol (#9189)

Part of this commit is also:
- complete plugins consolidation
- complete presets consolidation
- build system consolidation

Refs #9188
This commit is contained in:
Vladimír Gorej
2023-09-05 09:56:51 +02:00
committed by GitHub
parent 8137a8b337
commit edd1153723
118 changed files with 28137 additions and 853 deletions

View File

Before

Width:  |  Height:  |  Size: 734 B

After

Width:  |  Height:  |  Size: 734 B

View File

@@ -1,6 +0,0 @@
/* global ace */
ace.define("ace/snippets/yaml",
["require","exports","module"], function(e,t,n){ // eslint-disable-line no-unused-vars
t.snippetText=undefined
t.scope="yaml"
})

View File

@@ -3,6 +3,8 @@ import ImmutablePureComponent from "react-immutable-pure-component"
import ImPropTypes from "react-immutable-proptypes"
import PropTypes from "prop-types"
import RollingLoadSVG from "core/assets/rolling-load.svg"
const decodeRefName = uri => {
const unescaped = uri.replace(/~1/g, "/").replace(/~0/g, "~")
@@ -66,7 +68,7 @@ export default class Model extends ImmutablePureComponent {
if(!schema) {
return <span className="model model-title">
<span className="model-title__text">{ displayName || name }</span>
<img src={require("core/../img/rolling-load.svg")} height={"20px"} width={"20px"} />
<img src={RollingLoadSVG} height={"20px"} width={"20px"} />
</span>
}

View File

@@ -6,6 +6,7 @@ import { safeBuildUrl } from "core/utils/url"
import { Iterable, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import RollingLoadSVG from "core/assets/rolling-load.svg"
export default class Operation extends PureComponent {
static propTypes = {
@@ -122,7 +123,7 @@ export default class Operation extends PureComponent {
<Collapse isOpened={isShown}>
<div className="opblock-body">
{ (operation && operation.size) || operation === null ? null :
<img height={"32px"} width={"32px"} src={require("core/../img/rolling-load.svg")} className="opblock-loading-animation" />
<img height={"32px"} width={"32px"} src={RollingLoadSVG} className="opblock-loading-animation" />
}
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description &&
@@ -140,7 +141,7 @@ export default class Operation extends PureComponent {
{externalDocs.description &&
<span className="opblock-external-docs__description">
<Markdown source={ externalDocs.description } />
</span>
</span>
}
<Link target="_blank" className="opblock-external-docs__link" href={sanitizeUrl(externalDocsUrl)}>{externalDocsUrl}</Link>
</div>

View File

@@ -4,7 +4,7 @@ import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue } from "core/utils"
import getParameterSchema from "../../helpers/get-parameter-schema.js"
import getParameterSchema from "core/utils/get-parameter-schema.js"
export default class ParameterRow extends Component {
static propTypes = {

View File

@@ -3,7 +3,7 @@ import { fromJS, Iterable } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
import createHtmlReadyId from "../../helpers/create-html-ready-id"
import createHtmlReadyId from "core/utils/create-html-ready-id"
export default class Responses extends React.Component {
static propTypes = {

View File

@@ -1,8 +1,31 @@
import deepExtend from "deep-extend"
import System from "./system"
// presets
import BasePreset from "./presets/base"
import ApisPreset from "./presets/apis"
import AllPlugins from "./plugins/all"
// plugins
import AuthPlugin from "./plugins/auth/"
import ConfigsPlugin from "./plugins/configs"
import DeepLinkingPlugin from "./plugins/deep-linking"
import ErrPlugin from "./plugins/err"
import FilterPlugin from "./plugins/filter"
import IconsPlugin from "./plugins/icons"
import JSONSchema202012Plugin from "./plugins/json-schema-2020-12"
import LayoutPlugin from "./plugins/layout"
import LogsPlugin from "./plugins/logs"
import OpenAPI30Plugin from "./plugins/oas3"
import OpenAPI31Plugin from "./plugins/oas3"
import OnCompletePlugin from "./plugins/on-complete"
import RequestSnippetsPlugin from "./plugins/request-snippets"
import SamplesPlugin from "./plugins/samples"
import SpecPlugin from "./plugins/spec"
import SwaggerClientPlugin from "./plugins/swagger-client"
import UtilPlugin from "./plugins/util"
import ViewPlugin from "./plugins/view"
import DownloadUrlPlugin from "./plugins/download-url"
import SafeRenderPlugin from "./plugins/safe-render"
import { parseSearch } from "./utils"
import win from "./window"
@@ -216,10 +239,30 @@ export default function SwaggerUI(opts) {
return system
}
// Add presets
SwaggerUI.presets = {
base: BasePreset,
apis: ApisPreset,
}
// All Plugins
SwaggerUI.plugins = AllPlugins
SwaggerUI.plugins = {
Auth: AuthPlugin,
Configs: ConfigsPlugin,
DeepLining: DeepLinkingPlugin,
Err: ErrPlugin,
Filter: FilterPlugin,
Icons: IconsPlugin,
JSONSchema202012: JSONSchema202012Plugin,
Layout: LayoutPlugin,
Logs: LogsPlugin,
OpenAPI30: OpenAPI30Plugin,
OpenAPI31: OpenAPI31Plugin,
OnComplete: OnCompletePlugin,
RequestSnippets: RequestSnippetsPlugin,
Samples: SamplesPlugin,
Spec: SpecPlugin,
SwaggerClient: SwaggerClientPlugin,
Util: UtilPlugin,
View: ViewPlugin,
DownloadUrl: DownloadUrlPlugin,
SafeRender: SafeRenderPlugin,
}

View File

@@ -1,24 +0,0 @@
import { pascalCaseFilename } from "core/utils"
import SafeRender from "core/plugins/safe-render"
const request = require.context(".", true, /\.jsx?$/)
const allPlugins = {}
export default allPlugins
request.keys().forEach( function( key ){
if( key === "./index.js" ) {
return
}
// if( key.slice(2).indexOf("/") > -1) {
// // skip files in subdirs
// return
// }
let mod = request(key)
allPlugins[pascalCaseFilename(key)] = mod.default ? mod.default : mod
})
allPlugins.SafeRender = SafeRender

View File

@@ -1,110 +0,0 @@
import { createSelector } from "reselect"
import { Map } from "immutable"
import win from "../window"
export default function downloadUrlPlugin (toolbox) {
let { fn } = toolbox
const actions = {
download: (url)=> ({ errActions, specSelectors, specActions, getConfigs }) => {
let { fetch } = fn
const config = getConfigs()
url = url || specSelectors.url()
specActions.updateLoadingStatus("loading")
errActions.clear({source: "fetch"})
fetch({
url,
loadSpec: true,
requestInterceptor: config.requestInterceptor || (a => a),
responseInterceptor: config.responseInterceptor || (a => a),
credentials: "same-origin",
headers: {
"Accept": "application/json,*/*"
}
}).then(next,next)
function next(res) {
if(res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failed")
errActions.newThrownErr(Object.assign( new Error((res.message || res.statusText) + " " + url), {source: "fetch"}))
// Check if the failure was possibly due to CORS or mixed content
if (!res.status && res instanceof Error) checkPossibleFailReasons()
return
}
specActions.updateLoadingStatus("success")
specActions.updateSpec(res.text)
if(specSelectors.url() !== url) {
specActions.updateUrl(url)
}
}
function checkPossibleFailReasons() {
try {
let specUrl
if("URL" in win ) {
specUrl = new URL(url)
} else {
// legacy browser, use <a href> to parse the URL
specUrl = document.createElement("a")
specUrl.href = url
}
if(specUrl.protocol !== "https:" && win.location.protocol === "https:") {
const error = Object.assign(
new Error(`Possible mixed-content issue? The page was loaded over https:// but a ${specUrl.protocol}// URL was specified. Check that you are not attempting to load mixed content.`),
{source: "fetch"}
)
errActions.newThrownErr(error)
return
}
if(specUrl.origin !== win.location.origin) {
const error = Object.assign(
new Error(`Possible cross-origin (CORS) issue? The URL origin (${specUrl.origin}) does not match the page (${win.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`),
{source: "fetch"}
)
errActions.newThrownErr(error)
}
} catch (e) {
return
}
}
},
updateLoadingStatus: (status) => {
let enums = [null, "loading", "failed", "success", "failedConfig"]
if(enums.indexOf(status) === -1) {
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
}
return {
type: "spec_update_loading_status",
payload: status
}
}
}
let reducers = {
"spec_update_loading_status": (state, action) => {
return (typeof action.payload === "string")
? state.set("loadingStatus", action.payload)
: state
}
}
let selectors = {
loadingStatus: createSelector(
state => {
return state || Map()
},
spec => spec.get("loadingStatus") || null
)
}
return {
statePlugins: {
spec: { actions, reducers, selectors }
}
}
}

View File

@@ -0,0 +1,126 @@
/**
* @prettier
*/
import { createSelector } from "reselect"
import { Map } from "immutable"
import win from "core/window"
export default function downloadUrlPlugin(toolbox) {
let { fn } = toolbox
const actions = {
download:
(url) =>
({ errActions, specSelectors, specActions, getConfigs }) => {
let { fetch } = fn
const config = getConfigs()
url = url || specSelectors.url()
specActions.updateLoadingStatus("loading")
errActions.clear({ source: "fetch" })
fetch({
url,
loadSpec: true,
requestInterceptor: config.requestInterceptor || ((a) => a),
responseInterceptor: config.responseInterceptor || ((a) => a),
credentials: "same-origin",
headers: {
Accept: "application/json,*/*",
},
}).then(next, next)
function next(res) {
if (res instanceof Error || res.status >= 400) {
specActions.updateLoadingStatus("failed")
errActions.newThrownErr(
Object.assign(
new Error((res.message || res.statusText) + " " + url),
{ source: "fetch" }
)
)
// Check if the failure was possibly due to CORS or mixed content
if (!res.status && res instanceof Error) checkPossibleFailReasons()
return
}
specActions.updateLoadingStatus("success")
specActions.updateSpec(res.text)
if (specSelectors.url() !== url) {
specActions.updateUrl(url)
}
}
function checkPossibleFailReasons() {
try {
let specUrl
if ("URL" in win) {
specUrl = new URL(url)
} else {
// legacy browser, use <a href> to parse the URL
specUrl = document.createElement("a")
specUrl.href = url
}
if (
specUrl.protocol !== "https:" &&
win.location.protocol === "https:"
) {
const error = Object.assign(
new Error(
`Possible mixed-content issue? The page was loaded over https:// but a ${specUrl.protocol}// URL was specified. Check that you are not attempting to load mixed content.`
),
{ source: "fetch" }
)
errActions.newThrownErr(error)
return
}
if (specUrl.origin !== win.location.origin) {
const error = Object.assign(
new Error(
`Possible cross-origin (CORS) issue? The URL origin (${specUrl.origin}) does not match the page (${win.location.origin}). Check the server returns the correct 'Access-Control-Allow-*' headers.`
),
{ source: "fetch" }
)
errActions.newThrownErr(error)
}
} catch (e) {
return
}
}
},
updateLoadingStatus: (status) => {
let enums = [null, "loading", "failed", "success", "failedConfig"]
if (enums.indexOf(status) === -1) {
console.error(`Error: ${status} is not one of ${JSON.stringify(enums)}`)
}
return {
type: "spec_update_loading_status",
payload: status,
}
},
}
let reducers = {
spec_update_loading_status: (state, action) => {
return typeof action.payload === "string"
? state.set("loadingStatus", action.payload)
: state
},
}
let selectors = {
loadingStatus: createSelector(
(state) => {
return state || Map()
},
(spec) => spec.get("loadingStatus") || null
),
}
return {
statePlugins: {
spec: { actions, reducers, selectors },
},
}
}

View File

@@ -6,7 +6,7 @@ import isEmpty from "lodash/isEmpty"
import isPlainObject from "lodash/isPlainObject"
import { objectify, normalizeArray } from "core/utils"
import memoizeN from "../../../../../helpers/memoizeN"
import memoizeN from "core/utils/memoizeN"
import typeMap from "./types/index"
import { getType } from "./core/type"
import { typeCast } from "./core/utils"

View File

@@ -5,7 +5,7 @@ import { OrderedMap, Map, List } from "immutable"
import { createSelector } from "reselect"
import { getDefaultRequestBodyValue } from "./components/request-body"
import { stringify } from "../../utils"
import { stringify } from "core/utils"
// Helpers

View File

@@ -1,5 +1,6 @@
import win from "../../window"
import { Map } from "immutable"
import win from "../../window"
/**
* if duplicate key name existed from FormData entries,

View File

@@ -2,8 +2,7 @@ import XML from "xml"
import RandExp from "randexp"
import isEmpty from "lodash/isEmpty"
import { objectify, isFunc, normalizeArray, deeplyStripKey } from "core/utils"
import memoizeN from "../../../../helpers/memoizeN"
import memoizeN from "core/utils/memoizeN"
const generateStringFromRegex = (pattern) => {
try {

View File

@@ -1,17 +1,20 @@
/**
* @prettier
*/
import reducers from "./reducers"
import * as actions from "./actions"
import * as selectors from "./selectors"
import * as wrapActions from "./wrap-actions"
export default function() {
return {
statePlugins: {
spec: {
wrapActions,
reducers,
actions,
selectors
}
}
}
}
const SpecPlugin = () => ({
statePlugins: {
spec: {
wrapActions: { ...wrapActions },
reducers: { ...reducers },
actions: { ...actions },
selectors: { ...selectors },
},
},
})
export default SpecPlugin

View File

@@ -1,6 +1,6 @@
import { fromJS, List } from "immutable"
import { fromJSOrdered, validateParam, paramToValue } from "core/utils"
import win from "../../window"
import { fromJSOrdered, validateParam, paramToValue, paramToIdentifier } from "core/utils"
import win from "core/window"
// selector-in-reducer is suboptimal, but `operationWithMeta` is more of a helper
import {
@@ -27,7 +27,6 @@ import {
CLEAR_VALIDATE_PARAMS,
SET_SCHEME
} from "./actions"
import { paramToIdentifier } from "../../utils"
export default {

View File

@@ -1,7 +1,6 @@
import { createSelector } from "reselect"
import { sorters } from "core/utils"
import { sorters, paramToIdentifier } from "core/utils"
import { fromJS, Set, Map, OrderedMap, List } from "immutable"
import { paramToIdentifier } from "../../utils"
const DEFAULT_TAG = "default"

View File

@@ -1,8 +1,8 @@
import { memoize } from "core/utils"
import memoizeN from "core/utils/memoizeN"
import { getComponent, render, withMappedContainer } from "./root-injects"
import { getDisplayName } from "./fn"
import memoizeN from "../../../helpers/memoizeN"
const memoizeForGetComponent = (fn) => {
const resolver = (...args) => JSON.stringify(args)

View File

@@ -1,11 +0,0 @@
/**
* @prettier
*/
import BasePreset from "./base"
import OAS3Plugin from "../plugins/oas3"
import OAS31Plugin from "../plugins/oas31"
import JSONSchema202012Plugin from "../plugins/json-schema-2020-12"
export default function PresetApis() {
return [BasePreset, OAS3Plugin, JSONSchema202012Plugin, OAS31Plugin]
}

View File

@@ -0,0 +1,11 @@
/**
* @prettier
*/
import BasePreset from "core/presets/base"
import OpenAPI30Plugin from "core/plugins/oas3"
import OpenAPI31Plugin from "core/plugins/oas31"
import JSONSchema202012Plugin from "core/plugins/json-schema-2020-12"
export default function PresetApis() {
return [BasePreset, OpenAPI30Plugin, JSONSchema202012Plugin, OpenAPI31Plugin]
}

View File

@@ -0,0 +1,49 @@
/**
* @prettier
*/
import AuthPlugin from "core/plugins/auth/"
import ConfigsPlugin from "core/plugins/configs"
import DeepLinkingPlugin from "core/plugins/deep-linking"
import ErrPlugin from "core/plugins/err"
import FilterPlugin from "core/plugins/filter"
import IconsPlugin from "core/plugins/icons"
import LayoutPlugin from "core/plugins/layout"
import LogsPlugin from "core/plugins/logs"
import OnCompletePlugin from "core/plugins/on-complete"
import RequestSnippetsPlugin from "core/plugins/request-snippets"
import SamplesPlugin from "core/plugins/samples"
import SpecPlugin from "core/plugins/spec"
import SwaggerClientPlugin from "core/plugins/swagger-client"
import UtilPlugin from "core/plugins/util"
import ViewPlugin from "core/plugins/view"
import DownloadUrlPlugin from "core/plugins/download-url"
import SafeRenderPlugin from "core/plugins/safe-render"
// ad-hoc plugins
import CoreComponentsPlugin from "core/presets/base/plugins/core-components"
import FormComponentsPlugin from "core/presets/base/plugins/form-components"
import JSONSchemaComponentsPlugin from "core/presets/base/plugins/json-schema-components"
const BasePreset = () => [
ConfigsPlugin,
UtilPlugin,
LogsPlugin,
ViewPlugin,
SpecPlugin,
ErrPlugin,
IconsPlugin,
LayoutPlugin,
SamplesPlugin,
CoreComponentsPlugin,
FormComponentsPlugin,
SwaggerClientPlugin,
JSONSchemaComponentsPlugin,
AuthPlugin,
DownloadUrlPlugin,
DeepLinkingPlugin,
FilterPlugin,
OnCompletePlugin,
RequestSnippetsPlugin,
SafeRenderPlugin(),
]
export default BasePreset

View File

@@ -1,26 +1,6 @@
/**
* @prettier
*/
import err from "core/plugins/err"
import layout from "core/plugins/layout"
import spec from "core/plugins/spec"
import view from "core/plugins/view"
import samples from "core/plugins/samples"
import requestSnippets from "core/plugins/request-snippets"
import logs from "core/plugins/logs"
import swaggerJs from "core/plugins/swagger-js"
import auth from "core/plugins/auth"
import util from "core/plugins/util"
import downloadUrlPlugin from "core/plugins/download-url"
import configsPlugin from "core/plugins/configs"
import deepLinkingPlugin from "core/plugins/deep-linking"
import filter from "core/plugins/filter"
import onComplete from "core/plugins/on-complete"
import safeRender from "core/plugins/safe-render"
import iconsPlugin from "core/plugins/icons"
import OperationContainer from "core/containers/OperationContainer"
import App from "core/components/app"
import AuthorizationPopup from "core/components/auth/authorization-popup"
import AuthorizeBtn from "core/components/auth/authorize-btn"
@@ -41,6 +21,7 @@ import OnlineValidatorBadge from "core/components/online-validator-badge"
import Operations from "core/components/operations"
import OperationTag from "core/components/operation-tag"
import Operation from "core/components/operation"
import OperationContainer from "core/containers/OperationContainer"
import OperationSummary from "core/components/operation-summary"
import OperationSummaryMethod from "core/components/operation-summary-method"
import OperationSummaryPath from "core/components/operation-summary-path"
@@ -91,116 +72,83 @@ import DeepLink from "core/components/deep-link"
import SvgAssets from "core/components/svg-assets"
import Markdown from "core/components/providers/markdown"
import BaseLayout from "core/components/layouts/base"
import * as LayoutUtils from "core/components/layout-utils"
import * as JsonSchemaComponents from "core/json-schema-components"
export default function () {
let coreComponents = {
components: {
App,
authorizationPopup: AuthorizationPopup,
authorizeBtn: AuthorizeBtn,
AuthorizeBtnContainer,
authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths,
AuthItem: AuthItem,
authError: AuthError,
oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth,
basicAuth: BasicAuth,
clear: Clear,
liveResponse: LiveResponse,
InitializedInput,
info: Info,
InfoContainer,
InfoUrl,
InfoBasePath,
Contact,
License,
JumpToPath,
CopyToClipboardBtn,
onlineValidatorBadge: OnlineValidatorBadge,
operations: Operations,
operation: Operation,
OperationSummary,
OperationSummaryMethod,
OperationSummaryPath,
highlightCode: HighlightCode,
responses: Responses,
response: Response,
ResponseExtension: ResponseExtension,
responseBody: ResponseBody,
parameters: Parameters,
parameterRow: ParameterRow,
execute: Execute,
headers: Headers,
errors: Errors,
contentType: ContentType,
overview: Overview,
footer: Footer,
FilterContainer,
ParamBody: ParamBody,
curl: Curl,
schemes: Schemes,
SchemesContainer,
modelExample: ModelExample,
ModelWrapper,
ModelCollapse,
Model,
Models,
EnumModel,
ObjectModel,
ArrayModel,
PrimitiveModel,
Property,
TryItOutButton,
Markdown,
BaseLayout,
VersionPragmaFilter,
VersionStamp,
OpenAPIVersion,
OperationExt,
OperationExtRow,
ParameterExt,
ParameterIncludeEmpty,
OperationTag,
OperationContainer,
DeepLink,
SvgAssets,
Example,
ExamplesSelect,
ExamplesSelectValueRetainer,
},
}
const CoreComponentsPlugin = () => ({
components: {
App,
authorizationPopup: AuthorizationPopup,
authorizeBtn: AuthorizeBtn,
AuthorizeBtnContainer,
authorizeOperationBtn: AuthorizeOperationBtn,
auths: Auths,
AuthItem: AuthItem,
authError: AuthError,
oauth2: Oauth2,
apiKeyAuth: ApiKeyAuth,
basicAuth: BasicAuth,
clear: Clear,
liveResponse: LiveResponse,
InitializedInput,
info: Info,
InfoContainer,
InfoUrl,
InfoBasePath,
Contact,
License,
JumpToPath,
CopyToClipboardBtn,
onlineValidatorBadge: OnlineValidatorBadge,
operations: Operations,
operation: Operation,
OperationSummary,
OperationSummaryMethod,
OperationSummaryPath,
highlightCode: HighlightCode,
responses: Responses,
response: Response,
ResponseExtension: ResponseExtension,
responseBody: ResponseBody,
parameters: Parameters,
parameterRow: ParameterRow,
execute: Execute,
headers: Headers,
errors: Errors,
contentType: ContentType,
overview: Overview,
footer: Footer,
FilterContainer,
ParamBody: ParamBody,
curl: Curl,
schemes: Schemes,
SchemesContainer,
modelExample: ModelExample,
ModelWrapper,
ModelCollapse,
Model,
Models,
EnumModel,
ObjectModel,
ArrayModel,
PrimitiveModel,
Property,
TryItOutButton,
Markdown,
BaseLayout,
VersionPragmaFilter,
VersionStamp,
OperationExt,
OperationExtRow,
ParameterExt,
ParameterIncludeEmpty,
OperationTag,
OperationContainer,
OpenAPIVersion,
DeepLink,
SvgAssets,
Example,
ExamplesSelect,
ExamplesSelectValueRetainer,
},
})
let formComponents = {
components: LayoutUtils,
}
let jsonSchemaComponents = {
components: JsonSchemaComponents,
}
return [
configsPlugin,
util,
logs,
view,
spec,
err,
layout,
samples,
coreComponents,
formComponents,
swaggerJs,
jsonSchemaComponents,
auth,
downloadUrlPlugin,
deepLinkingPlugin,
filter,
onComplete,
requestSnippets,
iconsPlugin,
safeRender(),
]
}
export default CoreComponentsPlugin

View File

@@ -0,0 +1,10 @@
/**
* @prettier
*/
import * as LayoutUtils from "core/components/layout-utils"
const FormComponentsPlugin = () => ({
components: { ...LayoutUtils },
})
export default FormComponentsPlugin

View File

@@ -0,0 +1,10 @@
/**
* @prettier
*/
import * as JSONSchemaComponents from "core/components/json-schema-components"
const JSONSchemaComponentsPlugin = () => ({
components: { ...JSONSchemaComponents },
})
export default JSONSchemaComponentsPlugin

View File

@@ -1,16 +0,0 @@
import PropTypes from "prop-types"
// Takes a list and proptype, and returns a PropType.shape({ [item]: propType })
const mapListToPropTypeShape = (list, propType) => PropTypes.shape(
list.reduce((shape, propName) => {
shape[propName] = propType
return shape
}, {}))
export const arrayOrString = PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.string),
PropTypes.string,
])
export const objectWithFuncs = list => mapListToPropTypeShape(list, PropTypes.func.isRequired)

View File

@@ -5,7 +5,7 @@ import deepExtend from "deep-extend"
import { combineReducers } from "redux-immutable"
import { serializeError } from "serialize-error"
import merge from "lodash/merge"
import { NEW_THROWN_ERR } from "corePlugins/err/actions"
import { NEW_THROWN_ERR } from "core/plugins/err/actions"
import win from "core/window"
import { systemThunkMiddleware, isFn, objMap, objReduce, isObject, isArray, isFunc } from "core/utils"

View File

@@ -18,11 +18,12 @@ import find from "lodash/find"
import some from "lodash/some"
import eq from "lodash/eq"
import isFunction from "lodash/isFunction"
import win from "./window"
import cssEscape from "css.escape"
import getParameterSchema from "../helpers/get-parameter-schema"
import randomBytes from "randombytes"
import shaJs from "sha.js"
import win from "core/window"
import getParameterSchema from "core/utils/get-parameter-schema"
const DEFAULT_RESPONSE_KEY = "default"
@@ -117,11 +118,11 @@ export function createObjWithHashedKeys (fdObj) {
}
export function bindToState(obj, state) {
var newObj = {}
Object.keys(obj)
.filter(key => typeof obj[key] === "function")
.forEach(key => newObj[key] = obj[key].bind(null, state))
return newObj
var newObj = {}
Object.keys(obj)
.filter(key => typeof obj[key] === "function")
.forEach(key => newObj[key] = obj[key].bind(null, state))
return newObj
}
export function normalizeArray(arr) {
@@ -344,21 +345,21 @@ export const validateString = ( val ) => {
}
export const validateDateTime = (val) => {
if (isNaN(Date.parse(val))) {
return "Value must be a DateTime"
}
if (isNaN(Date.parse(val))) {
return "Value must be a DateTime"
}
}
export const validateGuid = (val) => {
val = val.toString().toLowerCase()
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(val)) {
return "Value must be a Guid"
}
val = val.toString().toLowerCase()
if (!/^[{(]?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}[)}]?$/.test(val)) {
return "Value must be a Guid"
}
}
export const validateMaxLength = (val, max) => {
if (val.length > max) {
return `Value must be no longer than ${max} character${max !== 1 ? "s" : ""}`
return `Value must be no longer than ${max} character${max !== 1 ? "s" : ""}`
}
}
@@ -386,7 +387,7 @@ export const validateUniqueItems = (val, uniqueItems) => {
export const validateMinItems = (val, min) => {
if (!val && min >= 1 || val && val.length < min) {
return `Array must contain at least ${min} item${min === 1 ? "" : "s"}`
return `Array must contain at least ${min} item${min === 1 ? "" : "s"}`
}
}
@@ -398,14 +399,14 @@ export const validateMaxItems = (val, max) => {
export const validateMinLength = (val, min) => {
if (val.length < min) {
return `Value must be at least ${min} character${min !== 1 ? "s" : ""}`
return `Value must be at least ${min} character${min !== 1 ? "s" : ""}`
}
}
export const validatePattern = (val, rxPattern) => {
var patt = new RegExp(rxPattern)
if (!patt.test(val)) {
return "Value must follow pattern " + rxPattern
return "Value must follow pattern " + rxPattern
}
}
@@ -823,10 +824,10 @@ export function generateCodeVerifier() {
export function createCodeChallenge(codeVerifier) {
return b64toB64UrlEncoded(
shaJs("sha256")
shaJs("sha256")
.update(codeVerifier)
.digest("base64")
)
)
}
function b64toB64UrlEncoded(str) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

View File

@@ -1,127 +0,0 @@
# Add a plugin
### Swagger-UI relies on plugins for all the good stuff.
Plugins allow you to add
- `statePlugins`
- `selectors` - query the state
- `reducers` - modify the state
- `actions` - fire and forget, that will eventually be handled by a reducer. You *can* rely on the result of async actions. But in general it's not recommended
- `wrapActions` - replace an action with a wrapped action (useful for hooking into existing `actions`)
- `components` - React components
- `fn` - commons functions
To add a plugin we include it in the configs...
```js
SwaggerUI({
url: 'some url',
plugins: [ ... ]
})
```
Or if you're updating the core plugins.. you'll add it to the base preset: [src/core/presets/base.js](https://github.com/swagger-api/swagger-ui/blob/master/src/core/presets/base.js)
Each Plugin is a function that returns an object. That object will get merged with the `system` and later bound to the state.
Here is an example of each `type`
```js
// A contrived, but quite full example....
export function SomePlugin(toolbox) {
const UPDATE_SOMETHING = "some_namespace_update_something" // strings just need to be uniuqe... see below
// Tools
const fromJS = toolbox.Im.fromJS // needed below
const createSelector = toolbox.createSelector // same, needed below
return {
statePlugins: {
someNamespace: {
actions: {
actionName: (args)=> ({type: UPDATE_SOMETHING, payload: args}), // Synchronous action must return an object for the reducer to handle
anotherAction: (a,b,c) => (system) => system.someNamespaceActions.actionName(a || b) // Asynchronous actions must return a function. The function gets the whole system, and can call other actions (based on state if needed)
},
wrapActions: {
anotherAction: (oriAction, system) => (...args) => {
oriAction(...args) // Usually we at least call the original action
system.someNamespace.actionName(...args) // why not call this?
console.log("args", args) // Log the args
// anotherAction in the someNamespace has now been replaced with the this function
}
},
reducers: {
[UPDATE_SOMETHING]: (state, action) => { // Take a state (which is immutable) and an action (see synchronous actions) and return a new state
return state.set("something", fromJS(action.payload)) // we're updating the Immutable state object... we need to convert vanilla objects into an immutable type (fromJS)
// See immutable about how to modify the state
// PS: you're only working with the state under the namespace, in this case "someNamespace". So you can do what you want, without worrying about /other/ namespaces
}
},
selectors: {
// creatSelector takes a list of fn's and passes all the results to the last fn.
// eg: createSelector(a => a, a => a+1, (a,a2) => a + a2)(1) // = 3
something: createSelector( // see [reselect#createSelector](https://github.com/reactjs/reselect#createselectorinputselectors--inputselectors-resultfunc)
getState => getState(), // This is a requirement... because we `bind` selectors, we don't want to bind to any particular state (which is an immutable value) so we bind to a function, which returns the current state
state => state.get("something") // return the whatever "something" points to
),
foo: getState => "bar" // In the end selectors are just functions that we pass getState to
}
}
... // you can include as many namespaces as you want. They just get merged into the 'system'
},
components: {
foo: ()=> <h1> Hello </h1> // just a map of names to react components, naturally you'd want to import a fuller react component
},
fn: {
addOne: (a) => a + 1 // just any extra functions you want to include
}
}
}
```
>The plugin factory gets one argument, which I like to call `toolbox`.
This argument is the entire plugin system (at the point the plugin factory is called). It also includes a reference to the `Immutable` lib, so that plugin authors don't need to include it.
### The Plugin system
Each plugin you include will end up getting merged into the `system`, which is just an object.
Then we bind the `system` to our state. And flatten it, so that we don't need to reach into deep objects
> ie: spec.actions becomes specActions, spec.selectors becomes specSelectors
You can reach this bound system by calling `getSystem` on the store.
`getSystem` is the heart of this whole project. Each container component will receive a spread of props from `getSystem`
here is an example....
```js
class Bobby extends React.Component {
handleClick(e) {
this.props.someNamespaceActions.actionName() // fires an action... which the reducer will *eventually* see
}
render() {
let { someNamespaceSelectors, someNamespaceActions } = this.props // this.props has the whole state spread
let something = someNamespaceSelectors.something() // calls our selector, which returns some state (either an immutable object or value)
return (
<h1 onClick={this.handleClick.bind(this)}> Hello {something} </h1> // render the contents
)
}
}
```
TODO: a lot more elaboration
`

View File

@@ -1,7 +0,0 @@
import Configs from "./configs"
import Topbar from "./topbar"
export default {
Configs,
Topbar
}

View File

@@ -1,11 +0,0 @@
import Topbar from "./topbar"
import Logo from "./logo"
export default function () {
return {
components: {
Topbar,
Logo
}
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import SwaggerUILogo from "./logo_small.svg"
export const Logo = () => (
<img height="40" src={ SwaggerUILogo } alt="Swagger UI"/>
)
export default Logo

View File

@@ -1,24 +0,0 @@
import StandaloneLayout from "./layout"
import TopbarPlugin from "plugins/topbar"
import ConfigsPlugin from "corePlugins/configs"
import SafeRenderPlugin from "core/plugins/safe-render"
// the Standalone preset
export default [
TopbarPlugin,
ConfigsPlugin,
() => {
return {
components: { StandaloneLayout }
}
},
SafeRenderPlugin({
fullOverride: true,
componentList: [
"Topbar",
"StandaloneLayout",
"onlineValidatorBadge"
]
})
]

View File

@@ -1,7 +1,7 @@
import React from "react"
import PropTypes from "prop-types"
export default class StandaloneLayout extends React.Component {
class StandaloneLayout extends React.Component {
static propTypes = {
errSelectors: PropTypes.object.isRequired,
@@ -36,3 +36,5 @@ export default class StandaloneLayout extends React.Component {
}
}
export default StandaloneLayout

View File

@@ -0,0 +1,12 @@
/**
* @prettier
*/
import StandaloneLayout from "./components/StandaloneLayout"
const StandaloneLayoutPlugin = () => ({
components: {
StandaloneLayout,
},
})
export default StandaloneLayoutPlugin

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,9 @@
/**
* @prettier
*/
import React from "react"
import SwaggerUILogo from "../assets/logo_small.svg"
const Logo = () => <img height="40" src={SwaggerUILogo} alt="Swagger UI" />
export default Logo

View File

@@ -1,10 +1,9 @@
import React, { cloneElement } from "react"
import PropTypes from "prop-types"
//import "./topbar.less"
import {parseSearch, serializeSearch} from "../../core/utils"
import {parseSearch, serializeSearch} from "core/utils"
export default class Topbar extends React.Component {
class TopBar extends React.Component {
static propTypes = {
layoutActions: PropTypes.object.isRequired,
@@ -163,9 +162,11 @@ export default class Topbar extends React.Component {
}
}
Topbar.propTypes = {
TopBar.propTypes = {
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired
}
export default TopBar

View File

@@ -0,0 +1,11 @@
/**
* @prettier
*/
import TopBar from "./components/TopBar"
import Logo from "./components/Logo"
const TopBarPlugin = () => ({
components: { Topbar: TopBar, Logo },
})
export default TopBarPlugin

View File

@@ -0,0 +1,19 @@
/**
* @prettier
*/
import StandaloneLayoutPlugin from "standalone/plugins/stadalone-layout"
import TopBarPlugin from "standalone/plugins/top-bar"
import ConfigsPlugin from "core/plugins/configs"
import SafeRenderPlugin from "core/plugins/safe-render"
const StandalonePreset = [
TopBarPlugin,
ConfigsPlugin,
StandaloneLayoutPlugin,
SafeRenderPlugin({
fullOverride: true,
componentList: ["Topbar", "StandaloneLayout", "onlineValidatorBadge"],
}),
]
export default StandalonePreset