]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/utils/table-map.ts
Opensearch: Fixed XML declaration when php short tags enabled
[bookstack] / resources / js / wysiwyg / utils / table-map.ts
1 import {$isTableCellNode, $isTableRowNode, TableCellNode, TableNode} from "@lexical/table";
2
3 export type CellRange = {
4     fromX: number;
5     fromY: number;
6     toX: number;
7     toY: number;
8 }
9
10 export class TableMap {
11
12     rowCount: number = 0;
13     columnCount: number = 0;
14
15     // Represents an array (rows*columns in length) of cell nodes from top-left to
16     // bottom right. Cells may repeat where merged and covering multiple spaces.
17     cells: TableCellNode[] = [];
18
19     constructor(table: TableNode) {
20         this.buildCellMap(table);
21     }
22
23     protected buildCellMap(table: TableNode) {
24         const rowsAndCells: TableCellNode[][] = [];
25         const setCell = (x: number, y: number, cell: TableCellNode) => {
26             if (typeof rowsAndCells[y] === 'undefined') {
27                 rowsAndCells[y] = [];
28             }
29
30             rowsAndCells[y][x] = cell;
31         };
32         const cellFilled = (x: number, y: number): boolean => !!(rowsAndCells[y] && rowsAndCells[y][x]);
33
34         const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
35         for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
36             const rowNode = rowNodes[rowIndex];
37             const cellNodes = rowNode.getChildren().filter(c => $isTableCellNode(c));
38             let targetColIndex: number = 0;
39             for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
40                 const cellNode = cellNodes[cellIndex];
41                 const colspan = cellNode.getColSpan() || 1;
42                 const rowSpan = cellNode.getRowSpan() || 1;
43                 for (let x = targetColIndex; x < targetColIndex + colspan; x++) {
44                     for (let y = rowIndex; y < rowIndex + rowSpan; y++) {
45                         while (cellFilled(x, y)) {
46                             targetColIndex += 1;
47                             x += 1;
48                         }
49
50                         setCell(x, y, cellNode);
51                     }
52                 }
53                 targetColIndex += colspan;
54             }
55         }
56
57         this.rowCount = rowsAndCells.length;
58         this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
59
60         const cells = [];
61         let lastCell: TableCellNode = rowsAndCells[0][0];
62         for (let y = 0; y < this.rowCount; y++) {
63             for (let x = 0; x < this.columnCount; x++) {
64                 if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
65                     cells.push(lastCell);
66                 } else {
67                     cells.push(rowsAndCells[y][x]);
68                     lastCell = rowsAndCells[y][x];
69                 }
70             }
71         }
72
73         this.cells = cells;
74     }
75
76     public getCellAtPosition(x: number, y: number): TableCellNode {
77         const position = (y * this.columnCount) + x;
78         if (position >= this.cells.length) {
79             throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
80         }
81
82         return this.cells[position];
83     }
84
85     public getCellsInRange(range: CellRange): TableCellNode[] {
86         const minX = Math.max(Math.min(range.fromX, range.toX), 0);
87         const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
88         const minY = Math.max(Math.min(range.fromY, range.toY), 0);
89         const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
90
91         const cells = new Set<TableCellNode>();
92
93         for (let y = minY; y <= maxY; y++) {
94             for (let x = minX; x <= maxX; x++) {
95                 cells.add(this.getCellAtPosition(x, y));
96             }
97         }
98
99         return [...cells.values()];
100     }
101
102     public getCellsInColumn(columnIndex: number): TableCellNode[] {
103         return this.getCellsInRange({
104             fromX: columnIndex,
105             toX: columnIndex,
106             fromY: 0,
107             toY: this.rowCount - 1,
108         });
109     }
110
111     public getRangeForCell(cell: TableCellNode): CellRange|null {
112         let range: CellRange|null = null;
113         const cellKey = cell.getKey();
114
115         for (let y = 0; y < this.rowCount; y++) {
116             for (let x = 0; x < this.columnCount; x++) {
117                 const index = (y * this.columnCount) + x;
118                 const lCell = this.cells[index];
119                 if (lCell.getKey() === cellKey) {
120                     if (range === null) {
121                         range = {fromX: x, toX: x, fromY: y, toY: y};
122                     } else {
123                         range.fromX = Math.min(range.fromX, x);
124                         range.toX = Math.max(range.toX, x);
125                         range.fromY = Math.min(range.fromY, y);
126                         range.toY = Math.max(range.toY, y);
127                     }
128                 }
129             }
130         }
131
132         return range;
133     }
134 }