@atlaskit/editor-plugin-panel
Version:
Panel plugin for @atlaskit/editor-core.
341 lines (339 loc) • 13.8 kB
JavaScript
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;
};