@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
242 lines (232 loc) • 9.44 kB
JavaScript
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);
}