mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-12-13 22:43:39 +08:00
272 lines
8.7 KiB
TypeScript
272 lines
8.7 KiB
TypeScript
import {NodeClipboard} from "./node-clipboard";
|
|
import {CustomTableRowNode} from "../nodes/custom-table-row";
|
|
import {$getTableCellsFromSelection, $getTableFromSelection, $getTableRowsFromSelection} from "./tables";
|
|
import {$getSelection, BaseSelection, LexicalEditor} from "lexical";
|
|
import {$createCustomTableCellNode, $isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
|
|
import {CustomTableNode} from "../nodes/custom-table";
|
|
import {TableMap} from "./table-map";
|
|
import {$isTableSelection} from "@lexical/table";
|
|
import {$getNodeFromSelection} from "./selection";
|
|
|
|
const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>();
|
|
|
|
export function isRowClipboardEmpty(): boolean {
|
|
return rowClipboard.size() === 0;
|
|
}
|
|
|
|
export function validateRowsToCopy(rows: CustomTableRowNode[]): void {
|
|
let commonRowSize: number|null = null;
|
|
|
|
for (const row of rows) {
|
|
const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
|
|
let rowSize = 0;
|
|
for (const cell of cells) {
|
|
rowSize += cell.getColSpan() || 1;
|
|
if (cell.getRowSpan() > 1) {
|
|
throw Error('Cannot copy rows with merged cells');
|
|
}
|
|
}
|
|
|
|
if (commonRowSize === null) {
|
|
commonRowSize = rowSize;
|
|
} else if (commonRowSize !== rowSize) {
|
|
throw Error('Cannot copy rows with inconsistent sizes');
|
|
}
|
|
}
|
|
}
|
|
|
|
export function validateRowsToPaste(rows: CustomTableRowNode[], targetTable: CustomTableNode): void {
|
|
const tableColCount = (new TableMap(targetTable)).columnCount;
|
|
for (const row of rows) {
|
|
const cells = row.getChildren().filter(n => $isCustomTableCellNode(n));
|
|
let rowSize = 0;
|
|
for (const cell of cells) {
|
|
rowSize += cell.getColSpan() || 1;
|
|
}
|
|
|
|
if (rowSize > tableColCount) {
|
|
throw Error('Cannot paste rows that are wider than target table');
|
|
}
|
|
|
|
while (rowSize < tableColCount) {
|
|
row.append($createCustomTableCellNode());
|
|
rowSize++;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function $cutSelectedRowsToClipboard(): void {
|
|
const rows = $getTableRowsFromSelection($getSelection());
|
|
validateRowsToCopy(rows);
|
|
rowClipboard.set(...rows);
|
|
for (const row of rows) {
|
|
row.remove();
|
|
}
|
|
}
|
|
|
|
export function $copySelectedRowsToClipboard(): void {
|
|
const rows = $getTableRowsFromSelection($getSelection());
|
|
validateRowsToCopy(rows);
|
|
rowClipboard.set(...rows);
|
|
}
|
|
|
|
export function $pasteClipboardRowsBefore(editor: LexicalEditor): void {
|
|
const selection = $getSelection();
|
|
const rows = $getTableRowsFromSelection(selection);
|
|
const table = $getTableFromSelection(selection);
|
|
const lastRow = rows[rows.length - 1];
|
|
if (lastRow && table) {
|
|
const clipboardRows = rowClipboard.get(editor);
|
|
validateRowsToPaste(clipboardRows, table);
|
|
for (const row of clipboardRows) {
|
|
lastRow.insertBefore(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function $pasteClipboardRowsAfter(editor: LexicalEditor): void {
|
|
const selection = $getSelection();
|
|
const rows = $getTableRowsFromSelection(selection);
|
|
const table = $getTableFromSelection(selection);
|
|
const lastRow = rows[rows.length - 1];
|
|
if (lastRow && table) {
|
|
const clipboardRows = rowClipboard.get(editor).reverse();
|
|
validateRowsToPaste(clipboardRows, table);
|
|
for (const row of clipboardRows) {
|
|
lastRow.insertAfter(row);
|
|
}
|
|
}
|
|
}
|
|
|
|
const columnClipboard: NodeClipboard<CustomTableCellNode>[] = [];
|
|
|
|
function setColumnClipboard(columns: CustomTableCellNode[][]): void {
|
|
const newClipboards = columns.map(cells => {
|
|
const clipboard = new NodeClipboard<CustomTableCellNode>();
|
|
clipboard.set(...cells);
|
|
return clipboard;
|
|
});
|
|
|
|
columnClipboard.splice(0, columnClipboard.length, ...newClipboards);
|
|
}
|
|
|
|
type TableRange = {from: number, to: number};
|
|
|
|
export function isColumnClipboardEmpty(): boolean {
|
|
return columnClipboard.length === 0;
|
|
}
|
|
|
|
function $getSelectionColumnRange(selection: BaseSelection|null): TableRange|null {
|
|
if ($isTableSelection(selection)) {
|
|
const shape = selection.getShape()
|
|
return {from: shape.fromX, to: shape.toX};
|
|
}
|
|
|
|
const cell = $getNodeFromSelection(selection, $isCustomTableCellNode);
|
|
const table = $getTableFromSelection(selection);
|
|
if (!$isCustomTableCellNode(cell) || !table) {
|
|
return null;
|
|
}
|
|
|
|
const map = new TableMap(table);
|
|
const range = map.getRangeForCell(cell);
|
|
if (!range) {
|
|
return null;
|
|
}
|
|
|
|
return {from: range.fromX, to: range.toX};
|
|
}
|
|
|
|
function $getTableColumnCellsFromSelection(range: TableRange, table: CustomTableNode): CustomTableCellNode[][] {
|
|
const map = new TableMap(table);
|
|
const columns = [];
|
|
for (let x = range.from; x <= range.to; x++) {
|
|
const cells = map.getCellsInColumn(x);
|
|
columns.push(cells);
|
|
}
|
|
|
|
return columns;
|
|
}
|
|
|
|
function validateColumnsToCopy(columns: CustomTableCellNode[][]): void {
|
|
let commonColSize: number|null = null;
|
|
|
|
for (const cells of columns) {
|
|
let colSize = 0;
|
|
for (const cell of cells) {
|
|
colSize += cell.getRowSpan() || 1;
|
|
if (cell.getColSpan() > 1) {
|
|
throw Error('Cannot copy columns with merged cells');
|
|
}
|
|
}
|
|
|
|
if (commonColSize === null) {
|
|
commonColSize = colSize;
|
|
} else if (commonColSize !== colSize) {
|
|
throw Error('Cannot copy columns with inconsistent sizes');
|
|
}
|
|
}
|
|
}
|
|
|
|
export function $cutSelectedColumnsToClipboard(): void {
|
|
const selection = $getSelection();
|
|
const range = $getSelectionColumnRange(selection);
|
|
const table = $getTableFromSelection(selection);
|
|
if (!range || !table) {
|
|
return;
|
|
}
|
|
|
|
const colWidths = table.getColWidths();
|
|
const columns = $getTableColumnCellsFromSelection(range, table);
|
|
validateColumnsToCopy(columns);
|
|
setColumnClipboard(columns);
|
|
for (const cells of columns) {
|
|
for (const cell of cells) {
|
|
cell.remove();
|
|
}
|
|
}
|
|
|
|
const newWidths = [...colWidths].splice(range.from, (range.to - range.from) + 1);
|
|
table.setColWidths(newWidths);
|
|
}
|
|
|
|
export function $copySelectedColumnsToClipboard(): void {
|
|
const selection = $getSelection();
|
|
const range = $getSelectionColumnRange(selection);
|
|
const table = $getTableFromSelection(selection);
|
|
if (!range || !table) {
|
|
return;
|
|
}
|
|
|
|
const columns = $getTableColumnCellsFromSelection(range, table);
|
|
validateColumnsToCopy(columns);
|
|
setColumnClipboard(columns);
|
|
}
|
|
|
|
function validateColumnsToPaste(columns: CustomTableCellNode[][], targetTable: CustomTableNode) {
|
|
const tableRowCount = (new TableMap(targetTable)).rowCount;
|
|
for (const cells of columns) {
|
|
let colSize = 0;
|
|
for (const cell of cells) {
|
|
colSize += cell.getRowSpan() || 1;
|
|
}
|
|
|
|
if (colSize > tableRowCount) {
|
|
throw Error('Cannot paste columns that are taller than target table');
|
|
}
|
|
|
|
while (colSize < tableRowCount) {
|
|
cells.push($createCustomTableCellNode());
|
|
colSize++;
|
|
}
|
|
}
|
|
}
|
|
|
|
function $pasteClipboardColumns(editor: LexicalEditor, isBefore: boolean): void {
|
|
const selection = $getSelection();
|
|
const table = $getTableFromSelection(selection);
|
|
const cells = $getTableCellsFromSelection(selection);
|
|
const referenceCell = cells[isBefore ? 0 : cells.length - 1];
|
|
if (!table || !referenceCell) {
|
|
return;
|
|
}
|
|
|
|
const clipboardCols = columnClipboard.map(cb => cb.get(editor));
|
|
if (!isBefore) {
|
|
clipboardCols.reverse();
|
|
}
|
|
|
|
validateColumnsToPaste(clipboardCols, table);
|
|
const map = new TableMap(table);
|
|
const cellRange = map.getRangeForCell(referenceCell);
|
|
if (!cellRange) {
|
|
return;
|
|
}
|
|
|
|
const colIndex = isBefore ? cellRange.fromX : cellRange.toX;
|
|
const colWidths = table.getColWidths();
|
|
|
|
for (let y = 0; y < map.rowCount; y++) {
|
|
const relCell = map.getCellAtPosition(colIndex, y);
|
|
for (const cells of clipboardCols) {
|
|
const newCell = cells[y];
|
|
if (isBefore) {
|
|
relCell.insertBefore(newCell);
|
|
} else {
|
|
relCell.insertAfter(newCell);
|
|
}
|
|
}
|
|
}
|
|
|
|
const refWidth = colWidths[colIndex];
|
|
const addedWidths = clipboardCols.map(_ => refWidth);
|
|
colWidths.splice(isBefore ? colIndex : colIndex + 1, 0, ...addedWidths);
|
|
}
|
|
|
|
export function $pasteClipboardColumnsBefore(editor: LexicalEditor): void {
|
|
$pasteClipboardColumns(editor, true);
|
|
}
|
|
|
|
export function $pasteClipboardColumnsAfter(editor: LexicalEditor): void {
|
|
$pasteClipboardColumns(editor, false);
|
|
} |