UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

175 lines (171 loc) • 7.3 kB
// #region Imports import { TableSortStep } from '@atlaskit/custom-steps'; import { isTextInput } from '@atlaskit/editor-common/utils'; import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { findTable } from '@atlaskit/editor-tables/utils'; import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled'; import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals'; import { defaultTableSelection } from './default-table-selection'; import { pluginKey as tableResizingPluginKey } from './table-resizing/plugin-key'; import { isTableCollapsible } from './utils/collapse'; import { checkIfHeaderColumnEnabled, checkIfHeaderRowEnabled, checkIfNumberColumnEnabled } from './utils/nodes'; const nextTableSorting = (tr, table) => { const tableSortStep = tr.steps.find(step => step instanceof TableSortStep); return tableSortStep && table && table.pos === tableSortStep.pos ? tableSortStep.next : undefined; }; const nextResizeHandleColumnIndex = (tr, resizeHandleColumnIndex) => { if (tr.getMeta(tableResizingPluginKey)) { return undefined; } return resizeHandleColumnIndex; }; const shouldCloseLegacyContextualMenu = ({ pluginState, targetCellPositionChanged, tr }) => Boolean(pluginState.isContextualMenuOpen && (targetCellPositionChanged || tr.selectionSet && !(tr.selection instanceof CellSelection))); const updateTargetCellPosition = ({ tr, table }) => pluginState => { const tableNode = table && table.node; if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) { var _pluginState$activeTa, _pluginState$activeTa2; let targetCellPosition; if (tableNode) { const { tableCell, tableHeader } = tr.doc.type.schema.nodes; const cell = findParentNodeOfType([tableCell, tableHeader])(tr.selection); targetCellPosition = cell ? cell.pos : undefined; } const hasTargetCellChanged = pluginState.targetCellPosition !== targetCellPosition; const hasActiveTableMenu = pluginState.activeTableMenu != null && pluginState.activeTableMenu.type !== 'none'; const isActiveDragMenu = ((_pluginState$activeTa = pluginState.activeTableMenu) === null || _pluginState$activeTa === void 0 ? void 0 : _pluginState$activeTa.type) === 'row' || ((_pluginState$activeTa2 = pluginState.activeTableMenu) === null || _pluginState$activeTa2 === void 0 ? void 0 : _pluginState$activeTa2.type) === 'column'; const shouldPreserveActiveDragMenu = isExperimentEnabled('platform_editor_table_menu_updates_patch_4') && isActiveDragMenu && tr.selection instanceof CellSelection; const shouldCloseMenu = hasActiveTableMenu && tr.selectionSet && (!tableNode || !(tr.selection instanceof CellSelection) || hasTargetCellChanged && !shouldPreserveActiveDragMenu); if (!hasTargetCellChanged && !shouldCloseMenu) { return pluginState; } // Cell menus are anchored to a specific cell and close when that target changes. Row and // column menus are anchored to the selected-range drag handle, so keep them open while a // CellSelection expands and let the handle re-render at the updated range. return { ...pluginState, targetCellPosition, activeTableMenu: shouldCloseMenu ? { type: 'none' } : pluginState.activeTableMenu }; } if (!tableNode) { return { ...pluginState, targetCellPosition: undefined }; } const { tableCell, tableHeader } = tr.doc.type.schema.nodes; const cell = findParentNodeOfType([tableCell, tableHeader])(tr.selection); const targetCellPosition = cell ? cell.pos : undefined; const targetCellPositionChanged = pluginState.targetCellPosition !== targetCellPosition; const closeContextualMenu = shouldCloseLegacyContextualMenu({ pluginState, targetCellPositionChanged, tr }); if (!targetCellPositionChanged && !closeContextualMenu) { return pluginState; } // Close the legacy contextual menu when moving cells because the cell background // color submenu can otherwise remain open against the previous cell selection. return { ...pluginState, ...(closeContextualMenu ? { isContextualMenuOpen: false } : {}), targetCellPosition }; }; const updateTableNodePluginState = ({ tr, table }) => pluginState => { const tableNode = table && table.node; if (!tableNode || isTextInput(tr)) { return pluginState; } return { ...pluginState, ...defaultTableSelection, tableNode, ordering: nextTableSorting(tr, table), resizeHandleColumnIndex: nextResizeHandleColumnIndex(tr, pluginState.resizeHandleColumnIndex), isNumberColumnEnabled: checkIfNumberColumnEnabled(tr.selection), isHeaderColumnEnabled: checkIfHeaderColumnEnabled(tr.selection), isHeaderRowEnabled: checkIfHeaderRowEnabled(tr.selection) }; }; const updateCollapseHandler = ({ tr, table }) => pluginState => { const tableNode = table && table.node; const schema = tr.doc.type.schema; const allowCollapse = pluginState.pluginConfig.allowCollapse; const isExpandInSchema = schema.nodes.expand !== undefined; const isCollapseEnabled = allowCollapse && isExpandInSchema; /** * If we don't have focus, or collapse isn't allowed, or a table node doesn't * exist, we don't need to waste extra checks below */ if (!pluginState.editorHasFocus || !isCollapseEnabled || !tableNode) { return pluginState; } const expandNodeType = schema.nodes.expand; const isTableCollapsed = expandNodeType && !!findParentNodeOfType(expandNodeType)(tr.selection); const trCanBeCollapsed = isTableCollapsible(tr).tableIsCollapsible; // We're focused on a table + we're not inside an expand const canCollapseTable = !!pluginState.tableNode && // is it already collapsed? !isTableCollapsed && !!trCanBeCollapsed; if (pluginState.isTableCollapsed !== isTableCollapsed || pluginState.canCollapseTable !== canCollapseTable) { return { ...pluginState, isTableCollapsed, canCollapseTable }; } return pluginState; }; const buildPluginState = builders => props => pluginState => { if (!props.table) { if (expValEquals('platform_editor_table_menu_updates', 'isEnabled', true)) { const shouldClearTargetCellPosition = pluginState.targetCellPosition !== undefined; const hasActiveTableMenu = pluginState.activeTableMenu != null && pluginState.activeTableMenu.type !== 'none'; if (!shouldClearTargetCellPosition && !hasActiveTableMenu) { return pluginState; } return { ...pluginState, targetCellPosition: undefined, activeTableMenu: hasActiveTableMenu ? { type: 'none' } : pluginState.activeTableMenu }; } return pluginState.targetCellPosition ? { ...pluginState, targetCellPosition: undefined } : pluginState; } return builders.reduce((_pluginState, transform) => transform(props)(_pluginState), pluginState); }; export const handleDocOrSelectionChanged = (tr, pluginState) => buildPluginState([updateTargetCellPosition, updateTableNodePluginState, updateCollapseHandler])({ tr, table: findTable(tr.selection) })(pluginState);