@atlaskit/editor-plugin-layout
Version:
Layout plugin for @atlaskit/editor-core
298 lines (296 loc) • 12.6 kB
JavaScript
import React from 'react';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import commonMessages, { layoutMessages, layoutMessages as toolbarMessages } from '@atlaskit/editor-common/messages';
import { areToolbarFlagsEnabled } from '@atlaskit/editor-common/toolbar-flag-check';
import { findDomRefAtPos } from '@atlaskit/editor-prosemirror/utils';
import { akEditorSelectedNodeClassName } from '@atlaskit/editor-shared-styles';
import CopyIcon from '@atlaskit/icon/core/copy';
import DeleteIcon from '@atlaskit/icon/core/delete';
import LayoutOneColumnIcon from '@atlaskit/icon/core/layout-one-column';
import LayoutThreeColumnsIcon from '@atlaskit/icon/core/layout-three-columns';
import LayoutThreeColumnsSidebarsIcon from '@atlaskit/icon/core/layout-three-columns-sidebars';
import LayoutTwoColumnsIcon from '@atlaskit/icon/core/layout-two-columns';
import LayoutTwoColumnsSidebarLeftIcon from '@atlaskit/icon/core/layout-two-columns-sidebar-left';
import LayoutTwoColumnsSidebarRightIcon from '@atlaskit/icon/core/layout-two-columns-sidebar-right';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import { deleteActiveLayoutNode, getPresetLayout, setPresetLayout } from '../pm-plugins/actions';
import { EditorLayoutFiveColumnsIcon, EditorLayoutFourColumnsIcon } from './icons/LayoutColumnsIcon';
import { LayoutThreeWithLeftSidebarsIcon } from './icons/LayoutThreeWithLeftSidebars';
import { LayoutThreeWithRightSidebarsIcon } from './icons/LayoutThreeWithRightSidebars';
const LAYOUT_TYPES = [{
id: 'editor.layout.twoEquals',
type: 'two_equal',
title: toolbarMessages.twoColumns,
icon: LayoutTwoColumnsIcon
}, {
id: 'editor.layout.threeEquals',
type: 'three_equal',
title: toolbarMessages.threeColumns,
icon: LayoutThreeColumnsIcon
}];
const LAYOUT_TYPES_WITH_SINGLE_COL = [{
id: 'editor.layout.singeLayout',
type: 'single',
title: toolbarMessages.singleColumn,
icon: LayoutOneColumnIcon
}, ...LAYOUT_TYPES];
const SIDEBAR_LAYOUT_TYPES = [{
id: 'editor.layout.twoRightSidebar',
type: 'two_right_sidebar',
title: toolbarMessages.rightSidebar,
icon: LayoutTwoColumnsSidebarRightIcon
}, {
id: 'editor.layout.twoLeftSidebar',
type: 'two_left_sidebar',
title: toolbarMessages.leftSidebar,
icon: LayoutTwoColumnsSidebarLeftIcon
}, {
id: 'editor.layout.threeWithSidebars',
type: 'three_with_sidebars',
title: toolbarMessages.threeColumnsWithSidebars,
icon: LayoutThreeColumnsSidebarsIcon
}];
// These are used for advanced layout options
const LAYOUT_WITH_TWO_COL_DISTRIBUTION = [{
id: 'editor.layout.twoEquals',
type: 'two_equal',
title: toolbarMessages.twoColumns,
icon: LayoutTwoColumnsIcon
}, {
id: 'editor.layout.twoRightSidebar',
type: 'two_right_sidebar',
title: toolbarMessages.rightSidebar,
icon: LayoutTwoColumnsSidebarRightIcon
}, {
id: 'editor.layout.twoLeftSidebar',
type: 'two_left_sidebar',
title: toolbarMessages.leftSidebar,
icon: LayoutTwoColumnsSidebarLeftIcon
}];
const LAYOUT_WITH_THREE_COL_DISTRIBUTION = [{
id: 'editor.layout.threeEquals',
type: 'three_equal',
title: toolbarMessages.threeColumns,
icon: LayoutThreeColumnsIcon
}, {
id: 'editor.layout.threeWithSidebars',
type: 'three_with_sidebars',
title: toolbarMessages.threeColumnsWithSidebars,
icon: LayoutThreeColumnsSidebarsIcon
}, {
id: 'editor.layout.threeRightSidebars',
type: 'three_right_sidebars',
title: toolbarMessages.threeColumnsWithRightSidebars,
icon: LayoutThreeWithRightSidebarsIcon,
iconFallback: LayoutThreeWithRightSidebarsIcon
}, {
id: 'editor.layout.threeLeftSidebars',
type: 'three_left_sidebars',
title: toolbarMessages.threeColumnsWithLeftSidebars,
icon: LayoutThreeWithLeftSidebarsIcon,
iconFallback: LayoutThreeWithLeftSidebarsIcon
}];
const buildLayoutButton = (intl, item, currentLayout, editorAnalyticsAPI) => ({
id: item.id,
type: 'button',
icon: item.icon,
iconFallback: item.iconFallback,
testId: item.title.id ? `${item.title.id}` : undefined,
title: intl.formatMessage(item.title),
onClick: setPresetLayout(editorAnalyticsAPI)(item.type),
selected: !!currentLayout && currentLayout === item.type,
tabIndex: null
});
export const layoutToolbarTitle = 'Layout floating controls';
const iconPlaceholder = LayoutTwoColumnsIcon; // TODO: ED-25466 - Replace with proper icon
const getLayoutColumnsIcons = colCount => {
if (!editorExperiment('single_column_layouts', true) && !editorExperiment('platform_editor_controls', 'variant1')) {
return undefined;
}
switch (colCount) {
case 1:
return /*#__PURE__*/React.createElement(LayoutOneColumnIcon, {
label: ""
});
case 2:
return /*#__PURE__*/React.createElement(LayoutTwoColumnsIcon, {
label: ""
});
case 3:
return /*#__PURE__*/React.createElement(LayoutThreeColumnsIcon, {
label: ""
});
case 4:
return /*#__PURE__*/React.createElement(EditorLayoutFourColumnsIcon, null);
case 5:
return /*#__PURE__*/React.createElement(EditorLayoutFiveColumnsIcon, null);
default:
return undefined;
}
};
const getAdvancedLayoutItems = ({
addSidebarLayouts,
intl,
editorAnalyticsAPI,
state,
node,
nodeType,
separator,
deleteButton,
currentLayout,
allowAdvancedSingleColumnLayout
}) => {
const numberOfColumns = node.content.childCount || 2;
const distributionOptions = numberOfColumns === 2 ? LAYOUT_WITH_TWO_COL_DISTRIBUTION : numberOfColumns === 3 ? LAYOUT_WITH_THREE_COL_DISTRIBUTION : [];
const columnOptions = [{
title: intl.formatMessage(layoutMessages.columnOption, {
count: 2
}),
//'2-columns',
icon: getLayoutColumnsIcons(2) || iconPlaceholder,
onClick: setPresetLayout(editorAnalyticsAPI)('two_equal'),
selected: numberOfColumns === 2
}, {
title: intl.formatMessage(layoutMessages.columnOption, {
count: 3
}),
//'3-columns'
icon: getLayoutColumnsIcons(3) || iconPlaceholder,
onClick: setPresetLayout(editorAnalyticsAPI)('three_equal'),
selected: numberOfColumns === 3
}, {
title: intl.formatMessage(layoutMessages.columnOption, {
count: 4
}),
//'4-columns'
icon: getLayoutColumnsIcons(4) || iconPlaceholder,
onClick: setPresetLayout(editorAnalyticsAPI)('four_equal'),
selected: numberOfColumns === 4
}, {
title: intl.formatMessage(layoutMessages.columnOption, {
count: 5
}),
//'5-columns'
icon: getLayoutColumnsIcons(5) || iconPlaceholder,
onClick: setPresetLayout(editorAnalyticsAPI)('five_equal'),
selected: numberOfColumns === 5
}];
const singleColumnOption = allowAdvancedSingleColumnLayout ? {
title: intl.formatMessage(layoutMessages.columnOption, {
count: 1
}),
//'1-columns',
icon: getLayoutColumnsIcons(1) || iconPlaceholder,
onClick: setPresetLayout(editorAnalyticsAPI)('single'),
selected: numberOfColumns === 1
} : [];
return [{
type: 'dropdown',
title: intl.formatMessage(layoutMessages.columnOption, {
count: numberOfColumns
}),
//`${numberOfColumns}-columns`,
options: [singleColumnOption, columnOptions].flat(),
showSelected: true,
testId: 'column-options-button'
}, ...(distributionOptions.length > 0 ? [separator] : []), ...(addSidebarLayouts ? distributionOptions.map(i => buildLayoutButton(intl, i, currentLayout, editorAnalyticsAPI)) : [])];
};
const fullHeightSeparator = {
type: 'separator',
fullHeight: true
};
export const buildToolbar = (state, intl, pos, _allowBreakout, addSidebarLayouts, allowSingleColumnLayout, allowAdvancedSingleColumnLayout, api) => {
var _api$decorations$acti, _api$decorations, _api$analytics;
const {
hoverDecoration
} = (_api$decorations$acti = api === null || api === void 0 ? void 0 : (_api$decorations = api.decorations) === null || _api$decorations === void 0 ? void 0 : _api$decorations.actions) !== null && _api$decorations$acti !== void 0 ? _api$decorations$acti : {};
const editorAnalyticsAPI = api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions;
const node = state.doc.nodeAt(pos);
const toolbarFlagsEnabled = areToolbarFlagsEnabled(Boolean(api === null || api === void 0 ? void 0 : api.toolbar));
if (node) {
const currentLayout = getPresetLayout(node);
const separator = {
type: 'separator'
};
const nodeType = state.schema.nodes.layoutSection;
const deleteButton = {
id: 'editor.layout.delete',
type: 'button',
appearance: 'danger',
focusEditoronEnter: true,
icon: DeleteIcon,
testId: commonMessages.remove.id,
title: intl.formatMessage(commonMessages.remove),
onClick: deleteActiveLayoutNode(editorAnalyticsAPI, INPUT_METHOD.FLOATING_TB),
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),
tabIndex: null
};
const copyButton = {
type: 'copy-button',
items: [{
state,
formatMessage: intl.formatMessage,
nodeType
}]
};
const layoutTypes = allowSingleColumnLayout ? LAYOUT_TYPES_WITH_SINGLE_COL : LAYOUT_TYPES;
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 = 'layout-overflow-dropdown-trigger';
const overflowMenu = {
type: 'overflow-dropdown',
testId,
options: [{
title: intl.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(nodeType, INPUT_METHOD.FLOATING_TB));
return true;
},
icon: /*#__PURE__*/React.createElement(CopyIcon, {
label: ""
}),
...hoverDecorationProps(nodeType, akEditorSelectedNodeClassName)
}, {
title: intl.formatMessage(commonMessages.delete),
onClick: deleteActiveLayoutNode(editorAnalyticsAPI, INPUT_METHOD.FLOATING_TB),
icon: /*#__PURE__*/React.createElement(DeleteIcon, {
label: ""
}),
...hoverDecorationProps(nodeType)
}]
};
return {
title: layoutToolbarTitle,
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
getDomRef: view => findDomRefAtPos(pos, view.domAtPos.bind(view)),
nodeType,
groupLabel: intl.formatMessage(toolbarMessages.floatingToolbarRadioGroupAriaLabel),
items: [...(editorExperiment('advanced_layouts', true) ? getAdvancedLayoutItems({
addSidebarLayouts,
intl,
editorAnalyticsAPI,
state,
nodeType,
node,
separator,
deleteButton,
currentLayout,
allowAdvancedSingleColumnLayout
}) : [...layoutTypes.map(i => buildLayoutButton(intl, i, currentLayout, editorAnalyticsAPI)), ...(addSidebarLayouts ? SIDEBAR_LAYOUT_TYPES.map(i => buildLayoutButton(intl, i, currentLayout, editorAnalyticsAPI)) : [])]), ...(toolbarFlagsEnabled ? [fullHeightSeparator, overflowMenu] : [separator, copyButton, separator, deleteButton])],
scrollable: true
};
}
return;
};