diff --git a/.babelrc b/.babelrc index bdd506769f3..9a606b0b15c 100644 --- a/.babelrc +++ b/.babelrc @@ -9,6 +9,10 @@ [ "module-alias", [ + { + "expose": "root", + "src": "." + }, { "expose": "components", "src": "src/core/components" diff --git a/docs/customization/plugin-api.md b/docs/customization/plugin-api.md index 245b5a14433..ee08ba07026 100644 --- a/docs/customization/plugin-api.md +++ b/docs/customization/plugin-api.md @@ -19,7 +19,8 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam }, components: {}, wrapComponents: {}, - fn: {} + afterLoad: (system) => {} + fn: {}, } ``` @@ -363,7 +364,35 @@ const MyWrapComponentPlugin = function(system) { } ``` +##### `afterLoad` +The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered with the system. + +This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the system. + +```javascript +const MyMethodProvidingPlugin = function() { + return { + afterLoad(system) { + // at this point in time, your actions have been bound into the system + // so you can do things with them + system.myMethod = system.exampleActions.updateFavoriteColor + }, + statePlugins: { + example: { + actions: { + updateFavoriteColor: (str) => { + return { + type: "EXAMPLE_SET_FAV_COLOR", + payload: str + } + } + } + } + } + } +} +``` ##### fn diff --git a/src/core/index.js b/src/core/index.js index 52e0f931241..15f0e798e56 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -92,6 +92,20 @@ module.exports = function SwaggerUI(opts) { }, constructorConfig.initialState) } + if(constructorConfig.initialState) { + // if the user sets a key as `undefined`, that signals to us that we + // should delete the key entirely. + // known usage: Swagger-Editor validate plugin tests + for (var key in constructorConfig.initialState) { + if( + constructorConfig.initialState.hasOwnProperty(key) + && constructorConfig.initialState[key] === undefined + ) { + delete storeConfigs.state[key] + } + } + } + let inlinePlugin = ()=> { return { fn: constructorConfig.fn, @@ -105,8 +119,6 @@ module.exports = function SwaggerUI(opts) { var system = store.getSystem() - system.initOAuth = system.authActions.configureAuth - const downloadSpec = (fetchedConfig) => { if(typeof constructorConfig !== "object") { return system @@ -138,6 +150,9 @@ module.exports = function SwaggerUI(opts) { } else if(mergedConfig.dom_id) { let domNode = document.querySelector(mergedConfig.dom_id) system.render(domNode, "App") + } else if(mergedConfig.dom_id === null || mergedConfig.domNode === null) { + // do nothing + // this is useful for testing that does not need to do any rendering } else { console.error("Skipped rendering: no `dom_id` or `domNode` was specified") } diff --git a/src/core/plugins/auth/index.js b/src/core/plugins/auth/index.js index 0937f805cb1..7e9413f4a53 100644 --- a/src/core/plugins/auth/index.js +++ b/src/core/plugins/auth/index.js @@ -5,6 +5,9 @@ import * as specWrapActionReplacements from "./spec-wrap-actions" export default function() { return { + afterLoad(system) { + system.initOAuth = system.authActions.configureAuth + }, statePlugins: { auth: { reducers, diff --git a/src/plugins/configs/actions.js b/src/core/plugins/configs/actions.js similarity index 100% rename from src/plugins/configs/actions.js rename to src/core/plugins/configs/actions.js diff --git a/src/plugins/configs/index.js b/src/core/plugins/configs/index.js similarity index 96% rename from src/plugins/configs/index.js rename to src/core/plugins/configs/index.js index 58edbfa1a01..0ab33c96808 100644 --- a/src/plugins/configs/index.js +++ b/src/core/plugins/configs/index.js @@ -1,5 +1,5 @@ import YAML from "js-yaml" -import yamlConfig from "../../../swagger-config.yaml" +import yamlConfig from "root/swagger-config.yaml" import * as actions from "./actions" import * as selectors from "./selectors" import reducers from "./reducers" diff --git a/src/plugins/configs/reducers.js b/src/core/plugins/configs/reducers.js similarity index 100% rename from src/plugins/configs/reducers.js rename to src/core/plugins/configs/reducers.js diff --git a/src/plugins/configs/selectors.js b/src/core/plugins/configs/selectors.js similarity index 100% rename from src/plugins/configs/selectors.js rename to src/core/plugins/configs/selectors.js diff --git a/src/core/plugins/err/error-transformers/hook.js b/src/core/plugins/err/error-transformers/hook.js index badec81db58..e33dbb6f614 100644 --- a/src/core/plugins/err/error-transformers/hook.js +++ b/src/core/plugins/err/error-transformers/hook.js @@ -1,26 +1,13 @@ import reduce from "lodash/reduce" -let request = require.context("./transformers/", true, /\.js$/) -let errorTransformers = [] +import * as NotOfType from "./transformers/not-of-type" +import * as ParameterOneOf from "./transformers/parameter-oneof" +import * as StripInstance from "./transformers/strip-instance" -request.keys().forEach( function( key ){ - if( key === "./hook.js" ) { - return - } - - if( !key.match(/js$/) ) { - return - } - - if( key.slice(2).indexOf("/") > -1) { - // skip files in subdirs - return - } - - errorTransformers.push({ - name: toTitleCase(key).replace(".js", "").replace("./", ""), - transform: request(key).transform - }) -}) +const errorTransformers = [ + NotOfType, + ParameterOneOf, + StripInstance +] export default function transformErrors (errors, system) { let inputs = { @@ -47,10 +34,3 @@ export default function transformErrors (errors, system) { }) } - -function toTitleCase(str) { - return str - .split("-") - .map(substr => substr[0].toUpperCase() + substr.slice(1)) - .join("") -} diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js index 6b6324ce7fe..a0cf3588308 100644 --- a/src/core/plugins/spec/actions.js +++ b/src/core/plugins/spec/actions.js @@ -105,8 +105,7 @@ export const resolveSpec = (json, url) => ({specActions, specSelectors, errActio errActions.clear({ type: "thrown" }) - - if(errors.length > 0) { + if(Array.isArray(errors) && errors.length > 0) { let preparedErrors = errors .map(err => { console.error(err) diff --git a/src/core/plugins/split-pane-mode/components/index.js b/src/core/plugins/split-pane-mode/components/index.js deleted file mode 100644 index 7149382d05b..00000000000 --- a/src/core/plugins/split-pane-mode/components/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import { pascalCaseFilename } from "core/utils" - -const request = require.context(".", true, /\.jsx?$/) - -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) - module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod -}) diff --git a/src/core/plugins/split-pane-mode/index.js b/src/core/plugins/split-pane-mode/index.js index 9b4d285e504..913738c48ca 100644 --- a/src/core/plugins/split-pane-mode/index.js +++ b/src/core/plugins/split-pane-mode/index.js @@ -1,5 +1,4 @@ -import * as components from "./components" - +import SplitPaneMode from "./components/split-pane-mode" export default function SplitPaneModePlugin() { return { // statePlugins: { @@ -9,6 +8,8 @@ export default function SplitPaneModePlugin() { // } // }, - components, + components: { + SplitPaneMode + } } } diff --git a/src/core/presets/base.js b/src/core/presets/base.js index 1521fa285e5..65bf891e8f2 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -10,7 +10,7 @@ import auth from "core/plugins/auth" import util from "core/plugins/util" import SplitPaneModePlugin from "core/plugins/split-pane-mode" import downloadUrlPlugin from "core/plugins/download-url" -import configsPlugin from "plugins/configs" +import configsPlugin from "core/plugins/configs" import deepLinkingPlugin from "core/plugins/deep-linking" import OperationContainer from "core/containers/OperationContainer" diff --git a/src/core/system.js b/src/core/system.js index 22707ae4d38..87a258877c1 100644 --- a/src/core/system.js +++ b/src/core/system.js @@ -68,6 +68,14 @@ export default class Store { if(rebuild) { this.buildSystem() } + + if(Array.isArray(plugins)) { + plugins.forEach(plugin => { + if(plugin.afterLoad) { + plugin.afterLoad(this.getSystem()) + } + }) + } } buildSystem(buildReducer=true) { diff --git a/src/plugins/index.js b/src/plugins/index.js index 5cbd4b9c404..f143ff0b3c9 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,17 +1,7 @@ -import { pascalCaseFilename } from "js/utils" +import Configs from "./configs" +import Topbar from "./topbar" -const request = require.context(".", true, /\.jsx?$/) - -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) - module.exports[pascalCaseFilename(key)] = mod.default ? mod.default : mod -}) +export default { + Configs, + Topbar +} diff --git a/test/core/system/system.js b/test/core/system/system.js index 073390fa132..205fc8a603b 100644 --- a/test/core/system/system.js +++ b/test/core/system/system.js @@ -683,4 +683,31 @@ describe("bound system", function(){ }) }) + describe("afterLoad", function() { + it("should call an plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const system = new System({ + plugins: [ + { + afterLoad(system) { + system.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + ] + }) + + // When + var res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + }) })