diff --git a/resources/js/wysiwyg/lexical/core/LexicalNode.ts b/resources/js/wysiwyg/lexical/core/LexicalNode.ts index a6c9b6023..163bb8c31 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalNode.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalNode.ts @@ -1165,6 +1165,16 @@ export class LexicalNode { markDirty(): void { this.getWritable(); } + + /** + * Insert the DOM of this node into that of the parent. + * Allows this node to implement custom DOM attachment logic. + * Boolean result indicates if the insertion was handled by the function. + * A true return value prevents default insertion logic from taking place. + */ + insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean { + return false; + } } function errorOnTypeKlassMismatch( diff --git a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts index fccf1ae23..297e96ce0 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalReconciler.ts @@ -171,16 +171,21 @@ function $createNode( } if (parentDOM !== null) { - if (insertDOM != null) { - parentDOM.insertBefore(dom, insertDOM); - } else { - // @ts-expect-error: internal field - const possibleLineBreak = parentDOM.__lexicalLineBreak; - if (possibleLineBreak != null) { - parentDOM.insertBefore(dom, possibleLineBreak); + const inserted = node?.insertDOMIntoParent(dom, parentDOM); + + if (!inserted) { + if (insertDOM != null) { + parentDOM.insertBefore(dom, insertDOM); } else { - parentDOM.appendChild(dom); + // @ts-expect-error: internal field + const possibleLineBreak = parentDOM.__lexicalLineBreak; + + if (possibleLineBreak != null) { + parentDOM.insertBefore(dom, possibleLineBreak); + } else { + parentDOM.appendChild(dom); + } } } } diff --git a/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts b/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts new file mode 100644 index 000000000..08c6870e6 --- /dev/null +++ b/resources/js/wysiwyg/lexical/table/LexicalCaptionNode.ts @@ -0,0 +1,74 @@ +import { + DOMConversionMap, + DOMExportOutput, + EditorConfig, + ElementNode, + LexicalEditor, + LexicalNode, + SerializedElementNode +} from "lexical"; + + +export class CaptionNode extends ElementNode { + static getType(): string { + return 'caption'; + } + + static clone(node: CaptionNode): CaptionNode { + return new CaptionNode(node.__key); + } + + createDOM(_config: EditorConfig, _editor: LexicalEditor): HTMLElement { + return document.createElement('caption'); + } + + updateDOM(_prevNode: unknown, _dom: HTMLElement, _config: EditorConfig): boolean { + return false; + } + + isParentRequired(): true { + return true; + } + + canBeEmpty(): boolean { + return false; + } + + exportJSON(): SerializedElementNode { + return { + ...super.exportJSON(), + type: 'caption', + version: 1, + }; + } + + insertDOMIntoParent(nodeDOM: HTMLElement, parentDOM: HTMLElement): boolean { + parentDOM.insertBefore(nodeDOM, parentDOM.firstChild); + return true; + } + + static importJSON(serializedNode: SerializedElementNode): CaptionNode { + return $createCaptionNode(); + } + + static importDOM(): DOMConversionMap | null { + return { + caption: (node: Node) => ({ + conversion(domNode: Node) { + return { + node: $createCaptionNode(), + } + }, + priority: 0, + }), + }; + } +} + +export function $createCaptionNode(): CaptionNode { + return new CaptionNode(); +} + +export function $isCaptionNode(node: LexicalNode | null | undefined): node is CaptionNode { + return node instanceof CaptionNode; +} \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts index 9443747a6..a10361475 100644 --- a/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts +++ b/resources/js/wysiwyg/lexical/table/LexicalTableNode.ts @@ -139,6 +139,8 @@ export class TableNode extends CommonBlockNode { for (const child of Array.from(tableElement.children)) { if (child.nodeName === 'TR') { tBody.append(child); + } else if (child.nodeName === 'CAPTION') { + newElement.insertBefore(child, newElement.firstChild); } else { newElement.append(child); } diff --git a/resources/js/wysiwyg/nodes.ts b/resources/js/wysiwyg/nodes.ts index 8a47f322d..c1db0f086 100644 --- a/resources/js/wysiwyg/nodes.ts +++ b/resources/js/wysiwyg/nodes.ts @@ -18,6 +18,7 @@ import {EditorUiContext} from "./ui/framework/core"; import {MediaNode} from "@lexical/rich-text/LexicalMediaNode"; import {HeadingNode} from "@lexical/rich-text/LexicalHeadingNode"; import {QuoteNode} from "@lexical/rich-text/LexicalQuoteNode"; +import {CaptionNode} from "@lexical/table/LexicalCaptionNode"; /** * Load the nodes for lexical. @@ -32,6 +33,7 @@ export function getNodesForPageEditor(): (KlassConstructor | TableNode, TableRowNode, TableCellNode, + CaptionNode, ImageNode, // TODO - Alignment HorizontalRuleNode, DetailsNode,