diff --git a/resources/js/wysiwyg/nodes/custom-table-cell-node.ts b/resources/js/wysiwyg/nodes/custom-table-cell.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 31504374a..b73a21807 100644 --- a/resources/js/wysiwyg/nodes/custom-table-cell-node.ts +++ b/resources/js/wysiwyg/nodes/custom-table-cell.ts @@ -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, }, SerializedTableCellNode> export class CustomTableCellNode extends TableCellNode { - __styles: Map = 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 { + getStyles(): StyleMap { const self = this.getLatest(); return new Map(self.__styles); } - setStyles(styles: Map): 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(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(); - 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 index 000000000..effaaa50d --- /dev/null +++ b/resources/js/wysiwyg/nodes/custom-table-row.ts @@ -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, +}, 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 diff --git a/resources/js/wysiwyg/nodes/index.ts b/resources/js/wysiwyg/nodes/index.ts index 92f6d2336..81a0c1a0d 100644 --- a/resources/js/wysiwyg/nodes/index.ts +++ b/resources/js/wysiwyg/nodes/index.ts @@ -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 | ListNode, // Todo - Create custom CustomListItemNode, CustomTableNode, - TableRowNode, + CustomTableRowNode, CustomTableCellNode, ImageNode, HorizontalRuleNode, @@ -49,6 +50,12 @@ export function getNodesForPageEditor(): (KlassConstructor | 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 | } }, { - replace: ListItemNode, - with: (node: ListItemNode) => { - return new CustomListItemNode(node.__value, node.__checked); + replace: TableRowNode, + with(node: TableRowNode) { + return new CustomTableRowNode(); } }, { diff --git a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts index 69d811ce2..88ea56186 100644 --- a/resources/js/wysiwyg/ui/defaults/buttons/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/buttons/tables.ts @@ -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); } }); }, diff --git a/resources/js/wysiwyg/ui/defaults/forms/tables.ts b/resources/js/wysiwyg/ui/defaults/forms/tables.ts index 1d637b0ee..1c577b72a 100644 --- a/resources/js/wysiwyg/ui/defaults/forms/tables.ts +++ b/resources/js/wysiwyg/ui/defaults/forms/tables.ts @@ -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 index 000000000..8767a7998 --- /dev/null +++ b/resources/js/wysiwyg/utils/styles.ts @@ -0,0 +1,11 @@ + +export type StyleMap = Map; + +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 diff --git a/resources/js/wysiwyg/utils/table-map.ts b/resources/js/wysiwyg/utils/table-map.ts index 77c4eba45..2b7eba62c 100644 --- a/resources/js/wysiwyg/utils/table-map.ts +++ b/resources/js/wysiwyg/utils/table-map.ts @@ -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 { diff --git a/resources/js/wysiwyg/utils/tables.ts b/resources/js/wysiwyg/utils/tables.ts index d4ef80f7f..d92f56c82 100644 --- a/resources/js/wysiwyg/utils/tables.ts +++ b/resources/js/wysiwyg/utils/tables.ts @@ -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();