UNPKG

@atlaskit/editor-plugin-extension

Version:

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

236 lines (233 loc) 10.5 kB
import { messages } from '@atlaskit/editor-common/extensions'; import commonMessages from '@atlaskit/editor-common/messages'; import { BODIED_EXT_MBE_MARGIN_TOP } from '@atlaskit/editor-common/styles'; import { getChildrenInfo, getNodeName, isReferencedSource } from '@atlaskit/editor-common/utils'; import { findParentNodeOfType, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import ContentWidthNarrowIcon from '@atlaskit/icon/core/content-width-narrow'; import ContentWidthWideIcon from '@atlaskit/icon/core/content-width-wide'; import DeleteIcon from '@atlaskit/icon/core/delete'; import EditIcon from '@atlaskit/icon/core/edit'; import ExpandHorizontalIcon from '@atlaskit/icon/core/expand-horizontal'; import LegacyEditIcon from '@atlaskit/icon/glyph/editor/edit'; import CenterIcon from '@atlaskit/icon/glyph/editor/media-center'; import FullWidthIcon from '@atlaskit/icon/glyph/editor/media-full-width'; import WideIcon from '@atlaskit/icon/glyph/editor/media-wide'; import RemoveIcon from '@atlaskit/icon/glyph/editor/remove'; import { fg } from '@atlaskit/platform-feature-flags'; import { editExtension } from '../editor-actions/actions'; import { removeDescendantNodes, removeExtension, updateExtensionLayout } from '../editor-commands/commands'; import { pluginKey as macroPluginKey } from './macro/plugin-key'; import { getPluginState } from './plugin-factory'; import { getSelectedExtension } from './utils'; const isLayoutSupported = (state, selectedExtNode) => { const { schema: { nodes: { bodiedExtension, extension, layoutSection, table, expand, multiBodiedExtension } }, selection } = state; if (!selectedExtNode) { return false; } const isMultiBodiedExtension = selectedExtNode.node.type === multiBodiedExtension; const isNonEmbeddedBodiedExtension = selectedExtNode.node.type === bodiedExtension && !hasParentNodeOfType([multiBodiedExtension].filter(Boolean))(selection); const isNonEmbeddedExtension = selectedExtNode.node.type === extension && !hasParentNodeOfType([bodiedExtension, table, expand, multiBodiedExtension].filter(Boolean))(selection); // if selection belongs to layout supported extension category // and not inside a layoutSection return !!((isMultiBodiedExtension || isNonEmbeddedBodiedExtension || isNonEmbeddedExtension) && !hasParentNodeOfType([layoutSection])(selection)); }; const breakoutOptions = (state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params ) => { const nodeWithPos = getSelectedExtension(state, true); // we should only return breakout options when breakouts are enabled and the node supports them if (nodeWithPos && breakoutEnabled && isLayoutSupported(state, nodeWithPos)) { const { layout } = nodeWithPos.node.attrs; return [{ type: 'button', icon: ContentWidthNarrowIcon, iconFallback: CenterIcon, onClick: updateExtensionLayout('default', editorAnalyticsAPI), selected: layout === 'default', title: formatMessage(commonMessages.layoutFixedWidth), tabIndex: null }, { type: 'button', icon: ContentWidthWideIcon, iconFallback: WideIcon, onClick: updateExtensionLayout('wide', editorAnalyticsAPI), selected: layout === 'wide', title: formatMessage(commonMessages.layoutWide), tabIndex: null }, { type: 'button', icon: ExpandHorizontalIcon, iconFallback: FullWidthIcon, onClick: updateExtensionLayout('full-width', editorAnalyticsAPI), selected: layout === 'full-width', title: formatMessage(commonMessages.layoutFullWidth), tabIndex: null }]; } return []; }; const editButton = (formatMessage, extensionState, applyChangeToContextPanel, editorAnalyticsAPI // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/max-params ) => { if (!extensionState.showEditButton) { return []; } return [{ id: 'editor.extension.edit', type: 'button', icon: EditIcon, iconFallback: LegacyEditIcon, testId: 'extension-toolbar-edit-button', // Taking the latest `updateExtension` from plugin state to avoid race condition @see ED-8501 onClick: (state, dispatch, view) => { const macroState = macroPluginKey.getState(state); const { updateExtension } = getPluginState(state); editExtension(macroState && macroState.macroProvider, applyChangeToContextPanel, editorAnalyticsAPI, updateExtension)(state, dispatch, view); return true; }, title: formatMessage(messages.edit), tabIndex: null, focusEditoronEnter: true }]; }; export const getToolbarConfig = ({ breakoutEnabled = true, hoverDecoration, applyChangeToContextPanel, editorAnalyticsAPI }) => (state, intl) => { const { formatMessage } = intl; const extensionState = getPluginState(state); if (extensionState && !extensionState.showContextPanel && extensionState.element) { const nodeType = [state.schema.nodes.extension, state.schema.nodes.inlineExtension, state.schema.nodes.bodiedExtension, state.schema.nodes.multiBodiedExtension]; const editButtonArray = editButton(formatMessage, extensionState, applyChangeToContextPanel, editorAnalyticsAPI); const breakoutButtonArray = breakoutOptions(state, formatMessage, extensionState, breakoutEnabled, editorAnalyticsAPI); const extensionObj = getSelectedExtension(state, true); // Check if we need to show confirm dialog for delete button let confirmDialog; if (isReferencedSource(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node)) { confirmDialog = () => { const localSourceName = formatMessage(messages.unnamedSource); return { title: formatMessage(messages.deleteElementTitle), okButtonLabel: formatMessage(messages.confirmDeleteLinkedModalOKButton), message: formatMessage(messages.confirmDeleteLinkedModalMessage, { nodeName: getNodeName(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node) || localSourceName }), isReferentialityDialog: true, getChildrenInfo: () => getChildrenInfo(state, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node), checkboxLabel: formatMessage(messages.confirmModalCheckboxLabel), onConfirm: (isChecked = false) => clickWithCheckboxHandler(isChecked, extensionObj === null || extensionObj === void 0 ? void 0 : extensionObj.node) }; }; } return { title: 'Extension floating controls', // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion getDomRef: () => extensionState.element.parentElement || undefined, nodeType, onPositionCalculated: (editorView, nextPos) => { const { state: { schema, selection } } = editorView; const extensionNode = getSelectedExtension(state, true); const possibleMbeParent = findParentNodeOfType(schema.nodes.extensionFrame)(selection); // We only want to use calculated position in case of a bodiedExtension present inside an MBE node const isBodiedExtensionInsideMBE = possibleMbeParent && (extensionNode === null || extensionNode === void 0 ? void 0 : extensionNode.node.type.name) === 'bodiedExtension'; if (fg('platform_editor_legacy_content_macro')) { if (!extensionNode) { return nextPos; } const isInsideEditableExtensionArea = !!editorView.dom.closest('.extension-editable-area'); if (!isBodiedExtensionInsideMBE && !isInsideEditableExtensionArea) { return nextPos; } } else { if (!isBodiedExtensionInsideMBE) { return nextPos; } } const scrollWrapper = editorView.dom.closest('.fabric-editor-popup-scroll-parent') || document.body; // Ignored via go/ees005 // eslint-disable-next-line @atlaskit/editor/no-as-casting const nestedBodiedExtensionDomElement = editorView.nodeDOM(extensionNode.pos); const nestedBodiedExtensionRect = nestedBodiedExtensionDomElement === null || nestedBodiedExtensionDomElement === void 0 ? void 0 : nestedBodiedExtensionDomElement.getBoundingClientRect(); const wrapperBounds = scrollWrapper.getBoundingClientRect(); const toolbarTopPos = nestedBodiedExtensionRect.bottom - wrapperBounds.top + scrollWrapper.scrollTop + BODIED_EXT_MBE_MARGIN_TOP; return { top: toolbarTopPos, left: nextPos.left }; }, items: [...editButtonArray, ...breakoutButtonArray, { type: 'separator', hidden: editButtonArray.length === 0 && breakoutButtonArray.length === 0 }, { type: 'extensions-placeholder', separator: 'end' }, { type: 'copy-button', supportsViewMode: !fg('platform_editor_remove_copy_button_from_view_mode'), items: [{ state, formatMessage: intl.formatMessage, nodeType }] }, { type: 'separator' }, { id: 'editor.extension.delete', type: 'button', icon: DeleteIcon, iconFallback: RemoveIcon, appearance: 'danger', onClick: removeExtension(editorAnalyticsAPI), onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true), onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false), onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true), onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false), focusEditoronEnter: true, title: formatMessage(commonMessages.remove), tabIndex: null, confirmDialog }], scrollable: true }; } return; }; const clickWithCheckboxHandler = (isChecked, node) => (state, dispatch) => { if (!node) { return false; } if (!isChecked) { removeExtension()(state, dispatch); } else { removeDescendantNodes(node)(state, dispatch); } return true; };