Skip to content

Commit

Permalink
Fixed text in adjacent spans being smushed in generateEditorState()
Browse files Browse the repository at this point in the history
no issue

- we had some code around for filtering out empty nodes due to some old bugs in Lexical, however those bugs no longer exist and surrounding code had already been updated to work with later lexical versions
- removed the node filtering code as it was causing text to get smushed together when spans were separated by a space in the HTML being parsed
- added tests to verify the old bugs caused by Lexical no longer existed
  • Loading branch information
kevinansfield committed Jan 8, 2025
1 parent 564c351 commit 48f2614
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 10 deletions.
10 changes: 2 additions & 8 deletions packages/koenig-lexical/src/utils/generateEditorState.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,19 @@ export default function generateEditorState({editor, initialHtml}) {
editor.update(() => {
const nodes = _$generateNodesFromHTML(editor, initialHtml);

// There are few recent issues related to $generateNodesFromDOM
// https://github.com/facebook/lexical/issues/2807
// https://github.com/facebook/lexical/issues/3677
// As a temporary fix, checking node content to remove additional spaces and br
const filteredNodes = nodes.filter(n => n.getTextContent().trim());

// Select the root
$getRoot().select();
// Clear existing content (we initialize an editor with an empty p node so it is focusable if there's no content)
$getRoot().clear();

// Insert them at a selection.
$insertNodes(filteredNodes);
$insertNodes(nodes);

// $insertNodes is focusing an editor (https://github.com/facebook/lexical/issues/4546)
// This behaviour can break the ability to autofocus the editor because
// initial state filling can happen after the component is already mounted.
// Reset selection to make it easier to manage editor focus in components instead of editor state generation
if (filteredNodes.length) {
if (nodes.length) {
$setSelection(null);
}
}, {discrete: true, tag: 'history-merge'}); // use history merge to prevent undo clearing the initial state
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import generateEditorState, {_$generateNodesFromHTML} from '../../../src/utils/generateEditorState';
import {DEFAULT_NODES} from '../../../src';
import {createEditor} from 'lexical';
import {describe, expect, test} from 'vitest';

describe('Utils: generateEditorState', () => {
function runGenerateEditorState(html, callback) {
function runGenerateEditorState(html, {nodes = DEFAULT_NODES} = {}) {
const editor = createEditor({
// lexical swallows errors inside updates by default,
// so we need to throw them to fail the test
onError: (error) => {
throw error;
}
},
nodes
});
const editorState = generateEditorState({editor, initialHtml: html});
return editorState.toJSON();
Expand All @@ -24,6 +26,17 @@ describe('Utils: generateEditorState', () => {
expect(editorState.root.children[0].children[0].text).toEqual('Test');
});

test('handles whitespace between paragraphs', function () {
const html = '<p>Test</p> <p>Test2</p>';
const editorState = runGenerateEditorState(html);

expect(editorState.root.children.length).toEqual(2);
expect(editorState.root.children[0].type).toEqual('paragraph');
expect(editorState.root.children[0].children[0].text).toEqual('Test');
expect(editorState.root.children[1].type).toEqual('paragraph');
expect(editorState.root.children[1].children[0].text).toEqual('Test2');
});

test('handles multiple spans inside paragraph', function () {
const html = '<p><span>Test</span> <span>Test2</span></p>';
const editorState = runGenerateEditorState(html);
Expand All @@ -41,6 +54,52 @@ describe('Utils: generateEditorState', () => {
expect(editorState.root.children[0].children[0].text).toEqual('Test Test2');
});

test('handles line breaks inside paragraph', function () {
const html = '<p>Test<br>Test2</p>';
const editorState = runGenerateEditorState(html);

expect(editorState.root.children[0].children.length).toEqual(3);
expect(editorState.root.children[0].children[0].text).toEqual('Test');
expect(editorState.root.children[0].children[1].type).toEqual('linebreak');
expect(editorState.root.children[0].children[2].text).toEqual('Test2');
});

test('handles line breaks with no wrapper', function () {
const html = 'Test<br>Test2';
const editorState = runGenerateEditorState(html);

expect(editorState.root.children.length).toEqual(1);
expect(editorState.root.children[0].type).toEqual('paragraph');
expect(editorState.root.children[0].children[0].text).toEqual('Test');
expect(editorState.root.children[0].children[1].type).toEqual('linebreak');
expect(editorState.root.children[0].children[2].text).toEqual('Test2');
});

test('handles line breaks and spans with no wrapper', function () {
const html = '<span>Test</span><br><span>Test2</span>';
const editorState = runGenerateEditorState(html);

expect(editorState.root.children.length).toEqual(1);
expect(editorState.root.children[0].type).toEqual('paragraph');
expect(editorState.root.children[0].children[0].text).toEqual('Test');
expect(editorState.root.children[0].children[1].type).toEqual('linebreak');
expect(editorState.root.children[0].children[2].text).toEqual('Test2');
});

// https://github.com/facebook/lexical/issues/2807
test('handles whitespace between list items', function () {
const html = '<ul><li>Test</li> <li>Test2</li></ul>';
const editorState = runGenerateEditorState(html);

expect(editorState.root.children.length).toEqual(1);
expect(editorState.root.children[0].type).toEqual('list');
expect(editorState.root.children[0].children.length).toEqual(2);
expect(editorState.root.children[0].children[0].type).toEqual('listitem');
expect(editorState.root.children[0].children[1].type).toEqual('listitem');
expect(editorState.root.children[0].children[0].children[0].text).toEqual('Test');
expect(editorState.root.children[0].children[1].children[0].text).toEqual('Test2');
});

describe('_$generateNodesFromHTML', () => {
function testGenerateNodesFromHTML(html, callback) {
const editor = createEditor({
Expand Down

0 comments on commit 48f2614

Please sign in to comment.