UNPKG

@atlaskit/editor-plugin-decorations

Version:

Decorations plugin for @atlaskit/editor-core

127 lines (125 loc) 4.56 kB
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { NodeSelection, PluginKey } from '@atlaskit/editor-prosemirror/state'; import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view'; import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; export const decorationStateKey = new PluginKey('decorationPlugin'); export let ACTIONS = /*#__PURE__*/function (ACTIONS) { ACTIONS[ACTIONS["DECORATION_ADD"] = 0] = "DECORATION_ADD"; ACTIONS[ACTIONS["DECORATION_REMOVE"] = 1] = "DECORATION_REMOVE"; return ACTIONS; }({}); export const removeDecoration = (state, dispatch) => { const { tr } = state; const { decoration } = decorationStateKey.getState(state); if (decoration && dispatch) { dispatch(tr.setMeta(decorationStateKey, { action: ACTIONS.DECORATION_REMOVE })); return true; } return false; }; export const hoverDecoration = (nodeType, add, className = 'danger') => (state, dispatch) => { let from; let parentNode; if (state.selection instanceof NodeSelection) { const selectedNode = state.selection.node; const nodeTypes = Array.isArray(nodeType) ? nodeType : [nodeType]; const isNodeTypeMatching = nodeTypes.indexOf(selectedNode.type) > -1; // This adds danger styling if the selected node is the one that requires // the decoration to be added, e.g. if a layout is selected and the user // hovers over the layout's delete button. if (isNodeTypeMatching) { from = state.selection.from; parentNode = selectedNode; } } // This adds danger styling if the selection is not a node selection, OR if // the selected node is a child of the one that requires the decoration to // be added, e.g. if a decision item is selected inside a layout and the // user hovers over the layout's delete button. const foundParentNode = findParentNodeOfType(nodeType)(state.selection); if (from === undefined && foundParentNode) { from = foundParentNode.pos; parentNode = foundParentNode.node; } // Note: can't use !from as from could be 0, which is falsy but valid if (from === undefined || parentNode === undefined) { return false; } if (dispatch) { dispatch(state.tr.setMeta(decorationStateKey, { action: add ? ACTIONS.DECORATION_ADD : ACTIONS.DECORATION_REMOVE, data: Decoration.node(from, from + parentNode.nodeSize, { class: className }, { key: 'decorationNode' }) }).setMeta('addToHistory', false)); } return true; }; export default (() => { return new SafePlugin({ key: decorationStateKey, state: { init: () => ({ decoration: undefined }), apply(tr, pluginState) { if (pluginState.decoration && pluginState.decoration instanceof Decoration) { const mapResult = tr.mapping.mapResult(pluginState.decoration.from); if (mapResult.deleted) { pluginState = { decoration: undefined }; } } if (pluginState.decoration && pluginState.decoration instanceof DecorationSet && editorExperiment('platform_editor_block_menu', true)) { pluginState.decoration = pluginState.decoration.map(tr.mapping, tr.doc); } const meta = tr.getMeta(decorationStateKey); if (!meta) { return pluginState; } switch (meta.action) { case ACTIONS.DECORATION_ADD: return { decoration: meta.data, hasDangerDecorations: expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) ? meta.hasDangerDecorations : undefined }; case ACTIONS.DECORATION_REMOVE: return { decoration: undefined, hasDangerDecorations: undefined }; default: return pluginState; } } }, props: { decorations(state) { const { doc } = state; const { decoration } = decorationStateKey.getState(state); if (decoration instanceof Decoration) { return DecorationSet.create(doc, [decoration]); } if (decoration instanceof DecorationSet && editorExperiment('platform_editor_block_menu', true)) { return decoration; } return null; } } }); });