UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

358 lines (353 loc) 20.2 kB
import React from 'react'; import { expandSelectionBounds } from '@atlaskit/editor-common/selection'; import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check'; import { TextSelection } from '@atlaskit/editor-prosemirror/state'; import { fg } from '@atlaskit/platform-feature-flags'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { handleKeyDownWithPreservedSelection } from './editor-commands/handle-key-down-with-preserved-selection'; import { mapPreservedSelection } from './editor-commands/map-preserved-selection'; import { moveNode } from './editor-commands/move-node'; import { moveNodeWithBlockMenu } from './editor-commands/move-node-with-block-menu'; import { moveToLayout } from './editor-commands/move-to-layout'; import { canMoveNodeUpOrDown } from './editor-commands/utils/move-node-utils'; import { firstNodeDecPlugin } from './pm-plugins/first-node-dec-plugin'; import { createInteractionTrackingPlugin, interactionTrackingPluginKey } from './pm-plugins/interaction-tracking/pm-plugin'; import { createPlugin, key } from './pm-plugins/main'; import { startPreservingSelection, stopPreservingSelection } from './pm-plugins/selection-preservation/editor-commands'; import { selectionPreservationPluginKey } from './pm-plugins/selection-preservation/plugin-key'; import { createSelectionPreservationPlugin } from './pm-plugins/selection-preservation/pm-plugin'; import { expandAndUpdateSelection } from './pm-plugins/utils/expand-and-update-selection'; import { selectNode } from './pm-plugins/utils/getSelection'; import { GlobalStylesWrapper } from './ui/global-styles'; export const blockControlsPlugin = ({ api, config }) => { var _config$rightSideCont, _config$quickInsertBu; const nodeDecorationRegistry = []; const rightSideControlsEnabled = (_config$rightSideCont = config === null || config === void 0 ? void 0 : config.rightSideControlsEnabled) !== null && _config$rightSideCont !== void 0 ? _config$rightSideCont : false; const quickInsertButtonEnabled = (_config$quickInsertBu = config === null || config === void 0 ? void 0 : config.quickInsertButtonEnabled) !== null && _config$quickInsertBu !== void 0 ? _config$quickInsertBu : true; return { name: 'blockControls', actions: { registerNodeDecoration: factory => { nodeDecorationRegistry.push(factory); }, unregisterNodeDecoration: type => { const idx = nodeDecorationRegistry.findIndex(f => f.type === type); if (idx !== -1) { nodeDecorationRegistry.splice(idx, 1); } } }, pmPlugins() { const pmPlugins = [{ name: 'blockControlsPmPlugin', plugin: ({ getIntl, nodeViewPortalProviderAPI }) => createPlugin(api, getIntl, nodeViewPortalProviderAPI, nodeDecorationRegistry, rightSideControlsEnabled, quickInsertButtonEnabled) }]; if (editorExperiment('platform_editor_controls', 'variant1')) { pmPlugins.push({ name: 'blockControlsInteractionTrackingPlugin', plugin: () => createInteractionTrackingPlugin(rightSideControlsEnabled) }); } if (editorExperiment('platform_editor_block_menu', true)) { pmPlugins.push({ name: 'blockControlsSelectionPreservationPlugin', plugin: createSelectionPreservationPlugin(api) }); } // platform_editor_controls note: quick insert rendering fixes if (areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar))) { pmPlugins.push({ name: 'firstNodeDec', plugin: firstNodeDecPlugin }); } return pmPlugins; }, commands: { expandAndUpdateSelection: ({ startPos, selection, isShiftPressed, nodeType }) => ({ tr }) => { expandAndUpdateSelection({ tr, selection, startPos, isShiftPressed, nodeType, api: api }); return tr; }, moveNode: moveNode(api), moveToLayout: moveToLayout(api), showDragHandleAt: (pos, anchorName, nodeType, handleOptions, rootPos, rootAnchorName, rootNodeType) => ({ tr }) => { const currMeta = tr.getMeta(key); tr.setMeta(key, { ...currMeta, activeNode: { pos, anchorName, nodeType, handleOptions, rootPos, rootAnchorName, rootNodeType } }); return tr; }, toggleBlockMenu: options => ({ tr }) => { var _api$userIntent, _api$userIntent$share, _api$blockControls, _api$blockControls$sh, _options$anchorName, _api$blockControls2, _api$blockControls2$s; if (!editorExperiment('platform_editor_block_menu', true)) { return tr; } const currMeta = tr.getMeta(key); const currentUserIntent = api === null || api === void 0 ? void 0 : (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 ? void 0 : (_api$userIntent$share = _api$userIntent.sharedState.currentState()) === null || _api$userIntent$share === void 0 ? void 0 : _api$userIntent$share.currentUserIntent; const isMenuCurrentlyOpen = api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : (_api$blockControls$sh = _api$blockControls.sharedState.currentState()) === null || _api$blockControls$sh === void 0 ? void 0 : _api$blockControls$sh.isMenuOpen; if (options !== null && options !== void 0 && options.closeMenu) { tr.setMeta(key, { ...currMeta, closeMenu: true }); if (currentUserIntent === 'blockMenuOpen') { var _api$userIntent2; api === null || api === void 0 ? void 0 : (_api$userIntent2 = api.userIntent) === null || _api$userIntent2 === void 0 ? void 0 : _api$userIntent2.commands.setCurrentUserIntent('default')({ tr }); } // When closing the menu, restart the active session timer if (isMenuCurrentlyOpen && fg('platform_editor_ease_of_use_metrics')) { var _api$metrics; api === null || api === void 0 ? void 0 : (_api$metrics = api.metrics) === null || _api$metrics === void 0 ? void 0 : _api$metrics.commands.startActiveSessionTimer()({ tr }); } return tr; } // Do not open menu on layoutColumn and close opened menu when layoutColumn drag handle is clicked if (options !== null && options !== void 0 && (_options$anchorName = options.anchorName) !== null && _options$anchorName !== void 0 && _options$anchorName.includes('layoutColumn')) { if (currentUserIntent === 'blockMenuOpen') { var _api$userIntent3; api === null || api === void 0 ? void 0 : (_api$userIntent3 = api.userIntent) === null || _api$userIntent3 === void 0 ? void 0 : _api$userIntent3.commands.setCurrentUserIntent('default')({ tr }); } tr.setMeta(key, { ...currMeta, closeMenu: true }); // When closing the menu, restart the active session timer if (isMenuCurrentlyOpen && fg('platform_editor_ease_of_use_metrics')) { var _api$metrics2; api === null || api === void 0 ? void 0 : (_api$metrics2 = api.metrics) === null || _api$metrics2 === void 0 ? void 0 : _api$metrics2.commands.startActiveSessionTimer()({ tr }); } return tr; } let toggleMenuMeta = { anchorName: options === null || options === void 0 ? void 0 : options.anchorName, triggerByNode: options === null || options === void 0 ? void 0 : options.triggerByNode }; const menuTriggerBy = api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$s = _api$blockControls2.sharedState.currentState()) === null || _api$blockControls2$s === void 0 ? void 0 : _api$blockControls2$s.menuTriggerBy; if (options !== null && options !== void 0 && options.anchorName) { const { moveUp, moveDown } = canMoveNodeUpOrDown(tr); toggleMenuMeta = { ...toggleMenuMeta, moveUp, moveDown, openedViaKeyboard: options === null || options === void 0 ? void 0 : options.openedViaKeyboard }; } tr.setMeta(key, { ...currMeta, toggleMenu: toggleMenuMeta }); if ((menuTriggerBy === undefined || !!menuTriggerBy && menuTriggerBy === (options === null || options === void 0 ? void 0 : options.anchorName)) && currentUserIntent === 'blockMenuOpen') { const state = api === null || api === void 0 ? void 0 : api.blockControls.sharedState.currentState(); if (state !== null && state !== void 0 && state.isSelectedViaDragHandle) { var _api$userIntent4; api === null || api === void 0 ? void 0 : (_api$userIntent4 = api.userIntent) === null || _api$userIntent4 === void 0 ? void 0 : _api$userIntent4.commands.setCurrentUserIntent('dragHandleSelected')({ tr }); } else { var _api$userIntent5; // Toggled from drag handle api === null || api === void 0 ? void 0 : (_api$userIntent5 = api.userIntent) === null || _api$userIntent5 === void 0 ? void 0 : _api$userIntent5.commands.setCurrentUserIntent('default')({ tr }); } // When closing the menu, restart the active session timer if (fg('platform_editor_ease_of_use_metrics')) { var _api$metrics3; api === null || api === void 0 ? void 0 : (_api$metrics3 = api.metrics) === null || _api$metrics3 === void 0 ? void 0 : _api$metrics3.commands.startActiveSessionTimer()({ tr }); } } else if (!isMenuCurrentlyOpen) { // When opening the menu, pause the active session timer if (fg('platform_editor_ease_of_use_metrics')) { var _api$metrics4; api === null || api === void 0 ? void 0 : (_api$metrics4 = api.metrics) === null || _api$metrics4 === void 0 ? void 0 : _api$metrics4.commands.handleIntentToStartEdit({ shouldStartTimer: false, shouldPersistActiveSession: true })({ tr }); } } return tr; }, setNodeDragged: (getPos, anchorName, nodeType) => ({ tr }) => { var _api$userIntent6; const pos = getPos(); if (pos === undefined) { return tr; } const currMeta = tr.getMeta(key); tr.setMeta(key, { ...currMeta, isDragging: true, activeNode: { pos, anchorName, nodeType } }); if (fg('platform_editor_ease_of_use_metrics')) { var _api$metrics5; api === null || api === void 0 ? void 0 : (_api$metrics5 = api.metrics) === null || _api$metrics5 === void 0 ? void 0 : _api$metrics5.commands.handleIntentToStartEdit({ shouldStartTimer: false, shouldPersistActiveSession: true })({ tr }); } api === null || api === void 0 ? void 0 : (_api$userIntent6 = api.userIntent) === null || _api$userIntent6 === void 0 ? void 0 : _api$userIntent6.commands.setCurrentUserIntent('dragging')({ tr }); return tr; }, setMultiSelectPositions: (anchor, head) => ({ tr }) => { var _api$selection, _$to$nodeBefore, _$from$nodeAfter; const { anchor: userAnchor, head: userHead } = tr.selection; let $expandedAnchor, $expandedHead; if (anchor !== undefined && head !== undefined) { $expandedAnchor = tr.doc.resolve(anchor); $expandedHead = tr.doc.resolve(head); } else { const expandedSelection = expandSelectionBounds(tr.selection.$anchor, tr.selection.$head); $expandedAnchor = expandedSelection.$anchor; $expandedHead = expandedSelection.$head; } api === null || api === void 0 ? void 0 : (_api$selection = api.selection) === null || _api$selection === void 0 ? void 0 : _api$selection.commands.setManualSelection($expandedAnchor.pos, $expandedHead.pos)({ tr }); const $from = $expandedAnchor.min($expandedHead); const $to = $expandedAnchor.max($expandedHead); let expandedNormalisedSel; if ($from.nodeAfter === $to.nodeBefore) { selectNode(tr, $from.pos, $expandedAnchor.node().type.name, api); expandedNormalisedSel = tr.selection; } else if (((_$to$nodeBefore = $to.nodeBefore) === null || _$to$nodeBefore === void 0 ? void 0 : _$to$nodeBefore.type.name) === 'mediaSingle' || ((_$from$nodeAfter = $from.nodeAfter) === null || _$from$nodeAfter === void 0 ? void 0 : _$from$nodeAfter.type.name) === 'mediaSingle') { expandedNormalisedSel = new TextSelection($expandedAnchor, $expandedHead); tr.setSelection(expandedNormalisedSel); } else { // this is to normalise the selection's boundaries to inline positions, preventing it from collapsing expandedNormalisedSel = TextSelection.between($expandedAnchor, $expandedHead); tr.setSelection(expandedNormalisedSel); } const multiSelectDnD = { anchor: $expandedAnchor.pos, head: $expandedHead.pos, textAnchor: expandedNormalisedSel.anchor, textHead: expandedNormalisedSel.head, userAnchor: userAnchor, userHead: userHead }; const currMeta = tr.getMeta(key); tr.setMeta(key, { ...currMeta, multiSelectDnD }); return tr; }, setSelectedViaDragHandle: isSelectedViaDragHandle => ({ tr }) => { const currMeta = tr.getMeta(key); return tr.setMeta(key, { ...currMeta, isSelectedViaDragHandle }); }, mapPreservedSelection: mapping => mapPreservedSelection(mapping), moveNodeWithBlockMenu: direction => moveNodeWithBlockMenu(api, direction), handleKeyDownWithPreservedSelection: handleKeyDownWithPreservedSelection(api), startPreservingSelection: () => startPreservingSelection, stopPreservingSelection: () => stopPreservingSelection }, getSharedState(editorState) { var _key$getState$isMenuO, _key$getState, _key$getState$menuTri, _key$getState2, _key$getState$menuTri2, _key$getState3, _key$getState$blockMe, _key$getState4, _key$getState$activeN, _key$getState5, _key$getState$activeD, _key$getState6, _key$getState$isDragg, _key$getState7, _key$getState$isPMDra, _key$getState8, _key$getState$multiSe, _key$getState9, _key$getState$isShift, _key$getState0, _key$getState$lastDra, _key$getState1, _interactionTrackingP, _key$getState$isSelec, _key$getState10; if (!editorState) { return undefined; } const sharedState = { isMenuOpen: (_key$getState$isMenuO = (_key$getState = key.getState(editorState)) === null || _key$getState === void 0 ? void 0 : _key$getState.isMenuOpen) !== null && _key$getState$isMenuO !== void 0 ? _key$getState$isMenuO : false, menuTriggerBy: (_key$getState$menuTri = (_key$getState2 = key.getState(editorState)) === null || _key$getState2 === void 0 ? void 0 : _key$getState2.menuTriggerBy) !== null && _key$getState$menuTri !== void 0 ? _key$getState$menuTri : undefined, menuTriggerByNode: (_key$getState$menuTri2 = (_key$getState3 = key.getState(editorState)) === null || _key$getState3 === void 0 ? void 0 : _key$getState3.menuTriggerByNode) !== null && _key$getState$menuTri2 !== void 0 ? _key$getState$menuTri2 : undefined, blockMenuOptions: (_key$getState$blockMe = (_key$getState4 = key.getState(editorState)) === null || _key$getState4 === void 0 ? void 0 : _key$getState4.blockMenuOptions) !== null && _key$getState$blockMe !== void 0 ? _key$getState$blockMe : undefined, activeNode: (_key$getState$activeN = (_key$getState5 = key.getState(editorState)) === null || _key$getState5 === void 0 ? void 0 : _key$getState5.activeNode) !== null && _key$getState$activeN !== void 0 ? _key$getState$activeN : undefined, activeDropTargetNode: (_key$getState$activeD = (_key$getState6 = key.getState(editorState)) === null || _key$getState6 === void 0 ? void 0 : _key$getState6.activeDropTargetNode) !== null && _key$getState$activeD !== void 0 ? _key$getState$activeD : undefined, isDragging: (_key$getState$isDragg = (_key$getState7 = key.getState(editorState)) === null || _key$getState7 === void 0 ? void 0 : _key$getState7.isDragging) !== null && _key$getState$isDragg !== void 0 ? _key$getState$isDragg : false, isPMDragging: (_key$getState$isPMDra = (_key$getState8 = key.getState(editorState)) === null || _key$getState8 === void 0 ? void 0 : _key$getState8.isPMDragging) !== null && _key$getState$isPMDra !== void 0 ? _key$getState$isPMDra : false, multiSelectDnD: (_key$getState$multiSe = (_key$getState9 = key.getState(editorState)) === null || _key$getState9 === void 0 ? void 0 : _key$getState9.multiSelectDnD) !== null && _key$getState$multiSe !== void 0 ? _key$getState$multiSe : undefined, isShiftDown: (_key$getState$isShift = (_key$getState0 = key.getState(editorState)) === null || _key$getState0 === void 0 ? void 0 : _key$getState0.isShiftDown) !== null && _key$getState$isShift !== void 0 ? _key$getState$isShift : undefined, lastDragCancelled: (_key$getState$lastDra = (_key$getState1 = key.getState(editorState)) === null || _key$getState1 === void 0 ? void 0 : _key$getState1.lastDragCancelled) !== null && _key$getState$lastDra !== void 0 ? _key$getState$lastDra : false, isEditing: (_interactionTrackingP = interactionTrackingPluginKey.getState(editorState)) === null || _interactionTrackingP === void 0 ? void 0 : _interactionTrackingP.isEditing, isSelectedViaDragHandle: (_key$getState$isSelec = (_key$getState10 = key.getState(editorState)) === null || _key$getState10 === void 0 ? void 0 : _key$getState10.isSelectedViaDragHandle) !== null && _key$getState$isSelec !== void 0 ? _key$getState$isSelec : false }; if (editorExperiment('platform_editor_controls', 'variant1')) { var _interactionTrackingP2, _interactionTrackingP3, _interactionTrackingP4; sharedState.isMouseOut = (_interactionTrackingP2 = (_interactionTrackingP3 = interactionTrackingPluginKey.getState(editorState)) === null || _interactionTrackingP3 === void 0 ? void 0 : _interactionTrackingP3.isMouseOut) !== null && _interactionTrackingP2 !== void 0 ? _interactionTrackingP2 : false; // rightSideControlsEnabled is the single source of truth (confluence_remix_button_right_side_block_fg from preset) sharedState.rightSideControlsEnabled = rightSideControlsEnabled; sharedState.hoverSide = rightSideControlsEnabled ? (_interactionTrackingP4 = interactionTrackingPluginKey.getState(editorState)) === null || _interactionTrackingP4 === void 0 ? void 0 : _interactionTrackingP4.hoverSide : undefined; } if (editorExperiment('platform_editor_block_menu', true)) { var _selectionPreservatio; sharedState.preservedSelection = (_selectionPreservatio = selectionPreservationPluginKey.getState(editorState)) === null || _selectionPreservatio === void 0 ? void 0 : _selectionPreservatio.preservedSelection; } return sharedState; }, contentComponent() { return /*#__PURE__*/React.createElement(GlobalStylesWrapper, { api: api }); } }; };