mirror of
				https://github.com/BookStackApp/BookStack.git
				synced 2025-10-31 03:50:27 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			136 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			136 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {CustomTableNode} from "../nodes/custom-table";
 | |
| import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
 | |
| import {$isTableRowNode} from "@lexical/table";
 | |
| 
 | |
| export type CellRange = {
 | |
|     fromX: number;
 | |
|     fromY: number;
 | |
|     toX: number;
 | |
|     toY: number;
 | |
| }
 | |
| 
 | |
| export class TableMap {
 | |
| 
 | |
|     rowCount: number = 0;
 | |
|     columnCount: number = 0;
 | |
| 
 | |
|     // Represents an array (rows*columns in length) of cell nodes from top-left to
 | |
|     // bottom right. Cells may repeat where merged and covering multiple spaces.
 | |
|     cells: CustomTableCellNode[] = [];
 | |
| 
 | |
|     constructor(table: CustomTableNode) {
 | |
|         this.buildCellMap(table);
 | |
|     }
 | |
| 
 | |
|     protected buildCellMap(table: CustomTableNode) {
 | |
|         const rowsAndCells: CustomTableCellNode[][] = [];
 | |
|         const setCell = (x: number, y: number, cell: CustomTableCellNode) => {
 | |
|             if (typeof rowsAndCells[y] === 'undefined') {
 | |
|                 rowsAndCells[y] = [];
 | |
|             }
 | |
| 
 | |
|             rowsAndCells[y][x] = cell;
 | |
|         };
 | |
|         const cellFilled = (x: number, y: number): boolean => !!(rowsAndCells[y] && rowsAndCells[y][x]);
 | |
| 
 | |
|         const rowNodes = table.getChildren().filter(r => $isTableRowNode(r));
 | |
|         for (let rowIndex = 0; rowIndex < rowNodes.length; rowIndex++) {
 | |
|             const rowNode = rowNodes[rowIndex];
 | |
|             const cellNodes = rowNode.getChildren().filter(c => $isCustomTableCellNode(c));
 | |
|             let targetColIndex: number = 0;
 | |
|             for (let cellIndex = 0; cellIndex < cellNodes.length; cellIndex++) {
 | |
|                 const cellNode = cellNodes[cellIndex];
 | |
|                 const colspan = cellNode.getColSpan() || 1;
 | |
|                 const rowSpan = cellNode.getRowSpan() || 1;
 | |
|                 for (let x = targetColIndex; x < targetColIndex + colspan; x++) {
 | |
|                     for (let y = rowIndex; y < rowIndex + rowSpan; y++) {
 | |
|                         while (cellFilled(x, y)) {
 | |
|                             targetColIndex += 1;
 | |
|                             x += 1;
 | |
|                         }
 | |
| 
 | |
|                         setCell(x, y, cellNode);
 | |
|                     }
 | |
|                 }
 | |
|                 targetColIndex += colspan;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.rowCount = rowsAndCells.length;
 | |
|         this.columnCount = Math.max(...rowsAndCells.map(r => r.length));
 | |
| 
 | |
|         const cells = [];
 | |
|         let lastCell: CustomTableCellNode = rowsAndCells[0][0];
 | |
|         for (let y = 0; y < this.rowCount; y++) {
 | |
|             for (let x = 0; x < this.columnCount; x++) {
 | |
|                 if (!rowsAndCells[y] || !rowsAndCells[y][x]) {
 | |
|                     cells.push(lastCell);
 | |
|                 } else {
 | |
|                     cells.push(rowsAndCells[y][x]);
 | |
|                     lastCell = rowsAndCells[y][x];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.cells = cells;
 | |
|     }
 | |
| 
 | |
|     public getCellAtPosition(x: number, y: number): CustomTableCellNode {
 | |
|         const position = (y * this.columnCount) + x;
 | |
|         if (position >= this.cells.length) {
 | |
|             throw new Error(`TableMap Error: Attempted to get cell ${position+1} of ${this.cells.length}`);
 | |
|         }
 | |
| 
 | |
|         return this.cells[position];
 | |
|     }
 | |
| 
 | |
|     public getCellsInRange(range: CellRange): CustomTableCellNode[] {
 | |
|         const minX = Math.max(Math.min(range.fromX, range.toX), 0);
 | |
|         const maxX = Math.min(Math.max(range.fromX, range.toX), this.columnCount - 1);
 | |
|         const minY = Math.max(Math.min(range.fromY, range.toY), 0);
 | |
|         const maxY = Math.min(Math.max(range.fromY, range.toY), this.rowCount - 1);
 | |
| 
 | |
|         const cells = new Set<CustomTableCellNode>();
 | |
| 
 | |
|         for (let y = minY; y <= maxY; y++) {
 | |
|             for (let x = minX; x <= maxX; x++) {
 | |
|                 cells.add(this.getCellAtPosition(x, y));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return [...cells.values()];
 | |
|     }
 | |
| 
 | |
|     public getCellsInColumn(columnIndex: number): CustomTableCellNode[] {
 | |
|         return this.getCellsInRange({
 | |
|             fromX: columnIndex,
 | |
|             toX: columnIndex,
 | |
|             fromY: 0,
 | |
|             toY: this.rowCount - 1,
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     public getRangeForCell(cell: CustomTableCellNode): CellRange|null {
 | |
|         let range: CellRange|null = null;
 | |
|         const cellKey = cell.getKey();
 | |
| 
 | |
|         for (let y = 0; y < this.rowCount; y++) {
 | |
|             for (let x = 0; x < this.columnCount; x++) {
 | |
|                 const index = (y * this.columnCount) + x;
 | |
|                 const lCell = this.cells[index];
 | |
|                 if (lCell.getKey() === cellKey) {
 | |
|                     if (range === null) {
 | |
|                         range = {fromX: x, toX: x, fromY: y, toY: y};
 | |
|                     } else {
 | |
|                         range.fromX = Math.min(range.fromX, x);
 | |
|                         range.toX = Math.max(range.toX, x);
 | |
|                         range.fromY = Math.min(range.fromY, y);
 | |
|                         range.toY = Math.max(range.toY, y);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return range;
 | |
|     }
 | |
| } |