UNPKG

@atlaskit/editor-plugin-selection-marker

Version:

Selection marker plugin for @atlaskit/editor-core.

136 lines (135 loc) 8.03 kB
import React, { useEffect, useRef } from 'react'; import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'; import { useSharedPluginStateSelector } from '@atlaskit/editor-common/use-shared-plugin-state-selector'; import { expValEqualsNoExposure } from '@atlaskit/tmp-editor-statsig/exp-val-equals-no-exposure'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { createPlugin, dispatchShouldHideDecorations, key } from './pm-plugins/main'; import { GlobalStylesWrapper } from './ui/global-styles'; export const selectionMarkerPlugin = ({ config, api }) => { return { name: 'selectionMarker', pmPlugins() { return [{ name: 'selectionMarkerPmPlugin', plugin: () => createPlugin(api) }]; }, getSharedState(editorState) { var _key$getState$forceHi, _key$getState, _key$getState2; if (!editorState) { return undefined; } return { isForcedHidden: (_key$getState$forceHi = (_key$getState = key.getState(editorState)) === null || _key$getState === void 0 ? void 0 : _key$getState.forceHide) !== null && _key$getState$forceHi !== void 0 ? _key$getState$forceHi : false, isMarkerActive: !((_key$getState2 = key.getState(editorState)) !== null && _key$getState2 !== void 0 && _key$getState2.shouldHideDecorations) }; }, actions: { // For now this is a very simple locking mechanism that only allows one // plugin to hide / release at a time. hideDecoration: () => { var _api$selectionMarker, _api$selectionMarker$, _api$core; if (api !== null && api !== void 0 && (_api$selectionMarker = api.selectionMarker) !== null && _api$selectionMarker !== void 0 && (_api$selectionMarker$ = _api$selectionMarker.sharedState.currentState()) !== null && _api$selectionMarker$ !== void 0 && _api$selectionMarker$.isForcedHidden) { return undefined; } const success = api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({ tr }) => tr.setMeta(key, { forceHide: true })); if (!success) { return undefined; } return cleanupHiddenDecoration(api); }, queueHideDecoration: setCleanup => { const result = api === null || api === void 0 ? void 0 : api.selectionMarker.actions.hideDecoration(); if (result === undefined) { var _api$selectionMarker2; const cleanup = api === null || api === void 0 ? void 0 : (_api$selectionMarker2 = api.selectionMarker) === null || _api$selectionMarker2 === void 0 ? void 0 : _api$selectionMarker2.sharedState.onChange(({ nextSharedState }) => { if ((nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isForcedHidden) === false && (nextSharedState === null || nextSharedState === void 0 ? void 0 : nextSharedState.isMarkerActive) === false) { const result = api === null || api === void 0 ? void 0 : api.selectionMarker.actions.hideDecoration(); setCleanup(result); cleanup === null || cleanup === void 0 ? void 0 : cleanup(); } }); return cleanup; } setCleanup(result); return () => {}; } }, usePluginHook({ editorView }) { const editorHasNotBeenFocused = useRef(true); useEffect(() => { // relatch when editorView changes (pretty good signal for reinit) editorHasNotBeenFocused.current = true; }, [editorView]); const { hasFocus, isOpen, editorDisabled, showToolbar, hasDangerDecorations, currentUserIntent } = useSharedPluginStateWithSelector(api, ['focus', 'typeAhead', 'editorDisabled', 'toolbar', 'decorations', 'userIntent'], states => { var _states$focusState, _states$typeAheadStat, _states$editorDisable, _states$toolbarState, _states$decorationsSt, _states$userIntentSta; return { hasFocus: (_states$focusState = states.focusState) === null || _states$focusState === void 0 ? void 0 : _states$focusState.hasFocus, isOpen: (_states$typeAheadStat = states.typeAheadState) === null || _states$typeAheadStat === void 0 ? void 0 : _states$typeAheadStat.isOpen, editorDisabled: (_states$editorDisable = states.editorDisabledState) === null || _states$editorDisable === void 0 ? void 0 : _states$editorDisable.editorDisabled, showToolbar: (_states$toolbarState = states.toolbarState) === null || _states$toolbarState === void 0 ? void 0 : _states$toolbarState.shouldShowToolbar, hasDangerDecorations: expValEqualsNoExposure('platform_editor_block_menu', 'isEnabled', true) ? (_states$decorationsSt = states.decorationsState) === null || _states$decorationsSt === void 0 ? void 0 : _states$decorationsSt.hasDangerDecorations : undefined, currentUserIntent: (_states$userIntentSta = states.userIntentState) === null || _states$userIntentSta === void 0 ? void 0 : _states$userIntentSta.currentUserIntent }; }); const isForcedHidden = useSharedPluginStateSelector(api, 'selectionMarker.isForcedHidden'); useEffect(() => { // On editor init we should use this latch to keep the marker hidden until // editor has received focus. This means editor will be initially hidden until // the first focus occurs, and after first focus the normal above rules will // apply if (hasFocus === true) { editorHasNotBeenFocused.current = false; } const isBlockMenuOpen = currentUserIntent === 'blockMenuOpen' && editorExperiment('platform_editor_block_menu', true); /** * There are a number of conditions we should not show the marker, * - Editor has not been focused: to keep the marker hidden until first focus if config is set * - Focus: to ensure it doesn't interrupt the normal cursor * - Typeahead Open: To ensure it doesn't show when we're typing in the typeahead * - Disabled: So that it behaves similar to the renderer in live pages/disabled * - Via the API: If another plugin has requested it to be hidden (force hidden). * - If danger styles is shown in decorationsPlugin, then we don't need to show the selection marker */ const shouldHide = (config === null || config === void 0 ? void 0 : config.hideCursorOnInit) && editorHasNotBeenFocused.current || hasFocus || (isOpen !== null && isOpen !== void 0 ? isOpen : false) || isForcedHidden || (editorDisabled !== null && editorDisabled !== void 0 ? editorDisabled : false) || (showToolbar !== null && showToolbar !== void 0 ? showToolbar : false) || !!hasDangerDecorations && editorExperiment('platform_editor_block_menu', true) || isBlockMenuOpen; requestAnimationFrame(() => dispatchShouldHideDecorations(editorView, shouldHide)); }, [editorView, hasFocus, isOpen, isForcedHidden, editorDisabled, showToolbar, hasDangerDecorations, currentUserIntent]); }, contentComponent() { return /*#__PURE__*/React.createElement(GlobalStylesWrapper, null); } }; }; function cleanupHiddenDecoration(api) { let hasRun = false; return () => { var _api$selectionMarker3, _api$selectionMarker4; if (!hasRun && api !== null && api !== void 0 && (_api$selectionMarker3 = api.selectionMarker) !== null && _api$selectionMarker3 !== void 0 && (_api$selectionMarker4 = _api$selectionMarker3.sharedState.currentState()) !== null && _api$selectionMarker4 !== void 0 && _api$selectionMarker4.isForcedHidden) { var _api$core2; hasRun = true; return api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({ tr }) => tr.setMeta(key, { forceHide: false })); } }; }