@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
599 lines (585 loc) • 29.2 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import React from 'react';
import camelCase from 'lodash/camelCase';
import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, CONTENT_COMPONENT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
import { isSSR } from '@atlaskit/editor-common/core-utils';
import { ErrorBoundary } from '@atlaskit/editor-common/error-boundary';
import {
// Deprecated API - Look at removing this sometime in the future
useSharedPluginState } from '@atlaskit/editor-common/hooks';
import { WithProviders } from '@atlaskit/editor-common/provider-factory';
import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { Popup } from '@atlaskit/editor-common/ui';
import { AllSelection, PluginKey, TextSelection } from '@atlaskit/editor-prosemirror/state';
import { findDomRefAtPos, findSelectedNodeOfType } from '@atlaskit/editor-prosemirror/utils';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { copyNode as _copyNode } from './pm-plugins/commands';
import forceFocusPlugin, { forceFocusSelector } from './pm-plugins/force-focus';
import { hideConfirmDialog } from './pm-plugins/toolbar-data/commands';
import { createPlugin as floatingToolbarDataPluginFactory } from './pm-plugins/toolbar-data/plugin';
import { pluginKey as dataPluginKey } from './pm-plugins/toolbar-data/plugin-key';
import { findNode } from './pm-plugins/utils';
import { ConfirmationModal } from './ui/ConfirmationModal';
import Toolbar from './ui/Toolbar';
import { consolidateOverflowDropdownItems } from './ui/utils';
var SUPPRESS_TOOLBAR_USER_INTENTS = ['dragging', 'tableContextualMenuPopupOpen', 'tableDragMenuPopupOpen', 'commenting', 'resizing', 'blockMenuOpen', 'statusPickerOpen'];
// TODO: AFP-2532 - Fix automatic suppressions below
export var getRelevantConfig = function getRelevantConfig(selection, configs) {
// node selections always take precedence, see if
var configPair;
configs.find(function (config) {
var node = findSelectedNodeOfType(config.nodeType)(selection);
if (node) {
configPair = {
node: node.node,
pos: node.pos,
config: config
};
}
return !!node;
});
if (configPair) {
return configPair;
}
// create mapping of node type name to configs
var configByNodeType = {};
configs.forEach(function (config) {
if (Array.isArray(config.nodeType)) {
config.nodeType.forEach(function (nodeType) {
configByNodeType[nodeType.name] = config;
});
} else {
configByNodeType[config.nodeType.name] = config;
}
});
// search up the tree from selection
var $from = selection.$from;
for (var i = $from.depth; i > 0; i--) {
var node = $from.node(i);
var matchedConfig = configByNodeType[node.type.name];
if (matchedConfig) {
return {
config: matchedConfig,
node: node,
pos: $from.pos
};
}
}
// if it is AllSelection (can be result of Cmd+A) - use first node
if (selection instanceof AllSelection) {
var docNode = $from.node(0);
var _matchedConfig = null;
var firstChild = findNode(docNode, function (node) {
_matchedConfig = configByNodeType[node.type.name];
return !!_matchedConfig;
});
if (firstChild && _matchedConfig) {
return {
config: _matchedConfig,
node: firstChild,
pos: $from.pos
};
}
}
return;
};
var getDomRefFromSelection = function getDomRefFromSelection(view, dispatchAnalyticsEvent) {
try {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
return findDomRefAtPos(view.state.selection.from, view.domAtPos.bind(view));
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error) {
// eslint-disable-next-line no-console
console.warn(error);
if (dispatchAnalyticsEvent) {
var payload = {
action: ACTION.ERRORED,
actionSubject: ACTION_SUBJECT.CONTENT_COMPONENT,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
component: CONTENT_COMPONENT.FLOATING_TOOLBAR,
selection: view.state.selection.toJSON(),
position: view.state.selection.from,
docSize: view.state.doc.nodeSize,
error: error.toString(),
// @ts-expect-error - Object literal may only specify known properties, 'errorStack' does not exist in type
// This error was introduced after upgrading to TypeScript 5
errorStack: error.stack || undefined
}
};
dispatchAnalyticsEvent(payload);
}
}
};
function filterUndefined(x) {
return !!x;
}
export var floatingToolbarPlugin = function floatingToolbarPlugin(_ref) {
var api = _ref.api;
return {
name: 'floatingToolbar',
pmPlugins: function pmPlugins() {
var floatingToolbarHandlers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var plugins = [{
// Should be after all toolbar plugins
name: 'floatingToolbar',
plugin: function plugin(_ref2) {
var providerFactory = _ref2.providerFactory,
getIntl = _ref2.getIntl;
return floatingToolbarPluginFactory({
api: api,
floatingToolbarHandlers: floatingToolbarHandlers,
providerFactory: providerFactory,
getIntl: getIntl
});
}
}, {
name: 'floatingToolbarData',
plugin: function plugin(_ref3) {
var dispatch = _ref3.dispatch;
return floatingToolbarDataPluginFactory(dispatch);
}
}, {
name: 'forceFocus',
plugin: function plugin() {
return forceFocusPlugin();
}
}];
return plugins;
},
actions: {
forceFocusSelector: forceFocusSelector
},
commands: {
copyNode: function copyNode(nodeType, inputMethod) {
var _api$analytics;
return _copyNode(nodeType, api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions, inputMethod);
}
},
getSharedState: function getSharedState(editorState) {
var _api$interaction, _pluginKey$getState$g, _pluginKey$getState, _pluginKey$getState$g2;
if (!editorState) {
return undefined;
}
var interactionState = api === null || api === void 0 || (_api$interaction = api.interaction) === null || _api$interaction === void 0 || (_api$interaction = _api$interaction.sharedState.currentState()) === null || _api$interaction === void 0 ? void 0 : _api$interaction.interactionState;
var configWithNodeInfo = interactionState !== 'hasNotHadInteraction' ? (_pluginKey$getState$g = (_pluginKey$getState = pluginKey.getState(editorState)) === null || _pluginKey$getState === void 0 || (_pluginKey$getState$g2 = _pluginKey$getState.getConfigWithNodeInfo) === null || _pluginKey$getState$g2 === void 0 ? void 0 : _pluginKey$getState$g2.call(_pluginKey$getState, editorState)) !== null && _pluginKey$getState$g !== void 0 ? _pluginKey$getState$g : undefined : undefined;
return {
configWithNodeInfo: configWithNodeInfo,
floatingToolbarData: dataPluginKey.getState(editorState)
};
},
contentComponent: function contentComponent(_ref4) {
var popupsMountPoint = _ref4.popupsMountPoint,
popupsBoundariesElement = _ref4.popupsBoundariesElement,
popupsScrollableElement = _ref4.popupsScrollableElement,
editorView = _ref4.editorView,
providerFactory = _ref4.providerFactory,
dispatchAnalyticsEvent = _ref4.dispatchAnalyticsEvent;
if (!editorView) {
return null;
}
return /*#__PURE__*/React.createElement(ContentComponent, {
editorView: editorView,
pluginInjectionApi: api,
popupsMountPoint: popupsMountPoint,
popupsBoundariesElement: popupsBoundariesElement,
popupsScrollableElement: popupsScrollableElement,
providerFactory: providerFactory,
dispatchAnalyticsEvent: dispatchAnalyticsEvent
});
}
};
};
/**
* React component that renders the floating toolbar UI for the editor.
*
* This component manages the display of floating toolbars based on the current editor state,
* selection, and configuration. It handles visibility conditions, positioning, toolbar items
* consolidation, and confirmation dialogs.
*
* @param props - Component properties
* @param props.pluginInjectionApi - Plugin injection API for accessing other plugin states
* @param props.editorView - ProseMirror EditorView instance
* @param props.popupsMountPoint - DOM element where popups should be mounted
* @param props.popupsBoundariesElement - Element that defines popup boundaries
* @param props.popupsScrollableElement - Scrollable container element for popups
* @param props.providerFactory - Factory for creating various providers
* @param props.dispatchAnalyticsEvent - Function to dispatch analytics events
* @returns JSX element representing the floating toolbar or null if not visible
*/
export function ContentComponent(_ref5) {
var _configWithNodeInfo$c, _configWithNodeInfo$c2, _items, _pluginInjectionApi$c, _pluginInjectionApi$d, _confirmButtonItem, _confirmButtonItem2, _confirmButtonItem3;
var pluginInjectionApi = _ref5.pluginInjectionApi,
editorView = _ref5.editorView,
popupsMountPoint = _ref5.popupsMountPoint,
popupsBoundariesElement = _ref5.popupsBoundariesElement,
popupsScrollableElement = _ref5.popupsScrollableElement,
providerFactory = _ref5.providerFactory,
dispatchAnalyticsEvent = _ref5.dispatchAnalyticsEvent;
var _useSharedPluginState = useSharedPluginState(pluginInjectionApi, ['floatingToolbar', 'editorDisabled', 'editorViewMode', 'userIntent']),
floatingToolbarState = _useSharedPluginState.floatingToolbarState,
editorDisabledState = _useSharedPluginState.editorDisabledState,
editorViewModeState = _useSharedPluginState.editorViewModeState,
userIntentState = _useSharedPluginState.userIntentState;
var _ref6 = floatingToolbarState !== null && floatingToolbarState !== void 0 ? floatingToolbarState : {},
configWithNodeInfo = _ref6.configWithNodeInfo,
floatingToolbarData = _ref6.floatingToolbarData;
if (isSSR()) {
return null;
}
if (!configWithNodeInfo || !configWithNodeInfo.config || typeof ((_configWithNodeInfo$c = configWithNodeInfo.config) === null || _configWithNodeInfo$c === void 0 ? void 0 : _configWithNodeInfo$c.visible) !== 'undefined' && !((_configWithNodeInfo$c2 = configWithNodeInfo.config) !== null && _configWithNodeInfo$c2 !== void 0 && _configWithNodeInfo$c2.visible)) {
return null;
}
if (userIntentState !== null && userIntentState !== void 0 && userIntentState.currentUserIntent && SUPPRESS_TOOLBAR_USER_INTENTS.includes(userIntentState === null || userIntentState === void 0 ? void 0 : userIntentState.currentUserIntent)) {
return null;
}
var config = configWithNodeInfo.config,
node = configWithNodeInfo.node;
// When the new inline editor-toolbar is enabled, suppress floating toolbar for text selections.
if (Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar)) {
var selection = editorView.state.selection;
var isCellSelection = '$anchorCell' in selection && !selection.empty;
var isTextSelected = selection instanceof TextSelection && !selection.empty;
// don't dismiss table toolbar when a cell selection is caused by clicking the drag handle (which has it's own userIntent)
if (isTextSelected && config.className !== 'hyperlink-floating-toolbar' || isCellSelection && (userIntentState === null || userIntentState === void 0 ? void 0 : userIntentState.currentUserIntent) === 'default') {
return null;
}
}
var items = config.items;
var groupLabel = config.groupLabel;
var title = config.title,
_config$getDomRef = config.getDomRef,
getDomRef = _config$getDomRef === void 0 ? getDomRefFromSelection : _config$getDomRef,
_config$align = config.align,
align = _config$align === void 0 ? 'center' : _config$align,
_config$className = config.className,
className = _config$className === void 0 ? '' : _config$className,
height = config.height,
width = config.width,
zIndex = config.zIndex,
_config$offset = config.offset,
offset = _config$offset === void 0 ? [0, 12] : _config$offset,
forcePlacement = config.forcePlacement,
preventPopupOverflow = config.preventPopupOverflow,
onPositionCalculated = config.onPositionCalculated,
_config$absoluteOffse = config.absoluteOffset,
absoluteOffset = _config$absoluteOffse === void 0 ? {
top: 0,
left: 0,
right: 0,
bottom: 0
} : _config$absoluteOffse,
focusTrap = config.focusTrap,
_config$mediaAssistiv = config.mediaAssistiveMessage,
mediaAssistiveMessage = _config$mediaAssistiv === void 0 ? '' : _config$mediaAssistiv,
_config$stick = config.stick,
stick = _config$stick === void 0 ? true : _config$stick;
var targetRef = getDomRef(editorView, dispatchAnalyticsEvent);
var isEditorDisabled = editorDisabledState && editorDisabledState.editorDisabled;
var isInViewMode = (editorViewModeState === null || editorViewModeState === void 0 ? void 0 : editorViewModeState.mode) === 'view';
if (!targetRef || isEditorDisabled && !isInViewMode) {
return null;
}
// TODO: MODES-3950 - Update this view mode specific logic once we refactor view mode.
// We should inverse the responsibility here: A blacklist for toolbar items in view mode, rather than this white list.
// Also consider moving this logic to the more specific toolbar plugins (media and selection).
var iterableItems = Array.isArray(items) ? items : (_items = items) === null || _items === void 0 ? void 0 : _items(node);
if (isInViewMode) {
// Typescript note: Not all toolbar item types have the `supportsViewMode` prop.
var toolbarItemViewModeProp = 'supportsViewMode';
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
items = iterableItems.filter(function (item) {
return toolbarItemViewModeProp in item && !!item[toolbarItemViewModeProp];
});
}
if (areToolbarFlagsEnabled(Boolean(pluginInjectionApi === null || pluginInjectionApi === void 0 ? void 0 : pluginInjectionApi.toolbar))) {
var _items2;
// Consolidate floating toolbar items
var toolbarItemsArray = Array.isArray(items) ? items : (_items2 = items) === null || _items2 === void 0 ? void 0 : _items2(node);
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
var overflowDropdownItems = toolbarItemsArray.filter(function (item) {
return item.type === 'overflow-dropdown';
});
if (overflowDropdownItems.length > 1) {
var consolidatedOverflowDropdown = consolidateOverflowDropdownItems(overflowDropdownItems);
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- Ignored via go/ees017 (to be fixed)
var otherItems = toolbarItemsArray.filter(function (item) {
return item.type !== 'overflow-dropdown';
});
if (otherItems.length > 0) {
// remove the last separators
while (((_otherItems$at = otherItems.at(-1)) === null || _otherItems$at === void 0 ? void 0 : _otherItems$at.type) === 'separator') {
var _otherItems$at;
otherItems.pop();
}
}
items = [].concat(_toConsumableArray(otherItems), [{
type: 'separator',
fullHeight: true,
supportsViewMode: true
}, consolidatedOverflowDropdown]);
}
// Apply analytics to dropdown
if (overflowDropdownItems.length > 0 && dispatchAnalyticsEvent) {
var _items3;
var currentItems = Array.isArray(items) ? items : (_items3 = items) === null || _items3 === void 0 ? void 0 : _items3(node);
var updatedItems = currentItems.map(function (item) {
if (item.type !== 'overflow-dropdown') {
return item;
}
var originalOnClick = item.onClick;
return _objectSpread(_objectSpread({}, item), {}, {
onClick: function onClick() {
var _pluginInjectionApi$e;
var editorContentMode = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$e = pluginInjectionApi.editorViewMode) === null || _pluginInjectionApi$e === void 0 || (_pluginInjectionApi$e = _pluginInjectionApi$e.sharedState.currentState()) === null || _pluginInjectionApi$e === void 0 ? void 0 : _pluginInjectionApi$e.mode;
dispatchAnalyticsEvent({
action: ACTION.CLICKED,
actionSubject: ACTION_SUBJECT.BUTTON,
actionSubjectId: ACTION_SUBJECT_ID.FLOATING_TOOLBAR_OVERFLOW,
eventType: EVENT_TYPE.UI,
attributes: {
editorContentMode: editorContentMode
}
});
// Call original onClick if it exists
originalOnClick === null || originalOnClick === void 0 || originalOnClick();
}
});
});
items = updatedItems;
}
}
var customPositionCalculation;
var toolbarItems = pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$c = pluginInjectionApi.copyButton) === null || _pluginInjectionApi$c === void 0 ? void 0 : _pluginInjectionApi$c.actions.processCopyButtonItems(editorView.state)(Array.isArray(items) ? items : items(node), pluginInjectionApi === null || pluginInjectionApi === void 0 || (_pluginInjectionApi$d = pluginInjectionApi.decorations) === null || _pluginInjectionApi$d === void 0 ? void 0 : _pluginInjectionApi$d.actions.hoverDecoration);
if (onPositionCalculated) {
customPositionCalculation = function customPositionCalculation(nextPos) {
return onPositionCalculated(editorView, nextPos);
};
}
var dispatchCommand = function dispatchCommand(fn) {
return fn && fn(editorView.state, editorView.dispatch, editorView);
};
// Confirm dialog
var confirmButtonItem;
var _ref7 = floatingToolbarData || {},
confirmDialogForItem = _ref7.confirmDialogForItem,
confirmDialogForItemOption = _ref7.confirmDialogForItemOption;
var matchingItem = confirmDialogForItem ? toolbarItems === null || toolbarItems === void 0 ? void 0 : toolbarItems[confirmDialogForItem] : undefined;
if ((matchingItem === null || matchingItem === void 0 ? void 0 : matchingItem.type) === 'button') {
confirmButtonItem = matchingItem;
}
if ((matchingItem === null || matchingItem === void 0 ? void 0 : matchingItem.type) === 'overflow-dropdown' && confirmDialogForItemOption !== undefined) {
var matchingItemOption = matchingItem.options[confirmDialogForItemOption];
// OverflowDropdownOption is the only member of the union that does not have a 'type' property
if (!('type' in matchingItemOption)) {
confirmButtonItem = matchingItemOption;
}
}
var scrollable = config.scrollable;
var confirmDialogOptions = typeof ((_confirmButtonItem = confirmButtonItem) === null || _confirmButtonItem === void 0 ? void 0 : _confirmButtonItem.confirmDialog) === 'function' ? (_confirmButtonItem2 = confirmButtonItem) === null || _confirmButtonItem2 === void 0 ? void 0 : _confirmButtonItem2.confirmDialog() : (_confirmButtonItem3 = confirmButtonItem) === null || _confirmButtonItem3 === void 0 ? void 0 : _confirmButtonItem3.confirmDialog;
return /*#__PURE__*/React.createElement(ErrorBoundary, {
component: ACTION_SUBJECT.FLOATING_TOOLBAR_PLUGIN,
componentId: camelCase(title),
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
fallbackComponent: null
}, /*#__PURE__*/React.createElement(Popup, {
ariaLabel: title,
role: 'toolbar',
offset: offset,
target: targetRef,
alignY: "bottom",
forcePlacement: forcePlacement,
fitHeight: height,
fitWidth: width,
absoluteOffset: absoluteOffset,
alignX: align,
stick: stick,
zIndex: zIndex,
mountTo: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
onPositionCalculated: customPositionCalculation
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
,
style: scrollable ? {
maxWidth: '100%'
} : {},
focusTrap: focusTrap,
preventOverflow: preventPopupOverflow
}, /*#__PURE__*/React.createElement(WithProviders, {
providerFactory: providerFactory
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
providers: ['extensionProvider']
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
renderNode: function renderNode(providers) {
return /*#__PURE__*/React.createElement(Toolbar, {
target: targetRef
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
,
items: toolbarItems,
groupLabel: groupLabel,
node: node,
dispatchCommand: dispatchCommand,
editorView: editorView
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: className
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
focusEditor: function focusEditor() {
return editorView.focus();
},
providerFactory: providerFactory,
popupsMountPoint: popupsMountPoint,
popupsBoundariesElement: popupsBoundariesElement,
popupsScrollableElement: popupsScrollableElement,
dispatchAnalyticsEvent: dispatchAnalyticsEvent,
extensionsProvider: providers.extensionProvider,
scrollable: scrollable,
api: pluginInjectionApi,
mediaAssistiveMessage: mediaAssistiveMessage
});
}
})), /*#__PURE__*/React.createElement(ConfirmationModal, {
testId: "ak-floating-toolbar-confirmation-modal",
options: confirmDialogOptions
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onConfirm: function onConfirm() {
var isChecked = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (!!confirmDialogOptions.onConfirm) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dispatchCommand(confirmDialogOptions.onConfirm(isChecked));
} else {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
dispatchCommand(confirmButtonItem.onClick);
}
}
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
onClose: function onClose() {
dispatchCommand(hideConfirmDialog());
// Need to set focus to Editor here,
// As when the Confirmation dialog pop up, and user interacts with the dialog, Editor loses focus.
// So when Confirmation dialog is closed, Editor does not have the focus, then cursor goes to the position 1 of the doc,
// instead of the cursor position before the dialog pop up.
if (!editorView.hasFocus()) {
editorView.focus();
}
}
}));
}
/**
*
* ProseMirror Plugin
*
*/
// We throttle update of this plugin with RAF.
// So from other plugins you will always get the previous state.
export var pluginKey = new PluginKey('floatingToolbarPluginKey');
/**
* Clean up floating toolbar configs from undesired properties.
*/
function sanitizeFloatingToolbarConfig(config) {
// Cleanup from non existing node types
if (Array.isArray(config.nodeType)) {
return _objectSpread(_objectSpread({}, config), {}, {
nodeType: config.nodeType.filter(filterUndefined)
});
}
return config;
}
/**
* Creates a floating toolbar plugin for the ProseMirror editor.
*
* This factory function creates a SafePlugin that manages floating toolbars in the editor.
* It processes an array of floating toolbar handlers and determines which toolbar configuration
* should be active based on the current editor state and selection.
*
* @param options - Configuration object for the floating toolbar plugin
* @param options.floatingToolbarHandlers - Array of handlers that return toolbar configurations
* @param options.getIntl - Function that returns the IntlShape instance for internationalization
* @param options.providerFactory - Factory for creating various providers used by the editor
* @returns A SafePlugin instance that manages floating toolbar state and behavior
*/
export function floatingToolbarPluginFactory(options) {
var floatingToolbarHandlers = options.floatingToolbarHandlers,
providerFactory = options.providerFactory,
getIntl = options.getIntl,
api = options.api;
var intl = getIntl();
var getConfigWithNodeInfo = function getConfigWithNodeInfo(editorState) {
var activeConfigs = [];
for (var index = 0; index < floatingToolbarHandlers.length; index++) {
var handler = floatingToolbarHandlers[index];
var config = handler(editorState, intl, providerFactory, activeConfigs);
if (config) {
var _api$userIntent;
if (SUPPRESS_TOOLBAR_USER_INTENTS.includes((api === null || api === void 0 || (_api$userIntent = api.userIntent) === null || _api$userIntent === void 0 || (_api$userIntent = _api$userIntent.sharedState.currentState()) === null || _api$userIntent === void 0 ? void 0 : _api$userIntent.currentUserIntent) || '')) {
activeConfigs = undefined;
break;
}
activeConfigs.push(sanitizeFloatingToolbarConfig(config));
}
}
var relevantConfig = activeConfigs && getRelevantConfig(editorState.selection, activeConfigs);
return relevantConfig;
};
var getIsToolbarSuppressed = function getIsToolbarSuppressed() {
return false;
};
var apply = function apply() {
var newPluginState = {
getConfigWithNodeInfo: getConfigWithNodeInfo
};
return newPluginState;
};
return new SafePlugin({
key: pluginKey,
state: {
init: function init() {
return {
getConfigWithNodeInfo: getConfigWithNodeInfo
};
},
apply: expValEquals('platform_editor_lovability_suppress_toolbar_event', 'isEnabled', true) ? function (_tr, _pluginState, __oldEditorState) {
var suppressedToolbar = getIsToolbarSuppressed();
var newPluginState = {
getConfigWithNodeInfo: getConfigWithNodeInfo,
suppressedToolbar: suppressedToolbar
};
return newPluginState;
} : apply
},
view: expValEquals('platform_editor_lovability_suppress_toolbar_event', 'isEnabled', true) ? function () {
return {
update: function update(view, prevState) {
var pluginState = pluginKey.getState(view.state);
var prevPluginState = pluginKey.getState(prevState);
if (pluginState !== null && pluginState !== void 0 && pluginState.suppressedToolbar && !(prevPluginState !== null && prevPluginState !== void 0 && prevPluginState.suppressedToolbar)) {
var _api$analytics2;
api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({
action: ACTION.SUPPRESSED,
actionSubject: ACTION_SUBJECT.FLOATING_TOOLBAR_PLUGIN,
actionSubjectId: ACTION_SUBJECT_ID.FLOATING_TOOLBAR,
eventType: EVENT_TYPE.TRACK
});
}
}
};
} : undefined
});
}