export function $createCustomTableCellNode(
- headerState: TableCellHeaderState,
+ headerState: TableCellHeaderState = TableCellHeaderStates.NO_STATUS,
colSpan = 1,
width?: number,
): CustomTableCellNode {
## In progress
-//
+- Table Cut/Copy/Paste column
## Main Todo
- Support media src conversions (https://github.com/tinymce/tinymce/blob/release/6.6/modules/tinymce/src/plugins/media/main/ts/core/UrlPatterns.ts)
- Media resize support (like images)
- Table caption text support
-- Table Cut/Copy/Paste column
- Mac: Shortcut support via command.
## Secondary Todo
$getTableRowsFromSelection,
$mergeTableCellsInSelection
} from "../../../utils/tables";
-import {$isCustomTableRowNode, CustomTableRowNode} from "../../../nodes/custom-table-row";
-import {NodeClipboard} from "../../../services/node-clipboard";
+import {$isCustomTableRowNode} from "../../../nodes/custom-table-row";
+import {
+ $copySelectedRowsToClipboard,
+ $cutSelectedRowsToClipboard,
+ $pasteClipboardRowsBefore, $pasteRowsAfter, isRowClipboardEmpty
+} from "../../../utils/table-copy-paste";
const neverActive = (): boolean => false;
const cellNotSelected = (selection: BaseSelection|null) => !$selectionContainsNodeType(selection, $isCustomTableCellNode);
isDisabled: cellNotSelected,
};
-const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(CustomTableRowNode);
-
export const cutRow: EditorButtonDefinition = {
label: 'Cut row',
format: 'long',
action(context: EditorUiContext) {
context.editor.update(() => {
- const rows = $getTableRowsFromSelection($getSelection());
- rowClipboard.set(...rows);
- for (const row of rows) {
- row.remove();
+ try {
+ $cutSelectedRowsToClipboard();
+ } catch (e: any) {
+ context.error(e.toString());
}
});
},
format: 'long',
action(context: EditorUiContext) {
context.editor.getEditorState().read(() => {
- const rows = $getTableRowsFromSelection($getSelection());
- rowClipboard.set(...rows);
+ try {
+ $copySelectedRowsToClipboard();
+ } catch (e: any) {
+ context.error(e.toString());
+ }
});
},
isActive: neverActive,
format: 'long',
action(context: EditorUiContext) {
context.editor.update(() => {
- const rows = $getTableRowsFromSelection($getSelection());
- const lastRow = rows[rows.length - 1];
- if (lastRow) {
- for (const row of rowClipboard.get(context.editor)) {
- lastRow.insertBefore(row);
- }
+ try {
+ $pasteClipboardRowsBefore(context.editor);
+ } catch (e: any) {
+ context.error(e.toString());
}
});
},
isActive: neverActive,
- isDisabled: (selection) => cellNotSelected(selection) || rowClipboard.size() === 0,
+ isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
};
export const pasteRowAfter: EditorButtonDefinition = {
format: 'long',
action(context: EditorUiContext) {
context.editor.update(() => {
- const rows = $getTableRowsFromSelection($getSelection());
- const lastRow = rows[rows.length - 1];
- if (lastRow) {
- for (const row of rowClipboard.get(context.editor).reverse()) {
- lastRow.insertAfter(row);
- }
+ try {
+ $pasteRowsAfter(context.editor);
+ } catch (e: any) {
+ context.error(e.toString());
}
});
},
isActive: neverActive,
- isDisabled: (selection) => cellNotSelected(selection) || rowClipboard.size() === 0,
+ isDisabled: (selection) => cellNotSelected(selection) || isRowClipboardEmpty(),
};
export const cutColumn: EditorButtonDefinition = {
containerDOM: HTMLElement; // DOM element which contains all editor elements
scrollDOM: HTMLElement; // DOM element which is the main content scroll container
translate: (text: string) => string; // Translate function
+ error: (text: string) => void; // Error reporting function
manager: EditorUIManager; // UI Manager instance for this editor
options: Record<string, any>; // General user options which may be used by sub elements
};
editorDOM: element,
scrollDOM: scrollContainer,
manager,
- translate: (text: string): string => text,
+ translate: (text: string): string => text, // TODO - Implement
+ error(error: string): void {
+ window.$events.error(error); // TODO - Translate
+ },
options,
};
manager.setContext(context);
}
}
- get(editor: LexicalEditor): LexicalNode[] {
+ get(editor: LexicalEditor): T[] {
return this.store.map(json => unserializeNodeRecursive(editor, json)).filter((node) => {
return node !== null;
- });
+ }) as T[];
}
size(): number {
--- /dev/null
+import {NodeClipboard} from "./node-clipboard";
+import {CustomTableRowNode} from "../nodes/custom-table-row";
+import {$getTableFromSelection, $getTableRowsFromSelection} from "./tables";
+import {$getSelection, LexicalEditor} from "lexical";
+import {$createCustomTableCellNode, $isCustomTableCellNode} from "../nodes/custom-table-cell";
+import {CustomTableNode} from "../nodes/custom-table";
+import {TableMap} from "./table-map";
+
+const rowClipboard: NodeClipboard<CustomTableRowNode> = new NodeClipboard<CustomTableRowNode>(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 $pasteRowsAfter(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);
+ }
+ }
+}
\ No newline at end of file
return [...cells.values()];
}
-}
+}
\ No newline at end of file