UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

157 lines (153 loc) 5.55 kB
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; }