Skip to content

Commit

Permalink
Add experimental plugin accumulator (#42)
Browse files Browse the repository at this point in the history
* WIP isAccumulator support

* reverse order of accumulatePluginOptions so that it can be used for the normal HOC path as well as the new __isAccumulator case

* add default value to coerceArray to handle undefined and de-shadow keyCommandListener value
  • Loading branch information
Ben Briggs authored Jan 24, 2020
1 parent 7edeaec commit b85835d
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 48 deletions.
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -10,6 +11,7 @@ export {
Toolbar,
KeyCommandController,
createPlugin,
accumulatePluginOptions,
pluginUtils,
compose
compose,
};
75 changes: 75 additions & 0 deletions src/plugins/accumulatePluginOptions.js
Original file line number Diff line number Diff line change
@@ -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,
};
};
86 changes: 44 additions & 42 deletions src/plugins/createPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand All @@ -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())
Expand Down Expand Up @@ -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 (
<ToWrap
{...this.props}
ref="child"
styleMap={newStyleMap}
styleFn={newStyleFn}
decorators={newDecorators}
buttons={newButtons}
overlays={newOverlays}
blockRendererFn={newBlockRendererFn}
blockStyleFn={newBlockStyleFn}
keyBindingFn={newKeyBindingFn}
keyCommandListeners={newKeyCommandListeners}
/>
);
// keyCommandListener isn't used by the Editor component or other plugin
// HOCs but keyCommandListeners is
const {
__keyCommandListener,
...editorPluginOptions
} = pluginAccumulation;

return <ToWrap {...this.props} ref="child" {...editorPluginOptions} />;
}
}

Expand Down Expand Up @@ -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) => {
Expand Down
17 changes: 12 additions & 5 deletions src/util/accumulateFunction.js
Original file line number Diff line number Diff line change
@@ -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;
};
};

0 comments on commit b85835d

Please sign in to comment.