0

I work with a database that contains articels saved as simple HTML and want to switch to a WYSIWYG that plays well with React so I am looking into Lexical.

When "feeding" Lexical with the intitial HTML I need to transform it into Nodes first. But: When the initial HTML is not superclean and contains e.g. spaces between the HTML tags, the transformation breaks.

So this as input string works:

const htmlString = '<p><b>I am bold</b></p><p><b><i>I am bold and italic</i></b></p>';

But this does not:

const htmlString = '<p><b>I am bold</b></p> <p><b><i>I am bold and italic</i></b></p>';

(because of the space between the p-tags)

How can I fix this? Since I cannot ensure the source code is well-formed HTML.

function InitialContentPlugin() {
  const [editor] = useLexicalComposerContext();

  editor.update(() => {

    const htmlString = '<p><b>I am bold</b></p><p><b><i>I am bold and italic</i></b></p>';
    const parser = new DOMParser();
    const dom = parser.parseFromString(htmlString, 'text/html');

    // Once you have the DOM instance it's easy to generate LexicalNodes.
    const nodes = $generateNodesFromDOM(editor, dom);

    // Select the root
    $getRoot().select();

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


  return null;
}

Edit:

I figured out that the empty space creates a TextNode. I cannot insert this into the root because TextNodes are "leaves" and therefore not allowed as direct children of the root. Need to find a way now how to fix this.

nerdess
  • 10,051
  • 10
  • 45
  • 55

2 Answers2

2

OK I figured out a simple solution myself that removes "leaf"-nodes on root level:

editor.update(() => {
        const htmlString = '<p><b>I am bold</b></p><br><p><b><i>I am bold and italic</i></b></p>';
        const parser = new DOMParser();
        const dom = parser.parseFromString(htmlString, 'text/html');

        // Once you have the DOM instance it's easy to generate LexicalNodes.
        const nodes = $generateNodesFromDOM(editor, dom)

            .map((node) => {

                if (node.getType() === 'text') {
                    if (node.getTextContent().trim() === '') {
                        return null;
                    } else {
                        return $createParagraphNode().append(node);
                    }
                }

                if (node.getType() === 'linebreak') {
                 return null
                }

                return node;
            })
            .filter((node) => !!node);

        // Select the root
        $getRoot().select();

        // Insert them at a selection.
        $insertNodes(nodes);
    });
nerdess
  • 10,051
  • 10
  • 45
  • 55
0

Thought at first @nerdess solution would work great but it means italic/bold text will be a new paragraph, which break the render.

Here my "final" homemade solution (that gathers root text leaves into paragraphs):

      const nodes = $generateNodesFromDOM(editor, dom);

      // Root nodes (leaves) must be blocks, but when parsing HTML nested structures of `<div>` are cropped to only keep
      // the final text node, breaking the step to get the editor state
      // Our solution is to gather successive root text nodes into an additional paragraph node
      const ajustedNodes: LexicalNode[] = [];
      let currentAdditionalParagraphNode: ParagraphNode | null = null;
      for (const node of nodes) {
        // Only parse the first layer
        if (!node.getParent()) {
          const nodeType = node.getType();

          if (nodeType === 'text') {
            if (node.getTextContent().trim() === '') {
              continue;
            }

            if (!currentAdditionalParagraphNode) {
              // Create a paragraph on the first layer
              currentAdditionalParagraphNode = $createParagraphNode();
              ajustedNodes.push(currentAdditionalParagraphNode);
            }

            if (currentAdditionalParagraphNode) {
              currentAdditionalParagraphNode.append(node);
            }
          } else {
            currentAdditionalParagraphNode = null;

            if (nodeType === 'linebreak') {
              continue;
            } else {
              ajustedNodes.push(node);
            }
          }
        }
      }

      const root = $getRoot();
      root.append(...ajustedNodes);
Thomas Ramé
  • 438
  • 4
  • 10