]> BookStack Code Mirror - bookstack/blob - resources/js/wysiwyg/utils/table-copy-paste.ts
Lexical: Improved table row copy/paste
[bookstack] / resources / js / wysiwyg / utils / table-copy-paste.ts
1 import {NodeClipboard} from "./node-clipboard";
2 import {CustomTableRowNode} from "../nodes/custom-table-row";
3 import {$getTableFromSelection, $getTableRowsFromSelection} from "./tables";
4 import {$getSelection, LexicalEditor} from "lexical";
5 import {$createCustomTableCellNode, $isCustomTableCellNode} from "../nodes/custom-table-cell";
6 import {CustomTableNode} from "../nodes/custom-table";
7 import {TableMap} from "./table-map";
8
9 const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(CustomTableRowNode);
10
11 export function isRowClipboardEmpty(): boolean {
12     return rowClipboard.size() === 0;
13 }
14
15 export function validateRowsToCopy(rows: CustomTableRowNode[]): void {
16     let commonRowSize: number|null = null;
17
18     for (const row of rows) {
19         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
20         let rowSize = 0;
21         for (const cell of cells) {
22             rowSize += cell.getColSpan() || 1;
23             if (cell.getRowSpan() > 1) {
24                 throw Error('Cannot copy rows with merged cells');
25             }
26         }
27
28         if (commonRowSize === null) {
29             commonRowSize = rowSize;
30         } else if (commonRowSize !== rowSize) {
31             throw Error('Cannot copy rows with inconsistent sizes');
32         }
33     }
34 }
35
36 export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: CustomTableNode): void {
37     const tableColCount = (new TableMap(targetTable)).columnCount;
38     for (const row of rows) {
39         const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
40         let rowSize = 0;
41         for (const cell of cells) {
42             rowSize += cell.getColSpan() || 1;
43         }
44
45         if (rowSize > tableColCount) {
46             throw Error('Cannot paste rows that are wider than target table');
47         }
48
49         while (rowSize < tableColCount) {
50             row.append($createCustomTableCellNode());
51             rowSize++;
52         }
53     }
54 }
55
56 export function $cutSelectedRowsToClipboard(): void {
57     const rows = $getTableRowsFromSelection($getSelection());
58     validateRowsToCopy(rows);
59     rowClipboard.set(...rows);
60     for (const row of rows) {
61         row.remove();
62     }
63 }
64
65 export function $copySelectedRowsToClipboard(): void {
66     const rows = $getTableRowsFromSelection($getSelection());
67     validateRowsToCopy(rows);
68     rowClipboard.set(...rows);
69 }
70
71 export function $pasteClipboardRowsBefore(editor: LexicalEditor): void {
72     const selection = $getSelection();
73     const rows = $getTableRowsFromSelection(selection);
74     const table = $getTableFromSelection(selection);
75     const lastRow = rows[rows.length - 1];
76     if (lastRow && table) {
77         const clipboardRows = rowClipboard.get(editor);
78         validateRowsToPaste(clipboardRows, table);
79         for (const row of clipboardRows) {
80             lastRow.insertBefore(row);
81         }
82     }
83 }
84
85 export function $pasteRowsAfter(editor: LexicalEditor): void {
86     const selection = $getSelection();
87     const rows = $getTableRowsFromSelection(selection);
88     const table = $getTableFromSelection(selection);
89     const lastRow = rows[rows.length - 1];
90     if (lastRow && table) {
91         const clipboardRows = rowClipboard.get(editor).reverse();
92         validateRowsToPaste(clipboardRows, table);
93         for (const row of clipboardRows) {
94             lastRow.insertAfter(row);
95         }
96     }
97 }