diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index 4af2e17af..482f6d65f 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -41,6 +41,7 @@ import {ImageNode} from "@lexical/rich-text/LexicalImageNode"; import {MediaNode} from "@lexical/rich-text/LexicalMediaNode"; import {DiagramNode} from "@lexical/rich-text/LexicalDiagramNode"; import {DiagramDecorator} from "../../../../ui/decorators/DiagramDecorator"; +import {$generateHtmlFromNodes} from "@lexical/html"; type TestEnv = { readonly container: HTMLDivElement; @@ -768,7 +769,14 @@ export function html( } export function expectHtmlToBeEqual(expected: string, actual: string): void { - expect(formatHtml(expected)).toBe(formatHtml(actual)); + expect(formatHtml(actual)).toBe(formatHtml(expected)); +} + +export function expectEditorHtmlToBeEqual(editor: LexicalEditor, expected: string): void { + const html = editor.read(() => { + return $generateHtmlFromNodes(editor, null); + }); + expect(formatHtml(html)).toBe(formatHtml(expected)); } type nodeTextShape = { diff --git a/resources/js/wysiwyg/lexical/core/nodes/LexicalLineBreakNode.ts b/resources/js/wysiwyg/lexical/core/nodes/LexicalLineBreakNode.ts index b1746e7f8..cef982e5f 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/LexicalLineBreakNode.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/LexicalLineBreakNode.ts @@ -6,10 +6,10 @@ * */ -import type {KlassConstructor} from '../LexicalEditor'; +import {KlassConstructor, LexicalEditor} from '../LexicalEditor'; import type { DOMConversionMap, - DOMConversionOutput, + DOMConversionOutput, DOMExportOutput, NodeKey, SerializedLexicalNode, } from '../LexicalNode'; @@ -43,6 +43,25 @@ export class LineBreakNode extends LexicalNode { return document.createElement('br'); } + exportDOM(editor: LexicalEditor): DOMExportOutput { + const output = super.exportDOM(editor); + const parent = this.getParent(); + + // We insert an extra line break when the line break is the last child of a block node + // so that a line break is properly represented as an empty line, which mirrors the in-editor + // behaviour, where it's handled at the reconciliation phase + if (parent && !parent.isInline() && parent.getLastChild()?.getKey() === this.getKey()) { + output.after = (el) => { + if (el instanceof HTMLElement) { + el.before(document.createElement('br'), el); + } + return null; + }; + } + + return output; + } + updateDOM(): false { return false; } diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalLineBreakNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalLineBreakNode.test.ts index 110086ac8..8e5b04ef1 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalLineBreakNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalLineBreakNode.test.ts @@ -6,9 +6,22 @@ * */ -import {$createLineBreakNode, $isLineBreakNode} from 'lexical'; +import { + $createLineBreakNode, + $createParagraphNode, + $createTextNode, + $getRoot, + $isLineBreakNode, + ParagraphNode +} from 'lexical'; -import {initializeUnitTest} from '../../../__tests__/utils'; +import { + createTestContext, + dispatchKeydownEventForSelectedNode, expectEditorHtmlToBeEqual, + expectNodeShapeToMatch, + initializeUnitTest +} from '../../../__tests__/utils'; +import {registerRichText} from "@lexical/rich-text"; describe('LexicalLineBreakNode tests', () => { initializeUnitTest((testEnv) => { @@ -71,4 +84,38 @@ describe('LexicalLineBreakNode tests', () => { }); }); }); + + describe('LineBreakNode.exportDOM()', () => { + test('first linebreaks are represented as a double break on export', () => { + const {editor} = createTestContext(); + registerRichText(editor); + + let textNode; + let paragraph!: ParagraphNode; + + editor.updateAndCommit(() => { + paragraph = $createParagraphNode(); + textNode = $createTextNode('hello'); + paragraph.append(textNode); + + $getRoot().append(paragraph); + textNode.selectEnd(); + }); + + dispatchKeydownEventForSelectedNode(editor, 'Enter', {shiftKey: true}); + + expectNodeShapeToMatch(editor, [ + {type: 'paragraph', children: [ + {text: 'hello'}, + {type: 'linebreak'}, + ]} + ]); + expectEditorHtmlToBeEqual(editor, `

hello

`); + + dispatchKeydownEventForSelectedNode(editor, 'Enter', {shiftKey: true}); + dispatchKeydownEventForSelectedNode(editor, 'Enter', {shiftKey: true}); + + expectEditorHtmlToBeEqual(editor, `

hello



`); + }); + }); });