UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

242 lines (232 loc) 9.44 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; 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 { Fragment } from '@atlaskit/editor-prosemirror/model'; import { Selection } from '@atlaskit/editor-prosemirror/state'; import { CellSelection } from '@atlaskit/editor-tables/cell-selection'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { findTable, getSelectionRect } from '@atlaskit/editor-tables/utils'; // re-creates table node with merged cells export function mergeCells(tr) { var selection = tr.selection; if (!(selection instanceof CellSelection) || !canMergeCells(tr)) { return tr; } // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var rect = getSelectionRect(selection); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var table = findTable(selection); var map = TableMap.get(table.node); var seen = []; var selectedCells = map.cellsInRect(rect); var mergedCellPos; var rows = []; for (var rowIndex = 0; rowIndex < map.height; rowIndex++) { var rowCells = []; var row = table.node.child(rowIndex); for (var colIndex = 0; colIndex < map.width; colIndex++) { var cellPos = map.map[rowIndex * map.width + colIndex]; var cell = table.node.nodeAt(cellPos); if (!cell || seen.indexOf(cellPos) > -1) { continue; } seen.push(cellPos); // merged cell if (colIndex === rect.left && rowIndex === rect.top) { mergedCellPos = cellPos; // merge content of the selected cells, dropping empty cells var content = isEmptyCell(cell) ? Fragment.empty : cell.content; var seenContent = [mergedCellPos]; for (var i = rect.top; i < rect.bottom; i++) { for (var j = rect.left; j < rect.right; j++) { var pos = map.map[i * map.width + j]; if (seenContent.indexOf(pos) === -1) { seenContent.push(pos); var copyCell = table.node.nodeAt(pos); if (copyCell && !isEmptyCell(copyCell)) { content = content.append(copyCell.content); } } } } var rowspan = rect.bottom - rect.top; if (rowspan < 1) { return tr; } // update colspan and rowspan of the merged cell to span the selection var attrs = addColSpan(_objectSpread(_objectSpread({}, cell.attrs), {}, { rowspan: rowspan }), cell.attrs.colspan, rect.right - rect.left - cell.attrs.colspan); var newCell = content === Fragment.empty ? cell.type.createAndFill(attrs, content, cell.marks) : cell.type.createChecked(attrs, content, cell.marks); // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion rowCells.push(newCell); } else if (selectedCells.indexOf(cellPos) === -1) { // if its one of the selected cells, but not the merged cell, we get rid of it // otherwise we keep the cell rowCells.push(cell); } } if (rowCells.length) { rows.push(row.type.createChecked(row.attrs, rowCells, row.marks)); } else { // empty row, we need to fix rowspans for rows above the current one for (var _i = rows.length - 1; _i >= 0; _i--) { var prevRow = rows[_i]; var cells = []; var rowChanged = false; for (var _j = 0; _j < prevRow.childCount; _j++) { var _cell = prevRow.child(_j); var _rowspan = _cell.attrs.rowspan; if (_rowspan && _rowspan + _i - 1 >= rows.length) { rowChanged = true; if (_rowspan < 2) { return tr; } cells.push(_cell.type.createChecked(_objectSpread(_objectSpread({}, _cell.attrs), {}, { rowspan: _rowspan - 1 }), _cell.content, _cell.marks)); } else { cells.push(_cell); } } if (rowChanged) { rows[_i] = row.type.createChecked(prevRow.attrs, cells, prevRow.marks); } } } } // empty tables? cancel merging like nothing happened if (!rows.length) { return tr; } var newTable = table.node.type.createChecked(table.node.attrs, rows, table.node.marks); var fixedTable = mergeEmptyColumns(newTable); if (fixedTable === null) { return tr; } return tr.replaceWith(table.pos, table.pos + table.node.nodeSize, fixedTable).setSelection(Selection.near(tr.doc.resolve((mergedCellPos || 0) + table.start))); } export function canMergeCells(tr) { var selection = tr.selection; if (!(selection instanceof CellSelection) || selection.$anchorCell.pos === selection.$headCell.pos) { return false; } var rect = getSelectionRect(selection); if (!rect) { return false; } var table = selection.$anchorCell.node(-1); var map = TableMap.get(table); if (cellsOverlapRectangle(map, rect)) { return false; } return true; } function isEmptyCell(cell) { var content = cell.content; return content.childCount === 1 && content.firstChild && content.firstChild.isTextblock && content.firstChild.childCount === 0; } function addColSpan(attrs, pos) { var span = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; var newAttrs = _objectSpread(_objectSpread({}, attrs), {}, { colspan: (attrs.colspan || 1) + span }); if (newAttrs.colwidth) { newAttrs.colwidth = newAttrs.colwidth.slice(); for (var i = 0; i < span; i++) { newAttrs.colwidth.splice(pos, 0, 0); } } return newAttrs; } function cellsOverlapRectangle(_ref, rect) { var width = _ref.width, height = _ref.height, map = _ref.map; var indexTop = rect.top * width + rect.left; var indexLeft = indexTop; var indexBottom = (rect.bottom - 1) * width + rect.left; var indexRight = indexTop + (rect.right - rect.left - 1); for (var i = rect.top; i < rect.bottom; i++) { if (rect.left > 0 && map[indexLeft] === map[indexLeft - 1] || rect.right < width && map[indexRight] === map[indexRight + 1]) { return true; } indexLeft += width; indexRight += width; } for (var _i2 = rect.left; _i2 < rect.right; _i2++) { if (rect.top > 0 && map[indexTop] === map[indexTop - width] || rect.bottom < height && map[indexBottom] === map[indexBottom + width]) { return true; } indexTop++; indexBottom++; } return false; } // returns an array of numbers, each number indicates the minimum colSpan in each column function getEmptyColumnIndexes(table) { var map = TableMap.get(table); var emptyColumnIndexes = new Set(); // Loop throuh each column for (var colIndex = 0; colIndex < map.width; colIndex++) { // Get the cells in each row for this column var cellPositions = map.cellsInRect({ left: colIndex, right: colIndex + 1, top: 0, bottom: map.height }); // If no cells exist in that column it is empty if (!cellPositions.length) { emptyColumnIndexes.add(colIndex); } } return emptyColumnIndexes; } export function mergeEmptyColumns(table) { var rows = []; var map = TableMap.get(table); var emptyColumnIndexes = getEmptyColumnIndexes(table); // We don't need to remove any so return early. if (emptyColumnIndexes.size === 0) { return table; } for (var rowIndex = 0; rowIndex < map.height; rowIndex++) { var cellsByCols = {}; // Work backwards so that calculating colwidths is easier with Array.slice for (var colIndex = map.width - 1; colIndex >= 0; colIndex--) { var cellPos = map.map[colIndex + rowIndex * map.width]; var rect = map.findCell(cellPos); var cell = cellsByCols[rect.left] || table.nodeAt(cellPos); if (rect.top !== rowIndex) { continue; } // If this column is empty, we want to decrement the colspan of its corresponding // cell as this column is being "merged" if (emptyColumnIndexes.has(colIndex)) { var _cell$attrs = cell.attrs, colspan = _cell$attrs.colspan, colwidth = _cell$attrs.colwidth; if (colspan > 1) { cell = cell.type.createChecked(_objectSpread(_objectSpread({}, cell.attrs), {}, { colspan: colspan - 1, colwidth: colwidth ? colwidth.slice(0, colspan) : null }), cell.content, cell.marks); } } cellsByCols[rect.left] = cell; } var rowCells = Object.values(cellsByCols); var row = table.child(rowIndex); if (row) { rows.push(row.type.createChecked(row.attrs, rowCells, row.marks)); } } if (!rows.length) { return null; } return table.type.createChecked(table.attrs, rows, table.marks); }