UNPKG

@atlaskit/editor-plugin-selection-extension

Version:

editor-plugin-selection-extension plugin for @atlaskit/editor-core

326 lines (319 loc) 15.4 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import React from 'react'; import { isSSR } from '@atlaskit/editor-common/core-utils'; import { selectionExtensionMessages } from '@atlaskit/editor-common/messages'; import { SafePlugin } from '@atlaskit/editor-common/safe-plugin'; import { usePluginStateEffect } from '@atlaskit/editor-common/use-plugin-state-effect'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { insertAdfAtEndOfDoc as _insertAdfAtEndOfDoc } from './pm-plugins/actions/insertAdfAtEndOfDoc'; import { replaceWithAdf as _replaceWithAdf } from './pm-plugins/actions/replaceWithAdf'; import { createPlugin, selectionExtensionPluginKey } from './pm-plugins/main'; import { getFragmentInfoFromSelection, getFragmentInfoFromSelectionNew, getSelectionAdfInfo, getSelectionAdfInfoNew, getSelectionTextInfo } from './pm-plugins/utils'; import { SelectionExtensionActionTypes } from './types'; import { SelectionExtensionComponentWrapper } from './ui/extension/SelectionExtensionComponentWrapper'; import { getMenuItemExtensions, getToolbarItemExtensions } from './ui/extensions'; import { LegacyPrimaryToolbarComponent } from './ui/LegacyToolbarComponent'; import { selectionToolbar } from './ui/selectionToolbar'; import { getToolbarComponents } from './ui/toolbar-components'; import { registerBlockMenuItems } from './ui/utils/registerBlockMenuItems'; export var selectionExtensionPlugin = function selectionExtensionPlugin(_ref) { var api = _ref.api, config = _ref.config; var editorViewRef = {}; var cachedSelection; var cachedOverflowMenuOptions; var isToolbarAIFCEnabled = Boolean(api === null || api === void 0 ? void 0 : api.toolbar); var _ref2 = config || {}, _ref2$extensionList = _ref2.extensionList, extensionList = _ref2$extensionList === void 0 ? [] : _ref2$extensionList, _ref2$extensions = _ref2.extensions, extensions = _ref2$extensions === void 0 ? {} : _ref2$extensions; var _ref3 = extensions || {}, _ref3$firstParty = _ref3.firstParty, firstParty = _ref3$firstParty === void 0 ? [] : _ref3$firstParty, _ref3$external = _ref3.external, external = _ref3$external === void 0 ? [] : _ref3$external; if (!isToolbarAIFCEnabled) { var primaryToolbarItemExtensions = getToolbarItemExtensions(extensionList, 'primaryToolbar'); if (primaryToolbarItemExtensions !== null && primaryToolbarItemExtensions !== void 0 && primaryToolbarItemExtensions.length) { var _api$primaryToolbar; api === null || api === void 0 || (_api$primaryToolbar = api.primaryToolbar) === null || _api$primaryToolbar === void 0 || (_api$primaryToolbar = _api$primaryToolbar.actions) === null || _api$primaryToolbar === void 0 || _api$primaryToolbar.registerComponent({ name: 'selectionExtension', component: function component() { return /*#__PURE__*/React.createElement(LegacyPrimaryToolbarComponent, { primaryToolbarItemExtensions: primaryToolbarItemExtensions }); } }); } } if (editorExperiment('platform_editor_block_menu', true, { exposure: true })) { registerBlockMenuItems({ extensionList: extensionList, api: api, editorViewRef: editorViewRef }); } return { name: 'selectionExtension', getSharedState: function getSharedState(editorState) { if (!editorState) { return null; } return selectionExtensionPluginKey.getState(editorState) || null; }, commands: { setActiveExtension: function setActiveExtension(extension) { return function (_ref4) { var tr = _ref4.tr; return tr.setMeta(selectionExtensionPluginKey, { type: 'set-active-extension', extension: extension }); }; }, clearActiveExtension: function clearActiveExtension() { return function (_ref5) { var tr = _ref5.tr; return tr.setMeta(selectionExtensionPluginKey, { type: 'clear-active-extension' }); }; } }, actions: { replaceWithAdf: function replaceWithAdf(nodeAdf) { if (!editorViewRef.current) { return { status: 'failed-to-replace' }; } var _editorViewRef$curren = editorViewRef.current, state = _editorViewRef$curren.state, dispatch = _editorViewRef$curren.dispatch; return _replaceWithAdf(nodeAdf, api)(state, dispatch); }, insertAdfAtEndOfDoc: function insertAdfAtEndOfDoc(nodeAdf) { if (!editorViewRef.current) { return { status: 'failed' }; } var _editorViewRef$curren2 = editorViewRef.current, state = _editorViewRef$curren2.state, dispatch = _editorViewRef$curren2.dispatch; return _insertAdfAtEndOfDoc(nodeAdf)(state, dispatch); }, getSelectionAdf: function getSelectionAdf() { if (!editorViewRef.current) { return null; } var state = editorViewRef.current.state; if (editorExperiment('platform_editor_block_menu', true, { exposure: true })) { var _api$blockControls; var selection = (api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 || (_api$blockControls = _api$blockControls.sharedState.currentState()) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.preservedSelection) || state.selection; return getSelectionAdfInfoNew(selection); } var _getSelectionAdfInfo = getSelectionAdfInfo(state), selectionRanges = _getSelectionAdfInfo.selectionRanges, selectedNodeAdf = _getSelectionAdfInfo.selectedNodeAdf; return { selectedNodeAdf: selectedNodeAdf, selectionRanges: selectionRanges }; }, getDocumentFromSelection: function getDocumentFromSelection() { if (!editorViewRef.current) { return null; } var state = editorViewRef.current.state; if (editorExperiment('platform_editor_block_menu', true, { exposure: true })) { var _api$blockControls2; var selection = (api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.sharedState.currentState()) === null || _api$blockControls2 === void 0 ? void 0 : _api$blockControls2.preservedSelection) || state.selection; return getFragmentInfoFromSelectionNew(selection); } var _getFragmentInfoFromS = getFragmentInfoFromSelection(state), selectedNodeAdf = _getFragmentInfoFromS.selectedNodeAdf; return { selectedNodeAdf: selectedNodeAdf }; } }, usePluginHook: function usePluginHook() { usePluginStateEffect(api, ['selection'], function () { if (isSSR()) { return; } if (isToolbarAIFCEnabled) { var _api$toolbar; api === null || api === void 0 || (_api$toolbar = api.toolbar) === null || _api$toolbar === void 0 || _api$toolbar.actions.registerComponents(getToolbarComponents({ api: api, config: config }), true); } }); }, contentComponent: function contentComponent(_ref6) { var _api$analytics; var editorView = _ref6.editorView; if (!editorView || isSSR()) { return null; } return /*#__PURE__*/React.createElement(SelectionExtensionComponentWrapper, { editorView: editorView, api: api, editorAnalyticsAPI: api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions }); }, pluginsOptions: { selectionToolbar: isToolbarAIFCEnabled ? undefined : function (state, intl) { var _api$editorViewMode; if (!config) { return; } var pageModes = config.pageModes; // Extensions Config Validation // Check whether plugin contains any selection extensions if (!(firstParty !== null && firstParty !== void 0 && firstParty.length) && !(external !== null && external !== void 0 && external.length) && !(extensionList !== null && extensionList !== void 0 && extensionList.length)) { return; } // Content Mode Validation // Check if pageModes is provided and matches against current content mode // This will eventually transition from mode to viewMode var editorViewMode = api === null || api === void 0 || (_api$editorViewMode = api.editorViewMode) === null || _api$editorViewMode === void 0 || (_api$editorViewMode = _api$editorViewMode.sharedState.currentState()) === null || _api$editorViewMode === void 0 ? void 0 : _api$editorViewMode.mode; if (pageModes) { // Early Exit: consumer has set pageModes but editorViewMode is undefined if (!editorViewMode) { return; } // Simplify traversion of pageModes which can be string or array of strings var showOnModesCollection = Array.isArray(pageModes) ? pageModes : [pageModes]; // Early Exit: consumer has set pageModes but current editorViewMode is not in the collection if (!showOnModesCollection.includes(editorViewMode)) { return; } } // Active Extension // Check if there is an active extension and hide the selection extension dropdown var selectionExtensionState = selectionExtensionPluginKey.getState(state); if (selectionExtensionState !== null && selectionExtensionState !== void 0 && selectionExtensionState.activeExtension) { return; } var handleOnExtensionClick = function handleOnExtensionClick(view) { return function (extension) { var _extension$onClick, _api$core; var selection = getSelectionTextInfo(view, api); if (extension.component) { api === null || api === void 0 || api.core.actions.execute(api === null || api === void 0 ? void 0 : api.selectionExtension.commands.setActiveExtension({ extension: extension, selection: selection })); } var _getSelectionAdfInfo2 = getSelectionAdfInfo(view.state), selectedNodeAdf = _getSelectionAdfInfo2.selectedNodeAdf, selectionRanges = _getSelectionAdfInfo2.selectionRanges, selectedNode = _getSelectionAdfInfo2.selectedNode, nodePos = _getSelectionAdfInfo2.nodePos; var onClickCallbackOptions = { selectedNodeAdf: selectedNodeAdf, selectionRanges: selectionRanges }; (_extension$onClick = extension.onClick) === null || _extension$onClick === void 0 || _extension$onClick.call(extension, onClickCallbackOptions); api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref7) { var tr = _ref7.tr; tr.setMeta(selectionExtensionPluginKey, { type: SelectionExtensionActionTypes.SET_SELECTED_NODE, selectedNode: selectedNode, nodePos: nodePos }); return tr; }); }; }; var convertExtensionToDropdownMenuItem = function convertExtensionToDropdownMenuItem(extension, rank) { var _extension$isDisabled; var disabled = (extension === null || extension === void 0 ? void 0 : extension.isDisabled) instanceof Function ? extension === null || extension === void 0 || (_extension$isDisabled = extension.isDisabled) === null || _extension$isDisabled === void 0 ? void 0 : _extension$isDisabled.call(extension, { selection: editorViewRef.current ? getSelectionTextInfo(editorViewRef.current, api) : undefined }) : extension === null || extension === void 0 ? void 0 : extension.isDisabled; return { title: extension.name, icon: extension.icon ? /*#__PURE__*/React.createElement(extension.icon, { label: '' }) : undefined, disabled: disabled, rank: rank, onClick: function onClick() { editorViewRef.current && handleOnExtensionClick(editorViewRef.current)(extension); return true; } }; }; var getFirstPartyExtensions = function getFirstPartyExtensions(extensions) { return extensions.map(function (extension) { return convertExtensionToDropdownMenuItem(extension, 30); }); }; // Add a heading to the external extensions var getExternalExtensions = function getExternalExtensions(extensions) { var externalExtensions = []; if (extensions !== null && extensions !== void 0 && extensions.length) { externalExtensions = extensions.map(function (extension) { return convertExtensionToDropdownMenuItem(extension); }); var externalExtensionsHeading = { type: 'overflow-dropdown-heading', title: intl.formatMessage(selectionExtensionMessages.externalExtensionsHeading) }; externalExtensions.unshift(externalExtensionsHeading); } return externalExtensions; }; // NEXT PR: Make sure we cache the whole generated selection toolbar // also debug this to make sure it's actually preventing unnecessary re-renders / work if (cachedOverflowMenuOptions && state.selection.eq(cachedSelection)) { return selectionToolbar({ overflowOptions: cachedOverflowMenuOptions, extensionList: extensionList }); } var allFirstParty = [].concat(_toConsumableArray(firstParty), _toConsumableArray(getMenuItemExtensions(extensionList, 'first-party'))); var allExternal = [].concat(_toConsumableArray(external), _toConsumableArray(getMenuItemExtensions(extensionList, 'external'))); var groupedExtensionsArray = [].concat(_toConsumableArray(getFirstPartyExtensions(allFirstParty)), _toConsumableArray(getExternalExtensions(allExternal))); cachedOverflowMenuOptions = groupedExtensionsArray; cachedSelection = state.selection; return selectionToolbar({ overflowOptions: cachedOverflowMenuOptions, extensionList: extensionList }); } }, pmPlugins: function pmPlugins() { return [{ name: 'selectionExtension', plugin: function plugin() { return createPlugin(); } }, { name: 'selectionExtensionGetEditorViewReferencePlugin', plugin: function plugin() { return new SafePlugin({ view: function view(editorView) { editorViewRef.current = editorView; return { destroy: function destroy() { delete editorViewRef.current; } }; } }); } }]; } }; };