Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,55 @@ describe('table converter', () => {
expect(result.rows[0].cells[0].colSpan).toBe(3);
});

it('preserves content-less rows covered by a full-width rowspan', () => {
const cell = (text: string): PMNode => ({
type: 'tableCell',
content: [{ type: 'paragraph', content: [{ type: 'text', text }] }],
});
const node: PMNode = {
type: 'table',
attrs: {
grid: [{ col: 1000 }, { col: 1000 }, { col: 1000 }],
},
content: [
{
type: 'tableRow',
content: [
{
...cell('Merged'),
attrs: { rowspan: 2, colspan: 3 },
},
],
},
// ProseMirror's mergeCells command leaves the covered tableRow in
// place but removes all of its cells. Node.toJSON() then omits the
// empty content property entirely.
{ type: 'tableRow' },
{
type: 'tableRow',
content: [cell('A'), cell('B'), cell('C')],
},
],
};

const result = tableNodeToBlock(
node,
mockBlockIdGenerator,
mockPositionMap,
'Arial',
16,
undefined,
undefined,
undefined,
undefined,
mockParagraphConverter,
) as TableBlock;

expect(result.rows).toHaveLength(3);
expect(result.rows[1].cells).toEqual([]);
expect(result.rows[2].cells).toHaveLength(3);
});

it('extracts cell borders when present', () => {
const node: PMNode = {
type: 'table',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,8 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => {
* @param args.context - Parser dependencies (block ID generator, converters, style context)
* @param args.defaultCellPadding - Optional default padding from table style to pass to cells
* @param args.tableStyleId - Optional table style ID for paragraph style cascade in cells
* @returns TableRow object with cells and attributes, or null if the row contains no valid cells
* @returns TableRow object with cells and attributes, including structurally empty rowspan continuation rows,
* or null when the node is not a table row
*
* @example
* // Row with cells
Expand All @@ -790,13 +791,14 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => {
* // Returns: { id: 'row-0', cells: [...], attrs: {...} }
*
* @example
* // Row with no valid cells returns null
* // A structurally empty row is preserved. ProseMirror emits these rows when
* // a full-width cell spans vertically across them.
* parseTableRow({
* rowNode: { type: 'tableRow', content: [] },
* rowIndex: 0,
* context: parserDeps,
* });
* // Returns: null
* // Returns: { id: 'row-0', cells: [] }
*/
/**
* Builds shared {@link TrackedChangeMeta} for a structural row-level tracked
Expand Down Expand Up @@ -842,15 +844,16 @@ const buildRowTrackedChangeMeta = (rowNode: PMNode, storyKey?: string): TrackedC

const parseTableRow = (args: ParseTableRowArgs): TableRow | null => {
const { rowNode, rowIndex, context, defaultCellPadding, tableProperties, numRows } = args;
if (!isTableRowNode(rowNode) || !Array.isArray(rowNode.content)) {
if (!isTableRowNode(rowNode)) {
return null;
}

const rowContent = Array.isArray(rowNode.content) ? rowNode.content : [];
const cells: TableCell[] = [];
const rowCnfStyle = (rowNode.attrs?.tableRowProperties as Record<string, unknown> | undefined)?.cnfStyle as
| Record<string, unknown>
| undefined;
rowNode.content.forEach((cellNode, cellIndex) => {
rowContent.forEach((cellNode, cellIndex) => {
if (isTableCellNode(cellNode) && isTableSkipPlaceholderCell(cellNode)) {
return;
}
Expand All @@ -862,7 +865,7 @@ const parseTableRow = (args: ParseTableRowArgs): TableRow | null => {
context,
defaultCellPadding,
tableProperties,
numCells: rowNode?.content?.length || 1,
numCells: rowContent.length || 1,
numRows,
rowCnfStyle,
gridPlacement: args.cellGridPlacements?.[cellIndex] ?? null,
Expand All @@ -873,8 +876,6 @@ const parseTableRow = (args: ParseTableRowArgs): TableRow | null => {
}
});

if (cells.length === 0) return null;

const rowProps = rowNode.attrs?.tableRowProperties;
const rowHeight = normalizeRowHeight(rowProps as Record<string, unknown> | undefined);
// Structural row-level tracked change (inserted/deleted whole row). Only
Expand Down Expand Up @@ -1162,7 +1163,7 @@ export function tableNodeToBlock(
}
});

if (rows.length === 0) return null;
if (rows.every((row) => row.cells.length === 0)) return null;

const tableAttrs: Record<string, unknown> = {};

Expand Down
Loading