From fcc1c2968d09fee8491bdc1d239539a7f37b41c3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 6 Aug 2024 09:36:37 +0100 Subject: [PATCH] Lexical: Added table cell node import logic --- .../wysiwyg/nodes/custom-table-cell-node.ts | 144 +++++++++++++++++- resources/js/wysiwyg/todo.md | 1 - 2 files changed, 139 insertions(+), 6 deletions(-) diff --git a/resources/js/wysiwyg/nodes/custom-table-cell-node.ts b/resources/js/wysiwyg/nodes/custom-table-cell-node.ts index 693ef5f5b..31504374a 100644 --- a/resources/js/wysiwyg/nodes/custom-table-cell-node.ts +++ b/resources/js/wysiwyg/nodes/custom-table-cell-node.ts @@ -1,7 +1,24 @@ -import {EditorConfig} from "lexical/LexicalEditor"; -import {DOMExportOutput, LexicalEditor, LexicalNode, Spread} from "lexical"; +import { + $createParagraphNode, + $isElementNode, + $isLineBreakNode, + $isTextNode, + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + LexicalEditor, + LexicalNode, + Spread +} from "lexical"; -import {SerializedTableCellNode, TableCellHeaderStates, TableCellNode} from "@lexical/table"; +import { + $createTableCellNode, + $isTableCellNode, + SerializedTableCellNode, + TableCellHeaderStates, + TableCellNode +} from "@lexical/table"; import {TableCellHeaderState} from "@lexical/table/LexicalTableCellNode"; export type SerializedCustomTableCellNode = Spread<{ @@ -54,13 +71,24 @@ export class CustomTableCellNode extends TableCellNode { return element; } - // TODO - Import DOM - updateDOM(prevNode: CustomTableCellNode): boolean { return super.updateDOM(prevNode) || this.__styles !== prevNode.__styles; } + static importDOM(): DOMConversionMap | null { + return { + td: (node: Node) => ({ + conversion: $convertCustomTableCellNodeElement, + priority: 0, + }), + th: (node: Node) => ({ + conversion: $convertCustomTableCellNodeElement, + priority: 0, + }), + }; + } + exportDOM(editor: LexicalEditor): DOMExportOutput { const element = this.createDOM(editor._config); return { @@ -68,6 +96,18 @@ export class CustomTableCellNode extends TableCellNode { }; } + static importJSON(serializedNode: SerializedCustomTableCellNode): CustomTableCellNode { + const node = $createCustomTableCellNode( + serializedNode.headerState, + serializedNode.colSpan, + serializedNode.width, + ); + + node.setStyles(new Map(Object.entries(serializedNode.styles))); + + return node; + } + exportJSON(): SerializedCustomTableCellNode { return { ...super.exportJSON(), @@ -77,6 +117,100 @@ export class CustomTableCellNode extends TableCellNode { } } +function $convertCustomTableCellNodeElement(domNode: Node): DOMConversionOutput { + const output = $convertTableCellNodeElement(domNode); + + if (domNode instanceof HTMLElement && output.node instanceof CustomTableCellNode) { + const styleMap = new Map(); + const styleNames = Array.from(domNode.style); + for (const style of styleNames) { + styleMap.set(style, domNode.style.getPropertyValue(style)); + } + output.node.setStyles(styleMap); + } + + return output; +} + +/** + * Function taken from: + * https://github.com/facebook/lexical/blob/e1881a6e409e1541c10dd0b5378f3a38c9dc8c9e/packages/lexical-table/src/LexicalTableCellNode.ts#L289 + * Copyright (c) Meta Platforms, Inc. and affiliates. + * MIT LICENSE + * Modified since copy. + */ +export function $convertTableCellNodeElement( + domNode: Node, +): DOMConversionOutput { + const domNode_ = domNode as HTMLTableCellElement; + const nodeName = domNode.nodeName.toLowerCase(); + + let width: number | undefined = undefined; + + + const PIXEL_VALUE_REG_EXP = /^(\d+(?:\.\d+)?)px$/; + if (PIXEL_VALUE_REG_EXP.test(domNode_.style.width)) { + width = parseFloat(domNode_.style.width); + } + + const tableCellNode = $createTableCellNode( + nodeName === 'th' + ? TableCellHeaderStates.ROW + : TableCellHeaderStates.NO_STATUS, + domNode_.colSpan, + width, + ); + + tableCellNode.__rowSpan = domNode_.rowSpan; + + const style = domNode_.style; + const textDecoration = style.textDecoration.split(' '); + const hasBoldFontWeight = + style.fontWeight === '700' || style.fontWeight === 'bold'; + const hasLinethroughTextDecoration = textDecoration.includes('line-through'); + const hasItalicFontStyle = style.fontStyle === 'italic'; + const hasUnderlineTextDecoration = textDecoration.includes('underline'); + return { + after: (childLexicalNodes) => { + if (childLexicalNodes.length === 0) { + childLexicalNodes.push($createParagraphNode()); + } + return childLexicalNodes; + }, + forChild: (lexicalNode, parentLexicalNode) => { + if ($isTableCellNode(parentLexicalNode) && !$isElementNode(lexicalNode)) { + const paragraphNode = $createParagraphNode(); + if ( + $isLineBreakNode(lexicalNode) && + lexicalNode.getTextContent() === '\n' + ) { + return null; + } + if ($isTextNode(lexicalNode)) { + if (hasBoldFontWeight) { + lexicalNode.toggleFormat('bold'); + } + if (hasLinethroughTextDecoration) { + lexicalNode.toggleFormat('strikethrough'); + } + if (hasItalicFontStyle) { + lexicalNode.toggleFormat('italic'); + } + if (hasUnderlineTextDecoration) { + lexicalNode.toggleFormat('underline'); + } + } + paragraphNode.append(lexicalNode); + return paragraphNode; + } + + return lexicalNode; + }, + node: tableCellNode, + }; +} + + export function $createCustomTableCellNode( headerState: TableCellHeaderState, colSpan = 1, diff --git a/resources/js/wysiwyg/todo.md b/resources/js/wysiwyg/todo.md index 086ca1462..ef86bfe53 100644 --- a/resources/js/wysiwyg/todo.md +++ b/resources/js/wysiwyg/todo.md @@ -3,7 +3,6 @@ ## In progress - Table features - - CustomTableCellNode importDOM logic - Merge cell action - Row properties form logic - Table properties form logic