Search code examples
jsdomlexical-editor

How to get the lexical editor state, programmatically, from the parsed html string?


My use case is to upload the Excel sheet, in the React UI, with multiple rows in it. Each row is corresponding to a question.

The different question fields will be having HTML strings in them. After uploading this data, it is to be parsed and a lexical editor state is to be created using the parsed data. This has to be done programmatically as the uploaded data is not to be rendered in DOM.

After going through the lexical documentation, I came to know that lexical headless can be used to the same but with lexical headless it is suggested to JSDOM to parse the HTML string but it seems as if JSDOM cannot be used on the front end.

I tried to use the lexical headless with JSDOM in the node server but there also parsed HTML nodes are not getting appended to the lexical root.

import type { NextApiRequest, NextApiResponse } from "next";
import { createHeadlessEditor } from "@lexical/headless";
import Nodes from "components/lexicalEditor/nodes/Nodes";
import { $getSelection, $getRoot } from "lexical";
import { $generateNodesFromDOM } from "@lexical/html";
import { JSDOM } from "jsdom";

const parseHTMLString = async (req: NextApiRequest, res: NextApiResponse) => {
  const { body, method } = req;

  if (method === "POST") {
    try {
      const { htmlString } = body;
      const editor = createHeadlessEditor({
        nodes: Nodes,
        onError: () => {},
      });

      editor.update(() => {
        const dom = new JSDOM(htmlString);
        const nodes = $generateNodesFromDOM(editor, dom.window.document);
        $getRoot().select();
        const selection = $getSelection();
        selection?.insertNodes(nodes);
      });
      const editorState = editor.getEditorState().toJSON();
      return res.status(200).send({ editorState: editorState });
    } catch (e) {
      return res.status(400).send({ message: "something went wrong." });
    }
  }
};

export default parseHTMLString;

the response from the above

"editorState": {
        "root": {
            "children": [],
            "direction": null,
            "format": "",
            "indent": 0,
            "type": "root",
            "version": 1
        }
    }
}

for the payload sent with the request made

{
"htmlString":"<p>Hello World!</p>"
}

I am having two following questions:-

  1. Is there a way to solve the above use case using the lexical, in frontend?

  2. Also, is there way to make this in the node?

Please let me know if any additional information is to shared


Solution

  • I found the issue in my code. The editor status update is an async operation. Hence, after making it synchronous it is working fine.

    import type { NextApiRequest, NextApiResponse } from "next";
    import { createHeadlessEditor } from "@lexical/headless";
    import Nodes from "components/lexicalEditor/nodes/Nodes";
    import { $getSelection, $getRoot } from "lexical";
    import { $generateNodesFromDOM } from "@lexical/html";
    import { JSDOM } from "jsdom";
    
    const parseHTMLString = async (req: NextApiRequest, res: NextApiResponse) => {
      const { body, method } = req;
    
      if (method === "POST") {
        try {
          const { htmlString } = body;
          const editor = createHeadlessEditor({
            nodes: Nodes,
            onError: () => {},
          });
    
          await editor.update(() => {
            const dom = new JSDOM(htmlString);
            const nodes = $generateNodesFromDOM(editor, dom.window.document);
            $getRoot().select();
            const selection = $getSelection();
            selection?.insertNodes(nodes);
          });
          const editorState = editor.getEditorState().toJSON();
          return res.status(200).send({ editorState: editorState });
        } catch (e) {
          return res.status(400).send({ message: "something went wrong." });
        }
      }
    };
    
    export default parseHTMLString