UNPKG

@atlaskit/editor-plugin-panel

Version:

Panel plugin for @atlaskit/editor-core.

341 lines (339 loc) 13.8 kB
import React from 'react'; import { PanelType } from '@atlaskit/adf-schema'; import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE, INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { withAnalytics } from '@atlaskit/editor-common/editor-analytics'; import commonMessages, { panelMessages as messages } from '@atlaskit/editor-common/messages'; import { getPanelTypeBackgroundNoTokens } from '@atlaskit/editor-common/panel'; import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check'; import { DEFAULT_BORDER_COLOR, panelBackgroundPalette } from '@atlaskit/editor-common/ui-color'; import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils'; import { akEditorSelectedNodeClassName } from '@atlaskit/editor-shared-styles'; import CopyIcon from '@atlaskit/icon/core/copy'; import CrossCircleIcon from '@atlaskit/icon/core/cross-circle'; import DeleteIcon from '@atlaskit/icon/core/delete'; import EmojiRemoveIcon from '@atlaskit/icon/core/emoji-remove'; import StatusDiscoveryIcon from '@atlaskit/icon/core/status-discovery'; import InformationIcon from '@atlaskit/icon/core/status-information'; import SuccessIcon from '@atlaskit/icon/core/status-success'; import WarningIcon from '@atlaskit/icon/core/status-warning'; import { changePanelType, removePanel } from '../editor-actions/actions'; import { findPanel } from '../pm-plugins/utils/utils'; import { panelTypeDropdown } from './panelTypeDropdown'; export const panelIconMap = { [PanelType.INFO]: { shortName: ':info:', id: 'atlassian-info' }, [PanelType.NOTE]: { shortName: ':note:', id: 'atlassian-note' }, [PanelType.WARNING]: { shortName: ':warning:', id: 'atlassian-warning' }, [PanelType.ERROR]: { shortName: ':cross_mark:', id: 'atlassian-cross_mark' }, [PanelType.SUCCESS]: { shortName: ':check_mark:', id: 'atlassian-check_mark' }, [PanelType.TIP]: { shortName: ':tip:', id: 'atlassian-tip' } }; export const getToolbarItems = (formatMessage, panelNodeType, isCustomPanelEnabled, isCustomPanelEditable, providerFactory, hoverDecoration, editorAnalyticsAPI, activePanelType, activePanelColor, activePanelIcon, state, api) => { const areAllNewToolbarFlagsDisabled = !areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar)); // TODO: ED-14403 - investigate why these titles are not getting translated for the tooltips const items = areAllNewToolbarFlagsDisabled ? [{ id: 'editor.panel.info', type: 'button', icon: InformationIcon, onClick: changePanelType(editorAnalyticsAPI)(PanelType.INFO), selected: activePanelType === PanelType.INFO, title: formatMessage(messages.info), tabIndex: null }, { id: 'editor.panel.note', type: 'button', icon: StatusDiscoveryIcon, onClick: changePanelType(editorAnalyticsAPI)(PanelType.NOTE), selected: activePanelType === PanelType.NOTE, title: formatMessage(messages.note), tabIndex: null }, { id: 'editor.panel.success', type: 'button', icon: SuccessIcon, onClick: changePanelType(editorAnalyticsAPI)(PanelType.SUCCESS), selected: activePanelType === PanelType.SUCCESS, title: formatMessage(messages.success), tabIndex: null }, { id: 'editor.panel.warning', type: 'button', icon: WarningIcon, onClick: changePanelType(editorAnalyticsAPI)(PanelType.WARNING), selected: activePanelType === PanelType.WARNING, title: formatMessage(messages.warning), tabIndex: null }, { id: 'editor.panel.error', type: 'button', icon: CrossCircleIcon, onClick: changePanelType(editorAnalyticsAPI)(PanelType.ERROR), selected: activePanelType === PanelType.ERROR, title: formatMessage(messages.error), tabIndex: null }] : [panelTypeDropdown({ activePanelType, editorAnalyticsAPI, formatMessage }), ...(Boolean(api === null || api === void 0 ? void 0 : api.toolbar) ? [] : [{ type: 'separator' }])]; if (isCustomPanelEnabled) { const changeColor = color => (state, dispatch) => { const panelNode = findPanel(state); if (panelNode === undefined) { return false; } const previousColor = panelNode.node.attrs.panelType === 'custom' ? panelNode.node.attrs.panelColor || 'none' : getPanelTypeBackgroundNoTokens(panelNode.node.attrs.panelType); const emojiInfo = panelNode.node.attrs.panelType; const panelEmoji = panelIconMap[emojiInfo]; const previousEmoji = panelEmoji ? { emoji: panelEmoji.shortName, emojiId: panelEmoji.id } : {}; if (previousColor === color) { changePanelType(editorAnalyticsAPI)(PanelType.CUSTOM, { color, ...previousEmoji }, isCustomPanelEnabled)(state, dispatch); return false; } const payload = { action: ACTION.CHANGED_BACKGROUND_COLOR, actionSubject: ACTION_SUBJECT.PANEL, actionSubjectId: ACTION_SUBJECT_ID.PANEL, attributes: { newColor: color, previousColor: previousColor }, eventType: EVENT_TYPE.TRACK }; withAnalytics(editorAnalyticsAPI, payload)(changePanelType(editorAnalyticsAPI)(PanelType.CUSTOM, { color, ...previousEmoji }, isCustomPanelEnabled))(state, dispatch); return false; }; const changeEmoji = emoji => (state, dispatch) => { const panelNode = findPanel(state); if (panelNode === undefined) { return false; } const previousIcon = panelNode.node.attrs.panelIcon || ''; if (previousIcon === emoji.shortName) { changePanelType(editorAnalyticsAPI)(PanelType.CUSTOM, { emoji: emoji.shortName, emojiId: emoji.id, emojiText: emoji.fallback }, true)(state, dispatch); return false; } const payload = { action: ACTION.CHANGED_ICON, actionSubject: ACTION_SUBJECT.PANEL, actionSubjectId: ACTION_SUBJECT_ID.PANEL, attributes: { newIcon: emoji.shortName, previousIcon: previousIcon }, eventType: EVENT_TYPE.TRACK }; withAnalytics(editorAnalyticsAPI, payload)(changePanelType(editorAnalyticsAPI)(PanelType.CUSTOM, { emoji: emoji.shortName, emojiId: emoji.id, emojiText: emoji.fallback }, true))(state, dispatch); return false; }; const removeEmoji = () => (state, dispatch) => { const panelNode = findPanel(state); if (activePanelType === PanelType.CUSTOM && !activePanelIcon) { return false; } if (panelNode === undefined) { return false; } const payload = { action: ACTION.REMOVE_ICON, actionSubject: ACTION_SUBJECT.PANEL, actionSubjectId: ACTION_SUBJECT_ID.PANEL, attributes: { icon: panelNode.node.attrs.panelIcon }, eventType: EVENT_TYPE.TRACK }; withAnalytics(editorAnalyticsAPI, payload)(changePanelType(editorAnalyticsAPI)(PanelType.CUSTOM, { emoji: undefined, emojiId: undefined, emojiText: undefined }, isCustomPanelEnabled))(state, dispatch); return false; }; const panelColor = activePanelType === PanelType.CUSTOM ? activePanelColor || getPanelTypeBackgroundNoTokens(PanelType.INFO) : getPanelTypeBackgroundNoTokens(activePanelType); const defaultPalette = panelBackgroundPalette.find(item => item.value === panelColor) || { // eslint-disable-next-line @atlassian/i18n/no-literal-string-in-object label: 'Custom', value: panelColor, border: DEFAULT_BORDER_COLOR }; if (isCustomPanelEditable) { const colorPicker = { id: 'editor.panel.colorPicker', title: formatMessage(messages.backgroundColor), isAriaExpanded: true, type: 'select', selectType: 'color', defaultValue: defaultPalette, options: panelBackgroundPalette, onChange: option => changeColor(option.value) }; const emojiPicker = { id: 'editor.panel.emojiPicker', title: formatMessage(messages.emoji), type: 'select', selectType: 'emoji', options: [], selected: activePanelType === PanelType.CUSTOM && !!activePanelIcon, onChange: emoji => changeEmoji(emoji) }; const removeEmojiButton = { id: 'editor.panel.removeEmoji', type: 'button', icon: () => /*#__PURE__*/React.createElement(EmojiRemoveIcon, { spacing: 'spacious', label: '' }), onClick: removeEmoji(), title: formatMessage(commonMessages.removeEmoji), disabled: activePanelIcon ? false : true }; items.push(emojiPicker, removeEmojiButton, { type: 'separator' }, colorPicker); } } if (areAllNewToolbarFlagsDisabled) { if (state) { items.push({ type: 'separator' }); items.push({ type: 'copy-button', items: [{ state, formatMessage, nodeType: panelNodeType }] }); } items.push({ type: 'separator' }, { id: 'editor.panel.delete', type: 'button', appearance: 'danger', focusEditoronEnter: true, icon: () => /*#__PURE__*/React.createElement(DeleteIcon, { spacing: 'spacious', label: '' }), onClick: removePanel(editorAnalyticsAPI), onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(panelNodeType, true), onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(panelNodeType, false), onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(panelNodeType, true), onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(panelNodeType, false), title: formatMessage(commonMessages.remove), tabIndex: null }); } else { const hoverDecorationProps = (nodeType, className) => ({ onMouseEnter: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true, className), onMouseLeave: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false, className), onFocus: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, true, className), onBlur: hoverDecoration === null || hoverDecoration === void 0 ? void 0 : hoverDecoration(nodeType, false, className) }); // testId is required to show focus on trigger button on ESC key press // see hideOnEsc in platform/packages/editor/editor-plugin-floating-toolbar/src/ui/Dropdown.tsx const testId = 'panel-overflow-dropdown-trigger'; const overflowMenuConfig = [{ type: 'separator', fullHeight: true }, { type: 'overflow-dropdown', testId, options: [{ title: formatMessage(commonMessages.copyToClipboard), onClick: () => { var _api$core, _api$floatingToolbar; api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute( // @ts-ignore api === null || api === void 0 ? void 0 : (_api$floatingToolbar = api.floatingToolbar) === null || _api$floatingToolbar === void 0 ? void 0 : _api$floatingToolbar.commands.copyNode(panelNodeType, INPUT_METHOD.FLOATING_TB)); return true; }, icon: /*#__PURE__*/React.createElement(CopyIcon, { label: "" }), ...hoverDecorationProps(panelNodeType, akEditorSelectedNodeClassName) }, { title: formatMessage(commonMessages.delete), onClick: removePanel(editorAnalyticsAPI), icon: /*#__PURE__*/React.createElement(DeleteIcon, { label: "" }), ...hoverDecorationProps(panelNodeType) }] }]; items.push(...overflowMenuConfig); } return items; }; export const getToolbarConfig = (state, intl, options = {}, providerFactory, api) => { const { formatMessage } = intl; const panelObject = findPanel(state); if (panelObject) { var _api$decorations, _api$analytics; const nodeType = state.schema.nodes.panel; const { panelType, panelColor, panelIcon } = panelObject.node.attrs; const isStandardPanel = panelType => { return panelType !== PanelType.CUSTOM ? panelType : undefined; }; // force toolbar to be turned on const items = getToolbarItems(formatMessage, nodeType, options.allowCustomPanel || false, options.allowCustomPanel && options.allowCustomPanelEdit || false, providerFactory, api === null || api === void 0 ? void 0 : (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions.hoverDecoration, api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions, panelType, options.allowCustomPanel ? panelColor : undefined, options.allowCustomPanel ? panelIcon || isStandardPanel(panelType) : undefined, state, areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar)) ? api : undefined); const getDomRef = editorView => { const domAtPos = editorView.domAtPos.bind(editorView); const element = findDomRefAtPos(panelObject.pos, domAtPos); return element; }; return { title: 'Panel floating controls', getDomRef, nodeType, items, scrollable: true, groupLabel: formatMessage(messages.panelsGroup) }; } return; };