UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

278 lines (264 loc) 15.4 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { getParentOfTypeCount } from '@atlaskit/editor-common/nesting'; import { flatmap, mapChildren, mapSlice } from '@atlaskit/editor-common/utils'; import { Slice, Fragment } from '@atlaskit/editor-prosemirror/model'; import { flatten, hasParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { CellSelection } from '@atlaskit/editor-tables'; import { fg } from '@atlaskit/platform-feature-flags'; import { getPluginState } from '../plugin-factory'; // lifts up the content of each cell, returning an array of nodes export var unwrapContentFromTable = function unwrapContentFromTable(maybeTable) { var schema = maybeTable.type.schema; if (maybeTable.type === schema.nodes.table) { var content = []; var _schema$nodes = schema.nodes, tableCell = _schema$nodes.tableCell, tableHeader = _schema$nodes.tableHeader; maybeTable.descendants(function (maybeCell) { if (maybeCell.type === tableCell || maybeCell.type === tableHeader) { content.push.apply(content, _toConsumableArray(flatten(maybeCell, false).map(function (child) { return child.node; }))); return false; } return true; }); return content; } return maybeTable; }; // Flattens nested tables after a given nesting depth // If this looks familiar, it's a heavily modified version of `mapFragment` which has been // adjusted to support tracking nesting depth. This wasn't possible by using `mapFragment` directly var _unwrapNestedTables = function unwrapNestedTables(content, schema, unwrapNestDepth) { var currentNestDepth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var flattenNested = function flattenNested(node, tableDepth) { if (node.type === schema.nodes.table) { if (tableDepth >= unwrapNestDepth) { return unwrapContentFromTable(node); } return node; } return node; }; var children = []; for (var i = 0, size = content.childCount; i < size; i++) { var node = content.child(i); var transformed = node.isLeaf ? flattenNested(node, currentNestDepth) : flattenNested(node.copy(Fragment.fromArray(_unwrapNestedTables(node.content, schema, unwrapNestDepth, node.type === schema.nodes.table ? currentNestDepth + 1 : currentNestDepth))), currentNestDepth); if (transformed) { if (Array.isArray(transformed)) { children.push.apply(children, _toConsumableArray(transformed)); } else { children.push(transformed); } } } return children; }; export var removeTableFromFirstChild = function removeTableFromFirstChild(node, i) { return i === 0 ? unwrapContentFromTable(node) : node; }; export var removeTableFromLastChild = function removeTableFromLastChild(node, i, fragment) { return i === fragment.childCount - 1 ? unwrapContentFromTable(node) : node; }; export var transformSliceToRemoveNestedTables = function transformSliceToRemoveNestedTables(slice, schema, selection) { var _schema$nodes2 = schema.nodes, table = _schema$nodes2.table, tableCell = _schema$nodes2.tableCell, tableHeader = _schema$nodes2.tableHeader; var openEnd = slice.openEnd; var newFragment = flatmap(slice.content, function (node, i, fragment) { var _slice$content$firstC, _slice$content$lastCh; // We allow default nesting of 2 to support // two levels of nesting in nodes that support table nesting already such as layoutSection and expands var allowedTableNesting = 2; var isCellSelection = selection instanceof CellSelection; var isPasteInTable = hasParentNodeOfType([table, tableCell, tableHeader])(selection); var isPasteInNestedTable = getParentOfTypeCount(schema.nodes.table)(selection.$from) > 1; var isPasteFullTableInsideEmptyCellEnabled = fg('platform_editor_paste_full_table_inside_empty_cell'); // Pasted content only contains a table and no other content var isCellPaste = isPasteInTable && slice.content.childCount === 1 && ((_slice$content$firstC = slice.content.firstChild) === null || _slice$content$firstC === void 0 ? void 0 : _slice$content$firstC.type) === table && (!isPasteFullTableInsideEmptyCellEnabled || slice.openStart !== 0 && slice.openEnd !== 0); // however if pasted content is a table, allow just one level if (node.type === schema.nodes.table) { allowedTableNesting = 1; // if paste is inside a table, allow no further nesting if (isPasteInTable) { allowedTableNesting = 0; } // unless we are pasting inside a nested table, then bounce back to 1 level // because editor-plugin-paste will lift the table to the parent table (just below it) if (isPasteInNestedTable) { allowedTableNesting = 1; } // paste of table cells into a table cell - content is spread across multiple cells // by editor-tables so needs to be treated a little differently if (isCellPaste || isCellSelection) { allowedTableNesting = 1; if (isPasteInNestedTable) { allowedTableNesting = 0; } } } // Prevent invalid openEnd after pasting tables with a selection that ends inside a nested table cell. // If the slice ends with a selection that ends inside a nested table, and we paste inside a table we // need to adjust the openEnd because it is no longer correct. If we don't, Prosemirror fires an exception // because it iterates to a non-existent depth and the transform will not be applied if (slice.openEnd >= 7 && // depth of a nested table cell slice.content.childCount > 1 && ((_slice$content$lastCh = slice.content.lastChild) === null || _slice$content$lastCh === void 0 ? void 0 : _slice$content$lastCh.type) === table && isPasteInTable) { // re-point the slice's openEnd to non-nested table cell depth openEnd = 4; } if (isCellSelection && !isCellPaste) { // if pasting into a cell selection, we need to flatten the parent table as well return _unwrapNestedTables(Fragment.fromArray([node]), schema, allowedTableNesting); } else { // after we've worked out what the allowed nesting depth is, unwrap nested tables var newChildren = _unwrapNestedTables(node.content, schema, allowedTableNesting); return node.copy(Fragment.fromArray(newChildren)); } }); return new Slice(newFragment, slice.openStart, openEnd); }; /** * When we copy from a table cell with a hardBreak at the end, * the slice generated will come with a hardBreak outside of the table. * This code will look for that pattern and fix it. */ export var transformSliceToFixHardBreakProblemOnCopyFromCell = function transformSliceToFixHardBreakProblemOnCopyFromCell(slice, schema) { var _schema$nodes3 = schema.nodes, paragraph = _schema$nodes3.paragraph, table = _schema$nodes3.table, hardBreak = _schema$nodes3.hardBreak; var emptyParagraphNode = paragraph.createAndFill(); var hardBreakNode = hardBreak === null || hardBreak === void 0 ? void 0 : hardBreak.createAndFill(); var paragraphNodeSize = emptyParagraphNode ? emptyParagraphNode.nodeSize : 0; var hardBreakNodeSize = hardBreakNode ? hardBreakNode.nodeSize : 0; var paragraphWithHardBreakSize = paragraphNodeSize + hardBreakNodeSize; if (slice.content.childCount === 2 && slice.content.firstChild && slice.content.lastChild && slice.content.firstChild.type === table && slice.content.lastChild.type === paragraph && slice.content.lastChild.nodeSize === paragraphWithHardBreakSize) { var nodes = unwrapContentFromTable(slice.content.firstChild); if (nodes instanceof Array) { return new Slice(Fragment.from( // keep only the content and discard the hardBreak nodes[0]), slice.openStart, slice.openEnd); } } return slice; }; var isNodeSingleCellTable = function isNodeSingleCellTable(node, schema) { var _node$firstChild, _node$firstChild$firs, _node$firstChild$firs2; return node.childCount === 1 && ((_node$firstChild = node.firstChild) === null || _node$firstChild === void 0 ? void 0 : _node$firstChild.type) === schema.nodes.tableRow && node.firstChild.childCount === 1 && (((_node$firstChild$firs = node.firstChild.firstChild) === null || _node$firstChild$firs === void 0 ? void 0 : _node$firstChild$firs.type) === schema.nodes.tableCell || ((_node$firstChild$firs2 = node.firstChild.firstChild) === null || _node$firstChild$firs2 === void 0 ? void 0 : _node$firstChild$firs2.type) === schema.nodes.tableHeader); }; var isFragmentSingleCellTable = function isFragmentSingleCellTable(fragment, schema) { return fragment.childCount === 1 && fragment.firstChild !== null && isNodeSingleCellTable(fragment.firstChild, schema); }; var containsNonTableBlockChildren = function containsNonTableBlockChildren(fragment, schema) { var containsNonTableBlock = false; var _schema$nodes4 = schema.nodes, table = _schema$nodes4.table, tableCell = _schema$nodes4.tableCell, tableHeader = _schema$nodes4.tableHeader; fragment.forEach(function (node) { if (node.isBlock && ![table, tableCell, tableHeader].includes(node.type)) { containsNonTableBlock = true; } }); return containsNonTableBlock; }; export var transformSliceToRemoveOpenTable = function transformSliceToRemoveOpenTable(slice, schema) { var _slice$content$firstC4; // Case 1: A slice of a block selection inside a nested table // Prosemirror wraps nested block selections in their respective tables // We are using `safeInsert` to paste nested tables, so we do not want to preserve this wrapping // slice starts and ends inside a nested table at the same depth if (slice.openStart >= 7 && slice.openEnd >= 7) { var cleaned = slice; var descendedDepth = 0; var tableDepthDecrement = 2; // if the slice is a single cell table and contains cells with single cell tables, descend into it until we find textblock children if (isFragmentSingleCellTable(slice.content, schema)) { var _slice$content$firstC2; (_slice$content$firstC2 = slice.content.firstChild) === null || _slice$content$firstC2 === void 0 || _slice$content$firstC2.descendants(function (node) { if (isNodeSingleCellTable(node, schema)) { descendedDepth += tableDepthDecrement; } else if (node.type === schema.nodes.table) { return false; } else if (containsNonTableBlockChildren(node.content, schema)) { descendedDepth += tableDepthDecrement; // create a new slice with the content of non-table block children and the depth of the nested tables subtracted cleaned = new Slice(node.content, slice.openStart - descendedDepth - tableDepthDecrement, slice.openEnd - descendedDepth - tableDepthDecrement); return false; } }); } if (!cleaned.eq(slice)) { return cleaned; } } // Case 2: A slice entirely within a single CELL if ( // starts and ends inside of a cell slice.openStart >= 4 && slice.openEnd >= 4 && // slice is a table node slice.content.childCount === 1 && // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion slice.content.firstChild.type === schema.nodes.table) { var _slice$content$firstC3; // we're removing the table, tableRow and tableCell reducing the open depth by 3 var depthDecrement = 3; // prosemirror-view has a bug that it duplicates table entry when selecting multiple paragraphs in a table cell. // https://github.com/ProseMirror/prosemirror/issues/1270 // The structure becomes // table(genuine) > tableRow(genuine) > table(duplicated) > tableRow(duplicated) > tableCell/tableHeader(genuine) > contents(genuine) // As we are removing wrapping table anyway, we keep duplicated table and tableRow for simplicity var _cleaned = slice; if (((_slice$content$firstC3 = slice.content.firstChild) === null || _slice$content$firstC3 === void 0 || (_slice$content$firstC3 = _slice$content$firstC3.content) === null || _slice$content$firstC3 === void 0 || (_slice$content$firstC3 = _slice$content$firstC3.firstChild) === null || _slice$content$firstC3 === void 0 || (_slice$content$firstC3 = _slice$content$firstC3.content) === null || _slice$content$firstC3 === void 0 || (_slice$content$firstC3 = _slice$content$firstC3.firstChild) === null || _slice$content$firstC3 === void 0 ? void 0 : _slice$content$firstC3.type) === schema.nodes.table) { _cleaned = new Slice(slice.content.firstChild.content.firstChild.content, slice.openStart - 2, slice.openEnd - 2); } return new Slice(flatmap(_cleaned.content, unwrapContentFromTable), _cleaned.openStart - depthDecrement, _cleaned.openEnd - depthDecrement); } // Case 3: A slice starting within a CELL and ending outside the table if ( // starts inside of a cell but ends outside of the starting table slice.openStart >= 4 && // slice starts from a table node (and spans across more than one node) slice.content.childCount > 1 && ((_slice$content$firstC4 = slice.content.firstChild) === null || _slice$content$firstC4 === void 0 ? void 0 : _slice$content$firstC4.type) === schema.nodes.table) { // repoint the slice's cutting depth so that cell content where the slice starts // does not get lifted out of the cell on paste return new Slice(slice.content, 1, slice.openEnd); } return slice; }; export var transformSliceToCorrectEmptyTableCells = function transformSliceToCorrectEmptyTableCells(slice, schema) { var _schema$nodes5 = schema.nodes, tableCell = _schema$nodes5.tableCell, tableHeader = _schema$nodes5.tableHeader; return mapSlice(slice, function (node) { if (node && (node.type === tableCell || node.type === tableHeader) && !node.content.childCount) { return node.type.createAndFill(node.attrs) || node; } return node; }); }; export function isHeaderRowRequired(state) { var tableState = getPluginState(state); return tableState && tableState.pluginConfig.isHeaderRowRequired; } export var transformSliceTableLayoutDefaultToCenter = function transformSliceTableLayoutDefaultToCenter(slice, schema) { var table = schema.nodes.table; var children = []; mapChildren(slice.content, function (node) { if (node.type === table && node.attrs.layout === 'default') { children.push(table.createChecked(_objectSpread(_objectSpread({}, node.attrs), {}, { layout: 'center' }), node.content, node.marks)); } else { children.push(node); } }); return new Slice(Fragment.fromArray(children), slice.openStart, slice.openEnd); };