UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

308 lines (306 loc) 13.9 kB
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); };