Slate: "paste-html" – error when pasting content in which A tag is wrapped in a text-formatting tag (i.e. STRONG)

Created on 18 Dec 2019  Â·  7Comments  Â·  Source: ianstormtaylor/slate

Do you want to request a _feature_ or report a _bug_?

Bug

What's the current behavior?

Go to https://hungerlyyak.htmlpasta.com/ and copy the content. The HTML is below:

<p>The most important thing is to copy this text <strong><a href="https://example.com/">and this A link</a></strong> that is wrapped in STRONG tags.</p>
<p>Open your console and see what it says when you paste.</p>

Then, go to the paste-html example. Open your developer console. Paste the content in the Slate Editor. It triggers an error:

Uncaught Error: The <text> hyperscript tag can only contain text content as children.

This is because the <strong> tag becomes a text tag (with bold: true) and then, there is an Element nested in it ({ type: "link", url:"https://example.com/", children: [{ text: "and this A link" }] } – which is a forbidden nesting as per Slate's constraints.

Slate: 0.56.9
Browser: All
OS: Mac

What's the expected behavior?

I think the bolding should transfer and apply to the Text leaves nested within the Link Element.

bug ♥ help

Most helpful comment

Maybe I'm misunderstanding your logic, but I'm thinking the goal is to return only valid children of the text node. Since find() returns 'undefined' if there aren't any, but filter() will always return an array, it just seems cleaner to me.

All 7 comments

For those facing this issue, for now, I am basically removing whatever "blocky Element" inside any text element.

// Text
if (TEXT_TAGS[nodeName]) {

    const attr = TEXT_TAGS[nodeName](el)

    // Check for potential conflicts in the children Array
    const nonTextChild = children.find(child => !Text.isText(child))

    if (nonTextChild) {
        // Reduce the children to only have leaf nodes (and remove whatever Block Elements we have in between)
        children = children.reduce((acc: Descendant[], cur: Descendant) => {
            if (!Text.isText(cur)) {
                const leaves = Array.from(Node.texts(cur)).map((entry) => entry[0])
                console.log(JSON.stringify(leaves, null, 4))
                acc.push(...leaves)
            }
            return acc
        }, [])
    }

    return children
        .map(child => {
            child.text = normalizeText(child.text)
            return jsx(`text`, attr, child)
        })
}

The issue persists with the latest version of slate, even with this example https://github.com/ianstormtaylor/slate/blob/master/site/examples/paste-html.js

What I did and it works for me great was to use the previous example and use the deserialize function like this:

import { Node, Text } from "slate";
import { jsx } from "slate-hyperscript";

import { ELEMENT_TAGS, TEXT_TAGS } from "../constants/slate";

export const deserialize = (el: HTMLElement | ChildNode) => {
  if (el.nodeType === 3) {
    return el?.textContent;
  } else if (el.nodeType !== 1) {
    return null;
  } else if (el.nodeName === "BR") {
    return "\n";
  }

  const { nodeName } = el;
  let parent = el;

  if (
    el.nodeName === "PRE" &&
    el.childNodes[0] &&
    el.childNodes[0].nodeName === "CODE"
  ) {
    parent = el.childNodes[0];
  }
  const children: Node[] = Array.from(parent.childNodes)
    .map(deserialize)
    .flat();

  if (el.nodeName === "BODY") {
    return jsx("fragment", {}, children);
  }

  if (ELEMENT_TAGS[nodeName]) {
    const attrs = ELEMENT_TAGS[nodeName](el);
    return jsx("element", attrs, children);
  }

  if (TEXT_TAGS[nodeName]) {
    const attrs = TEXT_TAGS[nodeName](el);

    return children
      .find((child) => Text.isText(child))
      ?.map((child: Node) => jsx("text", attrs, child));
  }

  return children;
};

Comparing it with the code that @lazharichir did I just added this:

if (TEXT_TAGS[nodeName]) {
    const attrs = TEXT_TAGS[nodeName](el);

    return children
      .find((child) => Text.isText(child))
      ?.map((child: Node) => jsx("text", attrs, child));
  }

It just returns what Text.isText(child) thinks is right

@MontoyaAndres's solution worked for me, however I would suggest

return children
    .filter(child => Text.isText(child))
    .map(child => jsx("text", attrs, child));

Why filter instead of find?

Maybe I'm misunderstanding your logic, but I'm thinking the goal is to return only valid children of the text node. Since find() returns 'undefined' if there aren't any, but filter() will always return an array, it just seems cleaner to me.

Interesting, thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chriserickson picture chriserickson  Â·  3Comments

ianstormtaylor picture ianstormtaylor  Â·  3Comments

Slapbox picture Slapbox  Â·  3Comments

vdms picture vdms  Â·  3Comments

adrianclay picture adrianclay  Â·  3Comments