diff --git a/src/index.js b/src/index.js index 9829a68..de42974 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import Editor from './components/Editor'; import Toolbar from './components/Toolbar'; import KeyCommandController from './components/KeyCommandController'; import createPlugin from './plugins/createPlugin'; +import accumulatePluginOptions from './plugins/accumulatePluginOptions'; import pluginUtils from './plugins/utils'; import compose from './util/compose'; @@ -10,6 +11,7 @@ export { Toolbar, KeyCommandController, createPlugin, + accumulatePluginOptions, pluginUtils, - compose + compose, }; diff --git a/src/plugins/accumulatePluginOptions.js b/src/plugins/accumulatePluginOptions.js new file mode 100644 index 0000000..368d9fc --- /dev/null +++ b/src/plugins/accumulatePluginOptions.js @@ -0,0 +1,75 @@ +import accumulateFunction from '../util/accumulateFunction'; +import memoize from '../util/memoize'; + +const emptyFunction = () => {}; +const emptyArray = []; +const emptyObject = {}; + +const memoizedAccumulateFunction = memoize(accumulateFunction); +const memoizedAssign = memoize((...args) => Object.assign({}, ...args)); +const memoizedConcat = memoize((a1, a2) => a1.concat(a2)); +const memoizedCoerceArray = memoize((arg = []) => + Array.isArray(arg) ? arg : [arg] +); + +export default (accumulation, pluginConfig) => { + const accumulationWithDefaults = { + styleMap: emptyObject, + styleFn: emptyFunction, + decorators: emptyArray, + buttons: emptyArray, + overlays: emptyArray, + blockRendererFn: emptyFunction, + blockStyleFn: emptyFunction, + keyBindingFn: emptyFunction, + keyCommandListeners: emptyArray, + ...accumulation, + }; + + const { + styleMap, + styleFn, + decorators, + buttons, + overlays, + blockRendererFn, + blockStyleFn, + keyBindingFn, + keyCommandListener, + } = pluginConfig; + + const keyCommandListeners = memoizedConcat( + accumulationWithDefaults.keyCommandListeners, + memoizedCoerceArray(keyCommandListener) + ); + + return { + ...accumulationWithDefaults, + styleMap: memoizedAssign(accumulationWithDefaults.styleMap, styleMap), + styleFn: memoizedAccumulateFunction( + accumulationWithDefaults.styleFn, + styleFn + ), + decorators: memoizedConcat(accumulationWithDefaults.decorators, decorators), + buttons: memoizedConcat(accumulationWithDefaults.buttons, buttons), + overlays: memoizedConcat(accumulationWithDefaults.overlays, overlays), + blockRendererFn: memoizedAccumulateFunction( + blockRendererFn, + accumulationWithDefaults.blockRendererFn + ), + blockStyleFn: memoizedAccumulateFunction( + blockStyleFn, + accumulationWithDefaults.blockStyleFn + ), + keyBindingFn: memoizedAccumulateFunction( + keyBindingFn, + accumulationWithDefaults.keyBindingFn + ), + + // `createPlugin` expects a singular `keyCommandListener`, but Editor + // component props expect the plural `keyCommandListeners`, so return both + // since this is used in both contexts + keyCommandListeners, + keyCommandListener: keyCommandListeners, + }; +}; diff --git a/src/plugins/createPlugin.js b/src/plugins/createPlugin.js index 0dee549..40c68ab 100644 --- a/src/plugins/createPlugin.js +++ b/src/plugins/createPlugin.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { OrderedSet } from 'immutable'; import memoize from '../util/memoize'; import compose from '../util/compose'; -import accumulateFunction from '../util/accumulateFunction'; import middlewareAdapter from '../util/middlewareAdapter'; +import accumulatePluginOptions from './accumulatePluginOptions'; const emptyFunction = () => {}; const emptyArray = []; @@ -14,9 +14,6 @@ const defaultMiddlewareFunction = next => (...args) => next(...args); defaultMiddlewareFunction.__isMiddleware = true; const memoizedCompose = memoize(compose); -const memoizedAccumulateFunction = memoize(accumulateFunction); -const memoizedAssign = memoize((...args) => Object.assign({}, ...args)); -const memoizedConcat = memoize((a1, a2) => a1.concat(a2)); const memoizedCoerceArray = memoize(arg => (Array.isArray(arg) ? arg : [arg])); const memoizedPassEmptyStyles = memoize(func => (nodeName, node) => func(nodeName, node, OrderedSet()) @@ -73,46 +70,39 @@ const createPlugin = ({ } render() { - const newStyleMap = memoizedAssign(this.props.styleMap, styleMap); - const newStyleFn = memoizedAccumulateFunction( - this.props.styleFn, - styleFn - ); - const newDecorators = memoizedConcat(this.props.decorators, decorators); - const newButtons = memoizedConcat(this.props.buttons, buttons); - const newOverlays = memoizedConcat(this.props.overlays, overlays); - const newBlockRendererFn = memoizedAccumulateFunction( - blockRendererFn, - this.props.blockRendererFn - ); - const newBlockStyleFn = memoizedAccumulateFunction( - blockStyleFn, - this.props.blockStyleFn - ); - const newKeyBindingFn = memoizedAccumulateFunction( - keyBindingFn, - this.props.keyBindingFn - ); - const newKeyCommandListeners = memoizedConcat( - this.props.keyCommandListeners, - memoizedCoerceArray(keyCommandListener) + const pluginAccumulation = accumulatePluginOptions( + { + styleMap: this.props.styleMap, + styleFn: this.props.styleFn, + decorators: this.props.decorators, + buttons: this.props.buttons, + overlays: this.props.overlays, + blockRendererFn: this.props.blockRendererFn, + blockStyleFn: this.props.blockStyleFn, + keyBindingFn: this.props.keyBindingFn, + keyCommandListeners: this.props.keyCommandListeners, + }, + { + styleMap, + styleFn, + decorators, + buttons, + overlays, + blockRendererFn, + blockStyleFn, + keyBindingFn, + keyCommandListener, + } ); - return ( - - ); + // keyCommandListener isn't used by the Editor component or other plugin + // HOCs but keyCommandListeners is + const { + __keyCommandListener, + ...editorPluginOptions + } = pluginAccumulation; + + return ; } } @@ -143,6 +133,18 @@ const createPlugin = ({ }; return Plugin; + } else if (ToWrap && ToWrap.__isAccumulator) { + return accumulatePluginOptions(ToWrap, { + styleMap, + styleFn, + decorators, + buttons, + overlays, + blockRendererFn, + blockStyleFn, + keyBindingFn, + keyCommandListener, + }); } else { // wrapping a converter function return (...args) => { diff --git a/src/util/accumulateFunction.js b/src/util/accumulateFunction.js index 021321c..f694135 100644 --- a/src/util/accumulateFunction.js +++ b/src/util/accumulateFunction.js @@ -1,9 +1,16 @@ // utility function to accumulate the common plugin option function pattern of // handling args by returning a non-null result or delegate to other plugins -export default (newFn, acc) => (...args) => { - const result = newFn(...args); - if (result === null || result === undefined) { - return acc(...args); +const emptyFunction = () => {}; +export default (newFn, acc = emptyFunction) => { + if (!newFn) { + return acc; } - return result; + + return (...args) => { + const result = newFn(...args); + if (result === null || result === undefined) { + return acc(...args); + } + return result; + }; };