@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
157 lines (153 loc) • 5.55 kB
JavaScript
import { tableCellMinWidth } from '@atlaskit/editor-common/styles';
import { akEditorDefaultLayoutWidth, akEditorWideLayoutWidth } from '@atlaskit/editor-shared-styles';
import { calculateColumnWidth, getCellsRefsInColumn } from '../table-resizing/utils/column-state';
import { contentWidth } from '../table-resizing/utils/content-width';
import { getLayoutSize } from '../table-resizing/utils/misc';
import { replaceCells } from './table-transform-utils';
const validateTableCellNodeAttrs = ({
colspan,
rowspan,
tableLocalId
}, reportInvalidTableCellSpanAttrs) => {
if (colspan && colspan < 0) {
reportInvalidTableCellSpanAttrs({
nodeType: 'tableCell',
attribute: 'colspan',
reason: 'negative value',
tableLocalId,
spanValue: colspan
});
}
if (rowspan && rowspan < 0) {
reportInvalidTableCellSpanAttrs({
nodeType: 'tableCell',
attribute: 'rowspan',
reason: 'negative value',
tableLocalId,
spanValue: rowspan
});
}
return;
};
// We attempt to patch the document when we have extra, unneeded, column widths
// Take this node for example:
//
// ['td', { colwidth: [100, 100, 100], colspan: 2 }]
//
// This row only spans two columns, yet it contains widths for 3.
// We remove the third width here, assumed duplicate content.
export const removeExtraneousColumnWidths = (node, basePos, tr, reportInvalidTableCellSpanAttrs) => {
let hasProblems = false;
tr = replaceCells(tr, node, basePos, cell => {
const {
colwidth,
colspan,
rowspan
} = cell.attrs;
if (reportInvalidTableCellSpanAttrs) {
validateTableCellNodeAttrs({
colspan,
rowspan,
tableLocalId: node.attrs.localId
}, reportInvalidTableCellSpanAttrs);
}
if (colwidth && colwidth.length > colspan) {
hasProblems = true;
return cell.type.createChecked({
...cell.attrs,
colwidth: colwidth.slice(0, colspan)
}, cell.content, cell.marks);
}
return cell;
});
if (hasProblems) {
return true;
}
return false;
};
export const fixTables = (tr, reportInvalidTableCellSpanAttrs) => {
let hasProblems = false;
tr.doc.descendants((node, pos) => {
if (node.type.name === 'table') {
// in the unlikely event of having to fix multiple tables at the same time
hasProblems = removeExtraneousColumnWidths(node, tr.mapping.map(pos), tr, reportInvalidTableCellSpanAttrs);
}
});
if (hasProblems) {
return tr;
}
};
// When we get a table with an 'auto' attribute, we want to:
// 1. render with table-layout: auto
// 2. capture the column widths
// 3. set the column widths as attributes, and remove the 'auto' attribute,
// so the table renders the same, but is now fixed-width
//
// This can be used to migrate table appearances from other sources that are
// usually rendered with 'auto'.
//
// We use this when migrating TinyMCE tables for Confluence, for example:
// https://pug.jira-dev.com/wiki/spaces/AEC/pages/3362882215/How+do+we+map+TinyMCE+tables+to+Fabric+tables
export const fixAutoSizedTable = (view, tableNode, tableRef, tablePos, opts) => {
let {
tr
} = view.state;
const domAtPos = view.domAtPos.bind(view);
const tableStart = tablePos + 1;
const colWidths = parseDOMColumnWidths(domAtPos, tableNode, tableStart, tableRef);
const totalContentWidth = colWidths.reduce((acc, current) => acc + current, 0);
const tableLayout = getLayoutBasedOnWidth(totalContentWidth);
const maxLayoutSize = getLayoutSize(tableLayout, opts.containerWidth, {});
// Content width will generally not meet the constraints of the layout
// whether it be below or above, so we scale our columns widths
// to meet these requirements
let scale = 1;
if (totalContentWidth !== maxLayoutSize) {
scale = maxLayoutSize / totalContentWidth;
}
const scaledColumnWidths = colWidths.map(width => Math.floor(width * scale));
tr = replaceCells(tr, tableNode, tablePos, (cell, _rowIndex, colIndex) => {
const newColWidths = scaledColumnWidths.slice(colIndex, colIndex + cell.attrs.colspan);
return cell.type.createChecked({
...cell.attrs,
colwidth: newColWidths.length ? newColWidths : null
}, cell.content, cell.marks);
});
// clear autosizing on the table node
return tr.setNodeMarkup(tablePos, undefined, {
...tableNode.attrs,
layout: tableLayout,
__autoSize: false
}).setMeta('addToHistory', false);
};
const getLayoutBasedOnWidth = totalWidth => {
if (totalWidth > akEditorWideLayoutWidth) {
return 'full-width';
} else if (totalWidth > akEditorDefaultLayoutWidth && totalWidth < akEditorWideLayoutWidth) {
return 'wide';
} else {
return 'default';
}
};
function parseDOMColumnWidths(domAtPos, tableNode, tableStart, tableRef) {
const row = tableRef.querySelector('tr');
if (!row) {
return [];
}
const cols = [];
for (let col = 0; col < row.childElementCount; col++) {
const currentCol = row.children[col];
const colspan = Number(currentCol.getAttribute('colspan') || 1);
for (let span = 0; span < colspan; span++) {
const colIdx = col + span;
const cells = getCellsRefsInColumn(colIdx, tableNode, tableStart, domAtPos);
const colWidth = calculateColumnWidth(cells, (_, col) => {
// Ignored via go/ees005
// eslint-disable-next-line @atlaskit/editor/no-as-casting
return contentWidth(col, tableRef).width;
});
cols[colIdx] = Math.max(colWidth, tableCellMinWidth);
}
}
return cols;
}