@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
308 lines (306 loc) • 13.9 kB
JavaScript
import React from 'react';
import { TableSortOrder as SortOrder } from '@atlaskit/custom-steps';
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics';
import { addColumnAfter, addColumnBefore, addRowAfter, addRowBefore, backspace, deleteColumn, deleteRow, moveColumnLeft, moveColumnRight, moveRowDown, moveRowUp, tooltip } from '@atlaskit/editor-common/keymaps';
import SortAscendingIcon from '@atlaskit/icon/core/sort-ascending';
import SortDescendingIcon from '@atlaskit/icon/core/sort-descending';
import TableCellClearIcon from '@atlaskit/icon/core/table-cell-clear';
import TableColumnAddLeftIcon from '@atlaskit/icon/core/table-column-add-left';
import TableColumnAddRightIcon from '@atlaskit/icon/core/table-column-add-right';
import TableColumnDeleteIcon from '@atlaskit/icon/core/table-column-delete';
import TableColumnMoveLeftIcon from '@atlaskit/icon/core/table-column-move-left';
import TableColumnMoveRightIcon from '@atlaskit/icon/core/table-column-move-right';
import TableColumnsDistributeIcon from '@atlaskit/icon/core/table-columns-distribute';
import TableRowAddAboveIcon from '@atlaskit/icon/core/table-row-add-above';
import TableRowAddBelowIcon from '@atlaskit/icon/core/table-row-add-below';
import TableRowDeleteIcon from '@atlaskit/icon/core/table-row-delete';
import TableRowMoveDownIcon from '@atlaskit/icon/core/table-row-move-down';
import TableRowMoveUpIcon from '@atlaskit/icon/core/table-row-move-up';
import ArrowDownIcon from '@atlaskit/icon/glyph/arrow-down';
import ArrowLeftIcon from '@atlaskit/icon/glyph/arrow-left';
import ArrowRightIcon from '@atlaskit/icon/glyph/arrow-right';
import ArrowUpIcon from '@atlaskit/icon/glyph/arrow-up';
import CrossCircleIcon from '@atlaskit/icon/glyph/cross-circle';
import EditorLayoutThreeEqualIcon from '@atlaskit/icon/glyph/editor/layout-three-equal';
import RemoveIcon from '@atlaskit/icon/glyph/editor/remove';
import HipchatChevronDoubleDownIcon from '@atlaskit/icon/glyph/hipchat/chevron-double-down';
import HipchatChevronDoubleUpIcon from '@atlaskit/icon/glyph/hipchat/chevron-double-up';
import { AddColLeftIcon } from '../../ui/icons/AddColLeftIcon';
import { AddColRightIcon } from '../../ui/icons/AddColRightIcon';
import { AddRowAboveIcon } from '../../ui/icons/AddRowAboveIcon';
import { AddRowBelowIcon } from '../../ui/icons/AddRowBelowIcon';
import { getClosestSelectionRect } from '../../ui/toolbar';
import { deleteColumnsWithAnalytics, deleteRowsWithAnalytics, distributeColumnsWidthsWithAnalytics, emptyMultipleCellsWithAnalytics, insertColumnWithAnalytics, insertRowWithAnalytics, sortColumnWithAnalytics } from '../commands/commands-with-analytics';
import { moveSourceWithAnalytics } from '../drag-and-drop/commands-with-analytics';
import { getPluginState as getTablePluginState } from '../plugin-factory';
import { getNewResizeStateFromSelectedColumns } from '../table-resizing/utils/resize-state';
import { hasMergedCellsInSelection, hasMergedCellsWithColumnNextToColumnIndex, hasMergedCellsWithRowNextToRowIndex } from './merged-cells';
import { getSelectedColumnIndexes, getSelectedRowIndexes } from './selection';
export const getTargetIndex = (selectedIndexes, direction) => Math[direction < 0 ? 'min' : 'max'](...selectedIndexes) + direction;
export const canMove = (sourceType, direction, totalItemsOfSourceTypeCount, selection, selectionRect) => {
if (!selectionRect) {
return false;
}
const isRow = sourceType === 'table-row';
const selectedIndexes = isRow ? getSelectedRowIndexes(selectionRect) : getSelectedColumnIndexes(selectionRect);
const targetIndex = getTargetIndex(selectedIndexes, direction);
const isValidTargetIndex = targetIndex >= 0 && targetIndex < totalItemsOfSourceTypeCount;
if (!isValidTargetIndex) {
return false;
}
// We can't move column when target has merged cells with other columns
// We can't move row when target has merged cells with other rows
const hasMergedCellsInTarget = isRow ? hasMergedCellsWithRowNextToRowIndex(targetIndex, selection) : hasMergedCellsWithColumnNextToColumnIndex(targetIndex, selection);
if (hasMergedCellsInTarget) {
return false;
}
// We can't move if selection in the source is not a rectangle
if (hasMergedCellsInSelection(selectedIndexes, isRow ? 'row' : 'column')(selection)) {
return false;
}
return true;
};
const isDistributeColumnsEnabled = state => {
const rect = getClosestSelectionRect(state);
if (rect) {
const selectedColIndexes = getSelectedColumnIndexes(rect);
return selectedColIndexes.length > 1;
}
return false;
};
const defaultSelectionRect = {
left: 0,
top: 0,
right: 0,
bottom: 0
};
export const getDragMenuConfig = (direction, getEditorContainerWidth, hasMergedCellsInTable, editorView, api, tableMap, index, targetCellPosition, selectionRect, editorAnalyticsAPI, isHeaderRowRequired, isTableScalingEnabled = false, isTableFixedColumnWidthsOptionEnabled = false, shouldUseIncreasedScalingPercent = false, ariaNotifyPlugin, isCommentEditor = false) => {
var _tableMap$height, _tableMap$height2, _tableMap$width, _tableMap$width2;
const {
selection
} = editorView.state;
const {
getIntl
} = getTablePluginState(editorView.state);
const addOptions = direction === 'row' ? [{
label: 'above',
offset: 0,
icon: () => /*#__PURE__*/React.createElement(TableRowAddAboveIcon, {
LEGACY_fallbackIcon: AddRowAboveIcon,
spacing: 'spacious',
label: ''
}),
keymap: addRowBefore
}, {
label: 'below',
offset: 1,
icon: () => /*#__PURE__*/React.createElement(TableRowAddBelowIcon, {
LEGACY_fallbackIcon: AddRowBelowIcon,
spacing: 'spacious',
label: ''
}),
keymap: addRowAfter
}] : [{
label: 'left',
offset: 0,
icon: () => /*#__PURE__*/React.createElement(TableColumnAddLeftIcon, {
LEGACY_fallbackIcon: AddColLeftIcon,
spacing: 'spacious',
label: ''
}),
keymap: addColumnBefore
}, {
label: 'right',
offset: 1,
icon: () => /*#__PURE__*/React.createElement(TableColumnAddRightIcon, {
LEGACY_fallbackIcon: AddColRightIcon,
spacing: 'spacious',
label: ''
}),
keymap: addColumnAfter
}];
const moveOptions = direction === 'row' ? [{
label: 'up',
icon: () => /*#__PURE__*/React.createElement(TableRowMoveUpIcon, {
LEGACY_fallbackIcon: ArrowUpIcon,
spacing: 'spacious',
label: ''
}),
keymap: moveRowUp,
canMove: canMove('table-row', -1, (_tableMap$height = tableMap === null || tableMap === void 0 ? void 0 : tableMap.height) !== null && _tableMap$height !== void 0 ? _tableMap$height : 0, selection, selectionRect),
getOriginIndexes: getSelectedRowIndexes,
getTargetIndex: selectionRect => selectionRect.top - 1
}, {
label: 'down',
icon: () => /*#__PURE__*/React.createElement(TableRowMoveDownIcon, {
LEGACY_fallbackIcon: ArrowDownIcon,
spacing: 'spacious',
label: ''
}),
keymap: moveRowDown,
canMove: canMove('table-row', 1, (_tableMap$height2 = tableMap === null || tableMap === void 0 ? void 0 : tableMap.height) !== null && _tableMap$height2 !== void 0 ? _tableMap$height2 : 0, selection, selectionRect),
getOriginIndexes: getSelectedRowIndexes,
getTargetIndex: selectionRect => selectionRect.bottom
}] : [{
label: 'left',
icon: () => /*#__PURE__*/React.createElement(TableColumnMoveLeftIcon, {
LEGACY_fallbackIcon: ArrowLeftIcon,
spacing: 'spacious',
label: ''
}),
keymap: moveColumnLeft,
canMove: canMove('table-column', -1, (_tableMap$width = tableMap === null || tableMap === void 0 ? void 0 : tableMap.width) !== null && _tableMap$width !== void 0 ? _tableMap$width : 0, selection, selectionRect),
getOriginIndexes: getSelectedColumnIndexes,
getTargetIndex: selectionRect => selectionRect.left - 1
}, {
label: 'right',
icon: () => /*#__PURE__*/React.createElement(TableColumnMoveRightIcon, {
LEGACY_fallbackIcon: ArrowRightIcon,
spacing: 'spacious',
label: ''
}),
keymap: moveColumnRight,
canMove: canMove('table-column', 1, (_tableMap$width2 = tableMap === null || tableMap === void 0 ? void 0 : tableMap.width) !== null && _tableMap$width2 !== void 0 ? _tableMap$width2 : 0, selection, selectionRect),
getOriginIndexes: getSelectedColumnIndexes,
getTargetIndex: selectionRect => selectionRect.right
}];
const sortOptions = direction === 'column' ? [{
label: 'increasing',
order: SortOrder.ASC,
icon: () => /*#__PURE__*/React.createElement(SortAscendingIcon, {
LEGACY_fallbackIcon: HipchatChevronDoubleUpIcon,
spacing: 'spacious',
label: ''
})
}, {
label: 'decreasing',
order: SortOrder.DESC,
icon: () => /*#__PURE__*/React.createElement(SortDescendingIcon, {
LEGACY_fallbackIcon: HipchatChevronDoubleDownIcon,
spacing: 'spacious',
label: ''
})
}] : [];
const sortConfigs = [...sortOptions.map(({
label,
order,
icon
}) => ({
id: `sort_column_${order}`,
title: `Sort ${label}`,
disabled: hasMergedCellsInTable,
icon: icon,
onClick: (state, dispatch) => {
sortColumnWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.TABLE_CONTEXT_MENU, index !== null && index !== void 0 ? index : 0, order)(state, dispatch);
return true;
}
}))];
const restConfigs = [...addOptions.map(({
label,
offset,
icon,
keymap
}) => ({
id: `add_${direction}_${label}`,
title: `Add ${direction} ${label}`,
icon: icon,
onClick: (state, dispatch) => {
if (direction === 'row') {
insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.TABLE_CONTEXT_MENU, {
index: (index !== null && index !== void 0 ? index : 0) + offset,
moveCursorToInsertedRow: true
})(state, dispatch);
} else {
insertColumnWithAnalytics(api, editorAnalyticsAPI, isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled, shouldUseIncreasedScalingPercent)(INPUT_METHOD.TABLE_CONTEXT_MENU, (index !== null && index !== void 0 ? index : 0) + offset)(state, dispatch, editorView);
}
return true;
},
keymap: keymap && tooltip(keymap)
})), direction === 'column' ? {
id: 'distribute_columns',
title: 'Distribute columns',
disabled: !isDistributeColumnsEnabled(editorView.state),
onClick: (state, dispatch) => {
const selectionRect = getClosestSelectionRect(state);
if (selectionRect) {
const newResizeState = getNewResizeStateFromSelectedColumns(selectionRect, state, editorView.domAtPos.bind(editorView), getEditorContainerWidth, isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled, isCommentEditor);
if (newResizeState) {
distributeColumnsWidthsWithAnalytics(editorAnalyticsAPI, api)(INPUT_METHOD.TABLE_CONTEXT_MENU, newResizeState)(state, dispatch);
return true;
}
return false;
}
return false;
},
icon: () => /*#__PURE__*/React.createElement(TableColumnsDistributeIcon, {
LEGACY_fallbackIcon: EditorLayoutThreeEqualIcon,
spacing: 'spacious',
label: ''
})
} : undefined, {
id: 'clear_cells',
title: 'Clear cells',
onClick: (state, dispatch) => {
emptyMultipleCellsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.TABLE_CONTEXT_MENU, targetCellPosition)(state, dispatch);
return true;
},
icon: () => /*#__PURE__*/React.createElement(TableCellClearIcon, {
LEGACY_fallbackIcon: CrossCircleIcon,
spacing: 'spacious',
label: ''
}),
keymap: tooltip(backspace)
}, {
id: `delete_${direction}`,
title: `Delete ${direction}`,
onClick: (state, dispatch) => {
if (direction === 'row') {
deleteRowsWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.TABLE_CONTEXT_MENU, selectionRect !== null && selectionRect !== void 0 ? selectionRect : defaultSelectionRect, !!isHeaderRowRequired)(state, dispatch);
} else {
deleteColumnsWithAnalytics(editorAnalyticsAPI, api, isTableScalingEnabled, isTableFixedColumnWidthsOptionEnabled, shouldUseIncreasedScalingPercent, isCommentEditor)(INPUT_METHOD.TABLE_CONTEXT_MENU, selectionRect !== null && selectionRect !== void 0 ? selectionRect : defaultSelectionRect)(state, dispatch, editorView);
}
return true;
},
icon: direction === 'row' ? () => /*#__PURE__*/React.createElement(TableRowDeleteIcon, {
LEGACY_fallbackIcon: RemoveIcon,
spacing: 'spacious',
label: ''
}) : () => /*#__PURE__*/React.createElement(TableColumnDeleteIcon, {
LEGACY_fallbackIcon: RemoveIcon,
spacing: 'spacious',
label: ''
}),
keymap: direction === 'row' ? tooltip(deleteRow) : tooltip(deleteColumn)
}, ...moveOptions.map(({
label,
canMove,
icon,
keymap,
getOriginIndexes,
getTargetIndex
}) => ({
id: `move_${direction}_${label}`,
title: `Move ${direction} ${label}`,
disabled: !canMove,
icon: icon,
onClick: (state, dispatch) => {
if (canMove) {
requestAnimationFrame(() => {
moveSourceWithAnalytics(editorAnalyticsAPI, ariaNotifyPlugin, getIntl)(INPUT_METHOD.TABLE_CONTEXT_MENU, `table-${direction}`,
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getOriginIndexes(selectionRect),
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
getTargetIndex(selectionRect))(editorView.state, editorView.dispatch);
});
return true;
}
return false;
},
keymap: keymap && tooltip(keymap)
}))];
const allConfigs = [...restConfigs];
allConfigs.unshift(...sortConfigs);
return allConfigs.filter(Boolean);
};