]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Added cell width fetching, Created custom row node
authorDan Brown <redacted>
Fri, 9 Aug 2024 10:24:25 +0000 (11:24 +0100)
committerDan Brown <redacted>
Fri, 9 Aug 2024 10:24:25 +0000 (11:24 +0100)
resources/js/wysiwyg/nodes/custom-table-cell.ts [moved from resources/js/wysiwyg/nodes/custom-table-cell-node.ts with 92% similarity]
resources/js/wysiwyg/nodes/custom-table-row.ts [new file with mode: 0644]
resources/js/wysiwyg/nodes/index.ts
resources/js/wysiwyg/ui/defaults/buttons/tables.ts
resources/js/wysiwyg/ui/defaults/forms/tables.ts
resources/js/wysiwyg/utils/styles.ts [new file with mode: 0644]
resources/js/wysiwyg/utils/table-map.ts
resources/js/wysiwyg/utils/tables.ts

similarity index 92%
rename from resources/js/wysiwyg/nodes/custom-table-cell-node.ts
rename to resources/js/wysiwyg/nodes/custom-table-cell.ts
index 31504374ad01a4e7cd83266b5f24fa14b3fe3062..b73a2180716420723b125c639fd7e9e2a49212c5 100644 (file)
@@ -20,13 +20,14 @@ import {
     TableCellNode
 } from "@lexical/table";
 import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode";
+import {createStyleMapFromDomStyles, StyleMap} from "../utils/styles";
 
 export type SerializedCustomTableCellNode = Spread<{
     styles: Record<string, string>,
 }, SerializedTableCellNode>
 
 export class CustomTableCellNode extends TableCellNode {
-    __styles: Map<string, string> = new Map;
+    __styles: StyleMap = new Map;
 
     static getType(): string {
         return 'custom-table-cell';
@@ -44,12 +45,12 @@ export class CustomTableCellNode extends TableCellNode {
         return cellNode;
     }
 
-    getStyles(): Map<string, string> {
+    getStyles(): StyleMap {
         const self = this.getLatest();
         return new Map(self.__styles);
     }
 
-    setStyles(styles: Map<string, string>): void {
+    setStyles(styles: StyleMap): void {
         const self = this.getWritable();
         self.__styles = new Map(styles);
     }
@@ -103,7 +104,7 @@ export class CustomTableCellNode extends TableCellNode {
             serializedNode.width,
         );
 
-        node.setStyles(new Map<string, string>(Object.entries(serializedNode.styles)));
+        node.setStyles(new Map(Object.entries(serializedNode.styles)));
 
         return node;
     }
@@ -121,12 +122,7 @@ function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput
     const output =  $convertTableCellNodeElement(domNode);
 
     if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) {
-        const styleMap = new Map<string, string>();
-        const styleNames = Array.from(domNode.style);
-        for (const style of styleNames) {
-            styleMap.set(style, domNode.style.getPropertyValue(style));
-        }
-        output.node.setStyles(styleMap);
+        output.node.setStyles(createStyleMapFromDomStyles(domNode.style));
     }
 
     return output;
diff --git a/resources/js/wysiwyg/nodes/custom-table-row.ts b/resources/js/wysiwyg/nodes/custom-table-row.ts
new file mode 100644 (file)
index 0000000..effaaa5
--- /dev/null
@@ -0,0 +1,113 @@
+import {
+    $createParagraphNode,
+    $isElementNode,
+    $isLineBreakNode,
+    $isTextNode,
+    DOMConversionMap,
+    DOMConversionOutput,
+    EditorConfig,
+    LexicalNode,
+    Spread
+} from "lexical";
+
+import {
+    $createTableCellNode,
+    $isTableCellNode,
+    SerializedTableRowNode,
+    TableCellHeaderStates,
+    TableRowNode
+} from "@lexical/table";
+import {createStyleMapFromDomStyles, StyleMap} from "../utils/styles";
+import {NodeKey} from "lexical/LexicalNode";
+
+export type SerializedCustomTableRowNode = Spread<{
+    styles: Record<string, string>,
+}, SerializedTableRowNode>
+
+export class CustomTableRowNode extends TableRowNode {
+    __styles: StyleMap = new Map();
+
+    constructor(key?: NodeKey) {
+        super(0, key);
+    }
+
+    static getType(): string {
+        return 'custom-table-row';
+    }
+
+    static clone(node: CustomTableRowNode): CustomTableRowNode {
+        const cellNode = new CustomTableRowNode(node.__key);
+
+        cellNode.__styles = new Map(node.__styles);
+        return cellNode;
+    }
+
+    getStyles(): StyleMap {
+        const self = this.getLatest();
+        return new Map(self.__styles);
+    }
+
+    setStyles(styles: StyleMap): void {
+        const self = this.getWritable();
+        self.__styles = new Map(styles);
+    }
+
+    createDOM(config: EditorConfig): HTMLElement {
+        const element = super.createDOM(config);
+
+        for (const [name, value] of this.__styles.entries()) {
+            element.style.setProperty(name, value);
+        }
+
+        return element;
+    }
+
+    updateDOM(prevNode: CustomTableRowNode): boolean {
+        return super.updateDOM(prevNode)
+            || this.__styles !== prevNode.__styles;
+    }
+
+    static importDOM(): DOMConversionMap | null {
+        return {
+            tr: (node: Node) => ({
+                conversion: $convertTableRowElement,
+                priority: 0,
+            }),
+        };
+    }
+
+    static importJSON(serializedNode: SerializedCustomTableRowNode): CustomTableRowNode {
+        const node = $createCustomTableRowNode();
+
+        node.setStyles(new Map(Object.entries(serializedNode.styles)));
+
+        return node;
+    }
+
+    exportJSON(): SerializedCustomTableRowNode {
+        return {
+            ...super.exportJSON(),
+            height: 0,
+            type: 'custom-table-row',
+            styles: Object.fromEntries(this.__styles),
+        };
+    }
+}
+
+export function $convertTableRowElement(domNode: Node): DOMConversionOutput {
+    const rowNode = $createCustomTableRowNode();
+
+    if (domNode instanceof HTMLElement) {
+        rowNode.setStyles(createStyleMapFromDomStyles(domNode.style));
+    }
+
+    return {node: rowNode};
+}
+
+export function $createCustomTableRowNode(): CustomTableRowNode {
+    return new CustomTableRowNode();
+}
+
+export function $isCustomTableRowNode(node: LexicalNode | null | undefined): node is CustomTableRowNode {
+    return node instanceof CustomTableRowNode;
+}
\ No newline at end of file
index 92f6d2336cf0b8e545bc99b10f854050e5cb0abe..81a0c1a0d8e8e1dbd19f9312b8f9e4f40402539e 100644 (file)
@@ -20,7 +20,8 @@ import {DiagramNode} from "./diagram";
 import {EditorUiContext} from "../ui/framework/core";
 import {MediaNode} from "./media";
 import {CustomListItemNode} from "./custom-list-item";
-import {CustomTableCellNode} from "./custom-table-cell-node";
+import {CustomTableCellNode} from "./custom-table-cell";
+import {CustomTableRowNode} from "./custom-table-row";
 
 /**
  * Load the nodes for lexical.
@@ -33,7 +34,7 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
         ListNode, // Todo - Create custom
         CustomListItemNode,
         CustomTableNode,
-        TableRowNode,
+        CustomTableRowNode,
         CustomTableCellNode,
         ImageNode,
         HorizontalRuleNode,
@@ -49,6 +50,12 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
                 return new CustomParagraphNode();
             }
         },
+        {
+            replace: ListItemNode,
+            with: (node: ListItemNode) => {
+                return new CustomListItemNode(node.__value, node.__checked);
+            }
+        },
         {
             replace: TableNode,
             with(node: TableNode) {
@@ -56,9 +63,9 @@ export function getNodesForPageEditor(): (KlassConstructor<typeof LexicalNode> |
             }
         },
         {
-            replace: ListItemNode,
-            with: (node: ListItemNode) => {
-                return new CustomListItemNode(node.__value, node.__checked);
+            replace: TableRowNode,
+            with(node: TableRowNode) {
+                return new CustomTableRowNode();
             }
         },
         {
index 69d811ce2475531e4b4539d11f09448418dc1ffd..88ea56186ad39f396e5705af935eed722489cb0c 100644 (file)
@@ -19,8 +19,8 @@ import {
 } from "@lexical/table";
 import {$getNodeFromSelection, $selectionContainsNodeType} from "../../../utils/selection";
 import {$getParentOfType} from "../../../utils/nodes";
-import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell-node";
-import {showCellPropertiesForm} from "../forms/tables";
+import {$isCustomTableCellNode} from "../../../nodes/custom-table-cell";
+import {$showCellPropertiesForm} from "../forms/tables";
 import {$mergeTableCellsInSelection} from "../../../utils/tables";
 
 const neverActive = (): boolean => false;
@@ -317,7 +317,7 @@ export const cellProperties: EditorButtonDefinition = {
         context.editor.getEditorState().read(() => {
             const cell = $getNodeFromSelection($getSelection(), $isCustomTableCellNode);
             if ($isCustomTableCellNode(cell)) {
-                showCellPropertiesForm(cell, context);
+                $showCellPropertiesForm(cell, context);
             }
         });
     },
index 1d637b0eea7e7928f4d34d01a597381473ebf717..1c577b72a5094c23aef718d6cb62b40aabe3806b 100644 (file)
@@ -5,10 +5,10 @@ import {
     EditorSelectFormFieldDefinition
 } from "../../framework/forms";
 import {EditorUiContext} from "../../framework/core";
-import {CustomTableCellNode} from "../../../nodes/custom-table-cell-node";
+import {CustomTableCellNode} from "../../../nodes/custom-table-cell";
 import {EditorFormModal} from "../../framework/modals";
 import {$getSelection, ElementFormatType} from "lexical";
-import {$getTableCellsFromSelection, $setTableCellColumnWidth} from "../../../utils/tables";
+import {$getTableCellColumnWidth, $getTableCellsFromSelection, $setTableCellColumnWidth} from "../../../utils/tables";
 import {formatSizeValue} from "../../../utils/dom";
 
 const borderStyleInput: EditorSelectFormFieldDefinition = {
@@ -54,11 +54,11 @@ const alignmentInput: EditorSelectFormFieldDefinition = {
     }
 };
 
-export function showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
+export function $showCellPropertiesForm(cell: CustomTableCellNode, context: EditorUiContext): EditorFormModal {
     const styles = cell.getStyles();
     const modalForm = context.manager.createModal('cell_properties');
     modalForm.show({
-        width: '', // TODO
+        width: $getTableCellColumnWidth(context.editor, cell),
         height: styles.get('height') || '',
         type: cell.getTag(),
         h_align: cell.getFormatType(),
@@ -171,45 +171,18 @@ export const rowProperties: EditorFormDefinition = {
         return true;
     },
     fields: [
+        // Removed fields:
+        // Removed 'Row Type' as we don't currently support thead/tfoot elements
+        //  TinyMCE would move rows up/down into these parents when set
+        // Removed 'Alignment' since this was broken in our editor (applied alignment class to whole parent table)
         {
-            build() {
-                const generalFields: EditorFormFieldDefinition[] = [
-                    {
-                        label: 'Row type',
-                        name: 'type',
-                        type: 'select',
-                        valuesByLabel: {
-                            'Body': 'body',
-                            'Header': 'header',
-                            'Footer': 'footer',
-                        }
-                    } as EditorSelectFormFieldDefinition,
-                    alignmentInput,
-                    {
-                        label: 'Height',
-                        name: 'height',
-                        type: 'text',
-                    },
-                ];
-
-                const advancedFields: EditorFormFieldDefinition[] = [
-                    borderStyleInput,
-                    borderColorInput,
-                    backgroundColorInput,
-                ];
-
-                return new EditorFormTabs([
-                    {
-                        label: 'General',
-                        contents: generalFields,
-                    },
-                    {
-                        label: 'Advanced',
-                        contents: advancedFields,
-                    }
-                ])
-            }
+            label: 'Height', // style on tr: height
+            name: 'height',
+            type: 'text',
         },
+        borderStyleInput, // style on tr: height
+        borderColorInput, // style on tr: height
+        backgroundColorInput, // style on tr: height
     ],
 };
 export const tableProperties: EditorFormDefinition = {
diff --git a/resources/js/wysiwyg/utils/styles.ts b/resources/js/wysiwyg/utils/styles.ts
new file mode 100644 (file)
index 0000000..8767a79
--- /dev/null
@@ -0,0 +1,11 @@
+
+export type StyleMap = Map<string, string>;
+
+export function createStyleMapFromDomStyles(domStyles: CSSStyleDeclaration): StyleMap {
+    const styleMap: StyleMap = new Map();
+    const styleNames: string[] = Array.from(domStyles);
+    for (const style of styleNames) {
+        styleMap.set(style, domStyles.getPropertyValue(style));
+    }
+    return styleMap;
+}
\ No newline at end of file
index 77c4eba45dcf44d073a1cf461594c69ca54f10e2..2b7eba62c035056635984f187d5f53532d818683 100644 (file)
@@ -1,5 +1,5 @@
 import {CustomTableNode} from "../nodes/custom-table";
-import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell-node";
+import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
 import {$isTableRowNode} from "@lexical/table";
 
 export class TableMap {
index d4ef80f7fa1f055b6ad651b425fc180c0d7a30d1..d92f56c8205daee69e884fa7e75cbae0d174cd9e 100644 (file)
@@ -1,7 +1,7 @@
 import {BaseSelection, LexicalEditor} from "lexical";
 import {$isTableRowNode, $isTableSelection, TableRowNode, TableSelection, TableSelectionShape} from "@lexical/table";
 import {$isCustomTableNode, CustomTableNode} from "../nodes/custom-table";
-import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell-node";
+import {$isCustomTableCellNode, CustomTableCellNode} from "../nodes/custom-table-cell";
 import {$getParentOfType} from "./nodes";
 import {$getNodeFromSelection} from "./selection";
 import {formatSizeValue} from "./dom";
@@ -124,6 +124,17 @@ export function $setTableCellColumnWidth(cell: CustomTableCellNode, width: strin
     }
 }
 
+export function $getTableCellColumnWidth(editor: LexicalEditor, cell: CustomTableCellNode): string {
+    const table = $getTableFromCell(cell)
+    const index = $getCellColumnIndex(cell);
+    if (!table) {
+        return '';
+    }
+
+    const widths = table.getColWidths();
+    return (widths.length > index) ? widths[index] : '';
+}
+
 export function $getTableCellsFromSelection(selection: BaseSelection|null): CustomTableCellNode[]  {
     if ($isTableSelection(selection)) {
         const nodes = selection.getNodes();