question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Bug: $generateNodesFromDOM does not apply styles properly

See original GitHub issue

When using $generateNodesFromDOM to convert HTML to Lexical, styles such as underline or align are not being applied.

In the case of HTML with styles that changes tag name such as bold(<strong>) or Italic(<em>), the style looks well when converted to lexical using $generateNodesFromDOM.

However, for HTML with styles that only add className(e.g.editor-text-*), such as underline or strikethrough, the styles don’t seem to be applied when converted to lexical.

I referenced the convert code(HTML -> Lexical - lexical doc).

Lexical version: 0.3.5

Steps To Reproduce

(* example HTML string - No style applied to text(STYLE TEST)) <div class="editor-input" contenteditable="true" spellcheck="true" data-lexical-editor="true" style="user-select: text; white-space: pre-wrap; word-break: break-word;" role="textbox"><p class="editor-paragraph ltr" dir="ltr"><span data-lexical-text="true">STYLE TEST</span></p></div>

case 1. bold

  1. Select bold - it is applied to <strong> tag. 스크린샷 2022-06-17 오전 11 05 53
  2. When convert it to lexical based on the html string obtained in step 1 and insert it, the bold style is well as shown below. 스크린샷 2022-06-17 오전 11 14 31

case 2. underline

  1. Select underline - The tagname is not changed(still <span>) and only editor-text-underline is added to the classname. 스크린샷 2022-06-17 오전 11 18 01 The underline is also visible in the editor. 스크린샷 2022-06-17 오전 11 20 30

  2. However, when convert it to lexical based on html obtained in step 1 and insert, converted without underline applied. 스크린샷 2022-06-17 오전 11 21 34

case 3. bold + italic

  1. Select bold and italic. The tagname is <strong>, and italics only apply to editor-text-italic classname. 스크린샷 2022-06-17 오전 11 25 33

  2. However, when convert and insert it, only applied bold(<strong>). (where is the editor-text-italic? 😢 ) 스크린샷 2022-06-17 오전 11 31 41

Considering the above cases, when converting html string to lexical, style or classname is ignored and it seems to be applied only based on tagname.

Link to code example: HTML -> Lexical

const LoadHtmlPlugin = ({htmlString}: Props) => {
  const [editor] = useLexicalComposerContext();
  useEffect(() => {
    editor.update(() => {
      const parser = new DOMParser();
      const dom = parser.parseFromString(htmlString, 'text/html');
      const nodes = $generateNodesFromDOM(editor, dom);

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

      // Insert them at a selection.
      const selection = $getSelection() as RangeSelection;
      selection.insertNodes(nodes);
    });
  }, [editor]);
  return null;
};

The current behavior

$generateNodesFromDOM does not apply styles properly

The expected behavior

If $generateNodesFromDOM is used, the style and className of the dom must be maintained.

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:18
  • Comments:10 (2 by maintainers)

github_iconTop GitHub Comments

8reactions
byTimocommented, Dec 15, 2022

I’ve implemented a patch-function that adds missed tag conversion (for Lexical@0.6.3: sub, sup, s) and background-color and color styles transfer from the DOM node to the Lexical node

const createMissedFormatConverter = (format: TextFormatType) => {
    return (): DOMConversionOutput => {
        return {
            forChild: lexicalNode => {
                if ($isTextNode(lexicalNode)) {
                    lexicalNode.toggleFormat(format);
                }

                return lexicalNode;
            },
            node: null,
        };
    };
};

const patchStyleConversion = (
    originalDOMConverter?: (node: HTMLElement) => DOMConversion | null,
): ((node: HTMLElement) => DOMConversionOutput | null) => {
    return node => {
        const original = originalDOMConverter?.(node);
        if (!original) {
            return null;
        }

        const originalOutput = original.conversion(node);

        if (!originalOutput) {
            return originalOutput;
        }

        const backgroundColor = node.style.backgroundColor;
        const color = node.style.color;

        return {
            ...originalOutput,
            forChild: (lexicalNode, parent) => {
                const originalForChild = originalOutput?.forChild ?? (x => x);
                const result = originalForChild(lexicalNode, parent);
                if ($isTextNode(result)) {
                    const style = [
                        backgroundColor ? `background-color: ${backgroundColor}` : null,
                        color ? `color: ${color}` : null,
                    ]
                        .filter(isNotNull)
                        .join('; ');
                    if (style.length) {
                        return result.setStyle(style);
                    }
                }
                return result;
            },
        };
    };
};

export function applyHtmlToRichContentPatches() {
    const importers = TextNode.importDOM();
    TextNode.importDOM = function _() {
        return {
            ...importers,
            span: () => ({
                conversion: patchStyleConversion(importers?.span),
                priority: 0,
            }),
            sub: () => ({
                conversion: createMissedFormatConverter('subscript'),
                priority: 0,
            }),
            sup: () => ({
                conversion: createMissedFormatConverter('superscript'),
                priority: 0,
            }),
            s: () => ({
                conversion: createMissedFormatConverter('strikethrough'),
                priority: 0,
            }),
        };
    };

    const missedFormatTag: Array<[TextFormatType, string]> = [
        ['underline', 'u'],
        ['strikethrough', 's'],
    ];

    const exportDOM = TextNode.prototype.exportDOM;
    TextNode.prototype.exportDOM = function _(this: TextNode, editor: LexicalEditor) {
        const { element } = exportDOM.apply(this, [editor]);
        if (!element) {
            return { element };
        }

        let wrapped = element;

        for (const [format, tag] of missedFormatTag) {
            if ($hasFormat(this, format)) {
                const wrapper = document.createElement(tag);
                wrapper.appendChild(element);
                wrapped = wrapper;
            }
        }

        return { element: wrapped };
    };
}

UPD After some investigation I figured out that the way of determining of applied formats for the node wasn’t correct for each cases, so I found the another way and updated the code snippet above using this

export function $hasFormat(node: TextNode, format: TextFormatType): boolean {
    const currentFormat = node.getFormat();
    return node.getFormatFlags(format, null) < currentFormat;
}
6reactions
patches11commented, Jun 22, 2022

I believe I have the same issue, and I do not believe it is just a styling concern.

It seems that $generateHtmlFromNodes correctly applies style classes to the HTML, for me it is something like this (snippet):

<span class="textUnderline">aaaa</span>

However as far as i can tell $generateNodesFromDOM does not have any logic to apply this in reverse (take the class and generate an underline node). So in my case where I am saving the output of $generateHtmlFromNodes and then reloading it via $generateNodesFromDOM I lose some subset of my styling on reload

Read more comments on GitHub >

github_iconTop Results From Across the Web

lexicaljs - Setting Editor from HTML - Stack Overflow
Got it working with the following code editor.update(() => { // In the browser you can use the native DOMParser API to parse...
Read more >
How to Fix CSS file is not Working with HTML in Chrome
Fix CSS file is not Working with HTML in Chrome | Problem Solved Show SupportBuy Me a COFFEE: https://buymeacoffee.com/Webjahin  ...
Read more >
@lexical/html - NPM Package Overview - Socket
This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code....
Read more >
240592 - Implement HTML Imports - chromium - Monorail
Tracking bug to HTML Imports implementation: ... use it in imports ... For example, the spec as defined is not very useful for...
Read more >
How to Style a Form with HTML and CSS - Hello Dev World
We are going use some CSS to take our form from yesterday from this… ... we can style it properly on the left...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found