Bug
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
I think the bolding should transfer and apply to the Text leaves nested within the Link Element.
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!
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.