@atlaskit/editor-plugin-selection-extension
Version:
editor-plugin-selection-extension plugin for @atlaskit/editor-core
326 lines (319 loc) • 15.4 kB
JavaScript
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;
}
};
}
});
}
}];
}
};
};