]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Linked up table resize handler (unfinished)
authorDan Brown <redacted>
Wed, 26 Jun 2024 12:52:00 +0000 (13:52 +0100)
committerDan Brown <redacted>
Wed, 26 Jun 2024 12:52:00 +0000 (13:52 +0100)
resources/js/wysiwyg/nodes/custom-table.ts
resources/js/wysiwyg/ui/framework/helpers/table-resizer.ts
resources/sass/_editor.scss

index c070e06b51d29be0d0e1fe091876fd1759c256b5..1107f0a906b5faad7bb28a02909c41935777a2c7 100644 (file)
@@ -1,5 +1,5 @@
 import {SerializedTableNode, TableNode, TableRowNode} from "@lexical/table";
-import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalNode, Spread} from "lexical";
+import {DOMConversion, DOMConversionMap, DOMConversionOutput, LexicalEditor, LexicalNode, Spread} from "lexical";
 import {EditorConfig} from "lexical/LexicalEditor";
 import {el} from "../helpers";
 
@@ -111,6 +111,21 @@ export class CustomTableNode extends TableNode {
 }
 
 function getTableColumnWidths(table: HTMLTableElement): string[] {
+    const maxColRow = getMaxColRowFromTable(table);
+
+    const colGroup = table.querySelector('colgroup');
+    let widths: string[] = [];
+    if (colGroup && (colGroup.childElementCount === maxColRow?.childElementCount || !maxColRow)) {
+        widths = extractWidthsFromRow(colGroup);
+    }
+    if (widths.filter(Boolean).length === 0 && maxColRow) {
+        widths = extractWidthsFromRow(maxColRow);
+    }
+
+    return widths;
+}
+
+function getMaxColRowFromTable(table: HTMLTableElement): HTMLTableRowElement|null {
     const rows = table.querySelectorAll('tr');
     let maxColCount: number = 0;
     let maxColRow: HTMLTableRowElement|null = null;
@@ -122,16 +137,7 @@ function getTableColumnWidths(table: HTMLTableElement): string[] {
         }
     }
 
-    const colGroup = table.querySelector('colgroup');
-    let widths: string[] = [];
-    if (colGroup && colGroup.childElementCount === maxColCount) {
-        widths = extractWidthsFromRow(colGroup);
-    }
-    if (widths.filter(Boolean).length === 0 && maxColRow) {
-        widths = extractWidthsFromRow(maxColRow);
-    }
-
-    return widths;
+    return maxColRow;
 }
 
 function extractWidthsFromRow(row: HTMLTableRowElement|HTMLTableColElement) {
@@ -140,7 +146,7 @@ function extractWidthsFromRow(row: HTMLTableRowElement|HTMLTableColElement) {
 
 function extractWidthFromElement(element: HTMLElement): string {
     let width = element.style.width || element.getAttribute('width');
-    if (!Number.isNaN(Number(width))) {
+    if (width && !Number.isNaN(Number(width))) {
         width = width + 'px';
     }
 
@@ -176,5 +182,23 @@ export function $setTableColumnWidth(node: CustomTableNode, columnIndex: number,
 
     colWidths[columnIndex] = width + 'px';
     node.setColWidths(colWidths);
-    console.log('setting col widths', node, colWidths);
+}
+
+export function $getTableColumnWidth(editor: LexicalEditor, node: CustomTableNode, columnIndex: number): number {
+    const colWidths = node.getColWidths();
+    if (colWidths.length > columnIndex && colWidths[columnIndex].endsWith('px')) {
+        return Number(colWidths[columnIndex].replace('px', ''));
+    }
+
+    // Otherwise, get from table element
+    const table = editor.getElementByKey(node.__key) as HTMLTableElement|null;
+    if (table) {
+        const maxColRow = getMaxColRowFromTable(table);
+        if (maxColRow && maxColRow.children.length > columnIndex) {
+            const cell = maxColRow.children[columnIndex];
+            return cell.clientWidth;
+        }
+    }
+
+    return 0;
 }
\ No newline at end of file
index ccf269daa0fe0d6c0765d0fb4f99fc85bf4e2d95..869de8460d2fc37812cc9af2a99305bb61fbea16 100644 (file)
@@ -1,6 +1,7 @@
-import {LexicalEditor} from "lexical";
+import {$getNearestNodeFromDOMNode, LexicalEditor} from "lexical";
 import {el} from "../../../helpers";
 import {MouseDragTracker, MouseDragTrackerDistance} from "./mouse-drag-tracker";
+import {$getTableColumnWidth, $setTableColumnWidth, CustomTableNode} from "../../../nodes/custom-table";
 
 type MarkerDomRecord = {x: HTMLElement, y: HTMLElement};
 
@@ -9,6 +10,10 @@ class TableResizer {
     protected editArea: HTMLElement;
     protected markerDom: MarkerDomRecord|null = null;
     protected mouseTracker: MouseDragTracker|null = null;
+    protected dragging: boolean = false;
+    protected targetCell: HTMLElement|null = null;
+    protected xMarkerAtStart : boolean = false;
+    protected yMarkerAtStart : boolean = false;
 
     constructor(editor: LexicalEditor, editArea: HTMLElement) {
         this.editor = editor;
@@ -19,7 +24,7 @@ class TableResizer {
     setupListeners() {
         this.editArea.addEventListener('mousemove', event => {
             const cell = (event.target as HTMLElement).closest('td,th');
-            if (cell) {
+            if (cell && !this.dragging) {
                 this.onCellMouseMove(cell as HTMLElement, event);
             }
         });
@@ -29,8 +34,13 @@ class TableResizer {
         const rect = cell.getBoundingClientRect();
         const midX = rect.left + (rect.width / 2);
         const midY = rect.top + (rect.height / 2);
-        const xMarkerPos = event.clientX <= midX ? rect.left : rect.right;
-        const yMarkerPos = event.clientY <= midY ? rect.top : rect.bottom;
+
+        this.targetCell = cell;
+        this.xMarkerAtStart = event.clientX <= midX;
+        this.yMarkerAtStart = event.clientY <= midY;
+
+        const xMarkerPos = this.xMarkerAtStart ? rect.left : rect.right;
+        const yMarkerPos = this.yMarkerAtStart ? rect.top : rect.bottom;
         this.updateMarkersTo(cell, xMarkerPos, yMarkerPos);
     }
 
@@ -65,13 +75,68 @@ class TableResizer {
     }
 
     watchMarkerMouseDrags(wrapper: HTMLElement) {
+        const _this = this;
+        let markerStart: number = 0;
+        let markerProp: 'left' | 'top' = 'left';
+
         this.mouseTracker = new MouseDragTracker(wrapper, '.editor-table-marker', {
+            down(event: MouseEvent, marker: HTMLElement) {
+                marker.classList.add('active');
+                _this.dragging = true;
+
+                markerProp = marker.classList.contains('editor-table-marker-column') ? 'left' : 'top';
+                markerStart = Number(marker.style[markerProp].replace('px', ''));
+            },
+            move(event: MouseEvent, marker: HTMLElement, distance: MouseDragTrackerDistance) {
+                  marker.style[markerProp] = (markerStart + distance[markerProp === 'left' ? 'x' : 'y']) + 'px';
+            },
             up(event: MouseEvent, marker: HTMLElement, distance: MouseDragTrackerDistance) {
-                console.log('up', distance, marker);
-                // TODO - Update row/column for distance
+                marker.classList.remove('active');
+                marker.style.left = '0';
+                marker.style.top = '0';
+
+                _this.dragging = false;
+                console.log('up', distance, marker, markerProp, _this.targetCell);
+                const parentTable = _this.targetCell?.closest('table');
+
+                if (markerProp === 'left' && _this.targetCell && parentTable) {
+                    const cellIndex = _this.getTargetCellColumnIndex();
+                    _this.editor.update(() => {
+                        const table = $getNearestNodeFromDOMNode(parentTable);
+                        if (table instanceof CustomTableNode) {
+                            const originalWidth = $getTableColumnWidth(_this.editor, table, cellIndex);
+                            const newWidth = Math.max(originalWidth + distance.x, 10);
+                            $setTableColumnWidth(table, cellIndex, newWidth);
+                        }
+                    });
+                }
             }
         });
     }
+
+    getTargetCellColumnIndex(): number {
+        const cell = this.targetCell;
+        if (cell === null) {
+            return -1;
+        }
+
+        let index = 0;
+        const row = cell.parentElement;
+        for (const rowCell of row?.children || []) {
+            let size = Number(rowCell.getAttribute('colspan'));
+            if (Number.isNaN(size) || size < 1) {
+                size = 1;
+            }
+
+            index += size;
+
+            if (rowCell === cell) {
+                return index - 1;
+            }
+        }
+
+        return -1;
+    }
 }
 
 
index b5ee69d982095e980efc12a03e4d116c1de7863b..a4b0e632f285b073a526025332a0f5ad545f727c 100644 (file)
   }
 }
 
-.editor-table-marker-row,
-.editor-table-marker-column {
+.editor-table-marker {
   position: fixed;
   background-color: var(--editor-color-primary);
   z-index: 99;
   user-select: none;
   opacity: 0;
-  &:hover {
+  &:hover, &.active {
     opacity: 0.4;
   }
 }