@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
98 lines (97 loc) • 2.98 kB
JavaScript
/**
* Creates a ProseMirror plugin's state and handles UI updates.
*
* Here's a few things to keep in mind:
* - plugin's state is stored as a single object
* - `Reducer` specifies how plugin's state changes in response to commands
* - `Command` describes only what happen, but not how state changes
* - `mapping` could be used to map ProseMirror positions stored in plugin's state
*
* Example:
* const { createPluginState, createCommand, getPluginState } = pluginFactory(
* reducer,
* pluginKey
* );
*
* export const createPlugin = (dispatch: Dispatch, initialState) =>
* new SafePlugin({
* state: createPluginState(dispatch, initialState),
* key: pluginKey
* });
*
* Example of a reducer:
*
* export const reducer = (
* state: TablePluginState,
* action: TableAction,
* ): TablePluginState => {
* switch (action.type) {
* case 'TOGGLE_CONTEXTUAL_MENU':
* return {
* ...state,
* isContextualMenuOpen: !state.isContextualMenuOpen,
* };
* default:
* return state;
* }
* };
*
*
* Example of a command:
*
* export const toggleContextualMenu = createCommand({
* type: 'TOGGLE_CONTEXTUAL_MENU',
* }, tr => tr.setMeta('addToHistory', false));
*
*/
function isFunction(x) {
return typeof x === 'function';
}
export function pluginFactory(pluginKey, reducer, options = {}) {
const {
mapping,
onDocChanged,
onSelectionChanged
} = options;
return {
createPluginState(dispatch, initialState) {
return {
init: (_, state) => isFunction(initialState) ? initialState(state) : initialState,
apply: (tr, _pluginState, _oldEditorState, newEditorState) => {
const oldPluginState = mapping ? mapping(tr, _pluginState, newEditorState) : _pluginState;
let newPluginState = oldPluginState;
const meta = tr.getMeta(pluginKey);
if (meta) {
newPluginState = reducer(oldPluginState, meta);
}
if (onDocChanged && tr.docChanged) {
newPluginState = onDocChanged(tr, newPluginState, newEditorState);
} else if (onSelectionChanged && tr.selectionSet) {
newPluginState = onSelectionChanged(tr, newPluginState, newEditorState);
}
if (newPluginState !== oldPluginState) {
dispatch(pluginKey, newPluginState);
}
return newPluginState;
}
};
},
createCommand(action, transform) {
return (state, dispatch) => {
if (dispatch) {
const tr = transform ? transform(state.tr, state) : state.tr;
const resolvedAction = isFunction(action) ? action(state) : action;
if (tr && resolvedAction) {
dispatch(tr.setMeta(pluginKey, resolvedAction));
} else {
return false;
}
}
return true;
};
},
getPluginState(state) {
return pluginKey.getState(state);
}
};
}