@atlaskit/editor-plugin-floating-toolbar
Version:
Floating toolbar plugin for @atlaskit/editor-core
178 lines (175 loc) • 6.44 kB
JavaScript
import React, { useEffect, useState } from 'react';
import Loadable from 'react-loadable';
import ButtonGroup from '@atlaskit/button/button-group';
import { getContextualToolbarItemsFromModule } from '@atlaskit/editor-common/extensions';
import { isNestedTablesSupported, isSelectionTableNestedInTable } from '@atlaskit/editor-common/nesting';
import { FloatingToolbarButton as Button, FloatingToolbarSeparator as Separator } from '@atlaskit/editor-common/ui';
import { nodeToJSON } from '@atlaskit/editor-common/utils';
import Dropdown from './Dropdown';
const noop = () => null;
const isDefaultExport = mod => {
return mod.hasOwnProperty('default');
};
const resolveExtensionIcon = async getIcon => {
if (!getIcon) {
return noop;
}
const maybeIcon = await getIcon();
return isDefaultExport(maybeIcon) ? maybeIcon.default : maybeIcon;
};
const ExtensionButton = props => {
const {
item,
node,
extensionApi,
areAnyNewToolbarFlagsEnabled
} = props;
const ButtonIcon = React.useMemo(() => item.icon ? Loadable({
// Ignored via go/ees005
// eslint-disable-next-line require-await
loader: async () => resolveExtensionIcon(item.icon),
loading: noop
}) : undefined, [item.icon]);
const onClick = () => {
if (typeof item.action !== 'function') {
throw new Error(`'action' of context toolbar item '${item.key}' is not a function`);
}
const targetNodeAdf = nodeToJSON(node);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
item.action(targetNodeAdf, extensionApi);
};
const getAriaLabel = () => {
if (item.ariaLabel) {
return item.ariaLabel;
}
if (typeof item.tooltip === 'string') {
return item.tooltip;
}
if (item.label) {
return item.label;
}
return '';
};
return /*#__PURE__*/React.createElement(Button, {
title: item.label,
ariaLabel: getAriaLabel(),
icon: ButtonIcon ? /*#__PURE__*/React.createElement(ButtonIcon, {
label: item.label || ''
}) : undefined,
onClick: onClick,
tooltipContent: item.tooltip,
tooltipStyle: item.tooltipStyle,
disabled: item.disabled,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}, item.label);
};
export const ExtensionsPlaceholder = props => {
const {
node,
editorView,
extensionProvider,
separator,
applyChangeToContextPanel,
extensionApi,
scrollable,
setDisableScroll,
dispatchCommand,
popupsMountPoint,
popupsBoundariesElement,
popupsScrollableElement,
alignDropdownWithToolbar,
areAnyNewToolbarFlagsEnabled
} = props;
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const [extensions, setExtensions] = useState([]);
useEffect(() => {
getExtensions();
async function getExtensions() {
const provider = await extensionProvider;
if (provider) {
setExtensions(await provider.getExtensions());
}
}
// leaving dependencies array empty so that this effect runs just once on component mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const nodeAdf = React.useMemo(() => nodeToJSON(node), [node]);
const extensionItems = React.useMemo(() => {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return getContextualToolbarItemsFromModule(extensions, nodeAdf, extensionApi);
}, [extensions, nodeAdf, extensionApi]);
if (!extensionItems.length) {
return null;
}
// ButtonGroup wraps each child with another layer
// but count fragment as 1 child, so here we create the children manually.
const children = [];
if (separator && ['start', 'both'].includes(separator)) {
children.push( /*#__PURE__*/React.createElement(Separator, {
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
}
const isNestedTable = isNestedTablesSupported(editorView.state.schema) && isSelectionTableNestedInTable(editorView.state);
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extensionItems.forEach((item, index) => {
// disable the referentiality and charts extensions for nested tables
if (isNestedTable && ['referentiality:connections', 'chart:insert-chart'].includes(item.key)) {
item.disabled = true;
}
if ('type' in item && item.type === 'dropdown') {
children.push( /*#__PURE__*/React.createElement(Dropdown, {
key: item.id,
title: item.title,
icon: item.icon,
dispatchCommand: dispatchCommand || (() => {}),
options: item.options,
disabled: item.disabled,
tooltip: item.tooltip,
hideExpandIcon: item.hideExpandIcon,
mountPoint: popupsMountPoint,
boundariesElement: popupsBoundariesElement,
scrollableElement: popupsScrollableElement,
dropdownWidth: item.dropdownWidth,
showSelected: item.showSelected,
buttonTestId: item.testId,
editorView: editorView,
setDisableParentScroll: scrollable ? setDisableScroll : undefined,
dropdownListId: (item === null || item === void 0 ? void 0 : item.id) && `${item.id}-dropdownList`,
alignDropdownWithToolbar: alignDropdownWithToolbar,
onToggle: item.onToggle,
footer: item.footer,
onMount: item.onMount,
onClick: item.onClick,
pulse: item.pulse,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
} else {
children.push( /*#__PURE__*/React.createElement(ExtensionButton, {
node: node,
item: item,
editorView: editorView,
applyChangeToContextPanel: applyChangeToContextPanel,
extensionApi: extensionApi,
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
}
if (index < extensionItems.length - 1) {
children.push( /*#__PURE__*/React.createElement(Separator, {
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
}
});
if (separator && ['end', 'both'].includes(separator)) {
children.push( /*#__PURE__*/React.createElement(Separator, {
areAnyNewToolbarFlagsEnabled: areAnyNewToolbarFlagsEnabled
}));
}
// eslint-disable-next-line react/no-children-prop
return /*#__PURE__*/React.createElement(ButtonGroup, {
children: children
});
};