Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | 1x 1x 1x 1x 1x 1x 1x 1x 17x 17x 17x 15x 15x 17x 34x 84x 84x 19x 19x 19x 84x 35x 49x 1x 84x 34x 84x 34x 19x 34x 17x 35x 21x 14x 3x 17x 19x 19x 50x 50x 50x 5x 21x 4x 3x 18x 18x 18x 48x 48x 3x 19x 2x 2x 21x 21x 8x 8x 8x 7x 7x 8x 8x | import { addBlock } from '../common/addBlock'; import { addSegment } from '../common/addSegment'; import { createBr } from '../creators/createBr'; import { createParagraph } from '../creators/createParagraph'; import { mutateBlock } from '../common/mutate'; import type { ContentModelSegmentFormat, ReadonlyContentModelSegment, ReadonlyContentModelTable, ReadonlyContentModelTableCell, } from 'roosterjs-content-model-types'; /** * Minimum width for a table cell */ export const MIN_ALLOWED_TABLE_CELL_WIDTH: number = 30; /** * Minimum height for a table cell */ export const MIN_ALLOWED_TABLE_CELL_HEIGHT: number = 22; /** * Normalize a Content Model table, make sure: * 1. Fist cells are not spanned * 2. Only first column and row can have headers * 3. All cells have content and width * 4. Table and table row have correct width/height * 5. Spanned cell has no child blocks * 6. default format is correctly applied * @param readonlyTable The table to normalize * @param defaultSegmentFormat @optional Default segment format to apply to cell */ export function normalizeTable( readonlyTable: ReadonlyContentModelTable, defaultSegmentFormat?: ContentModelSegmentFormat ) { const table = mutateBlock(readonlyTable); // Always collapse border and use border box for table in roosterjs to make layout simpler const format = table.format; if (!format.borderCollapse || !format.useBorderBox) { format.borderCollapse = true; format.useBorderBox = true; } // Make sure all first cells are not spanned // Make sure all inner cells are not header // Make sure all cells have content and width table.rows.forEach((row, rowIndex) => { row.cells.forEach((readonlyCell, colIndex) => { const cell = mutateBlock(readonlyCell); if (cell.blocks.length == 0) { const format = cell.format.textColor ? { ...defaultSegmentFormat, textColor: cell.format.textColor, } : defaultSegmentFormat; addBlock( cell, createParagraph(undefined /*isImplicit*/, undefined /*blockFormat*/, format) ); addSegment(cell, createBr(format)); } if (rowIndex == 0) { cell.spanAbove = false; } else if (rowIndex > 0 && colIndex > 0 && cell.isHeader) { cell.isHeader = false; } if (colIndex == 0) { cell.spanLeft = false; } cell.format.useBorderBox = true; }); // Make sure table has correct width and height array if (row.height < MIN_ALLOWED_TABLE_CELL_HEIGHT) { row.height = MIN_ALLOWED_TABLE_CELL_HEIGHT; } }); const columns = Math.max(...table.rows.map(row => row.cells.length)); for (let i = 0; i < columns; i++) { if (table.widths[i] === undefined) { table.widths[i] = getTableCellWidth(columns); } else if (table.widths[i] < MIN_ALLOWED_TABLE_CELL_WIDTH) { table.widths[i] = MIN_ALLOWED_TABLE_CELL_WIDTH; } } // Move blocks from spanned cell to its main cell if any, // and remove rows/columns if all cells in it are spanned const colCount = table.rows[0]?.cells.length || 0; for (let colIndex = colCount - 1; colIndex > 0; colIndex--) { table.rows.forEach(row => { const cell = row.cells[colIndex]; const leftCell = row.cells[colIndex - 1]; if (cell && leftCell && cell.spanLeft) { tryMoveBlocks(leftCell, cell); } }); if (table.rows.every(row => row.cells[colIndex]?.spanLeft)) { table.rows.forEach(row => row.cells.splice(colIndex, 1)); table.widths.splice( colIndex - 1, 2, table.widths[colIndex - 1] + table.widths[colIndex] ); } } for (let rowIndex = table.rows.length - 1; rowIndex > 0; rowIndex--) { const row = table.rows[rowIndex]; row.cells.forEach((cell, colIndex) => { const aboveCell = table.rows[rowIndex - 1]?.cells[colIndex]; if (aboveCell && cell.spanAbove) { tryMoveBlocks(aboveCell, cell); } }); if (row.cells.every(cell => cell.spanAbove)) { table.rows[rowIndex - 1].height += row.height; table.rows.splice(rowIndex, 1); } } } function getTableCellWidth(columns: number): number { Eif (columns <= 4) { return 120; } else if (columns <= 6) { return 100; } else { return 70; } } function tryMoveBlocks( targetCell: ReadonlyContentModelTableCell, sourceCell: ReadonlyContentModelTableCell ) { const onlyHasEmptyOrBr = sourceCell.blocks.every( block => block.blockType == 'Paragraph' && hasOnlyBrSegment(block.segments) ); if (!onlyHasEmptyOrBr) { mutateBlock(targetCell).blocks.push(...sourceCell.blocks); mutateBlock(sourceCell).blocks = []; } } function hasOnlyBrSegment(segments: ReadonlyArray<ReadonlyContentModelSegment>): boolean { segments = segments.filter(s => s.segmentType != 'SelectionMarker'); return segments.length == 0 || (segments.length == 1 && segments[0].segmentType == 'Br'); } |