I am learning Slate.js
text editor for react and I have problem. When I for example press button to change text to <h1>
nothing is changed in textarea, but when I inspect code in Elements panel of browser I can see changes (element changing from paragraph to h1). Don't know whats wrong. Code is pretty much taken from Slate.js
example (simplified a bit). Using bold,code,italic works fine (MarkButton, ToggleMark, Leaf) only Block Elements are problematic (text alignment works fine tho). Here's my component:
import { SystemUpdateOutlined } from "@mui/icons-material";
import React, { useCallback, useMemo, useState } from "react";
import { Editor, Transforms, createEditor, Element } from "slate";
import { Slate, Editable, withReact } from "slate-react";
// Define our own custom set of helpers.
const LIST_TYPES = ["numbered-list", "bulleted-list"];
const TEXT_ALIGN_TYPES = ["left", "center", "right", "justify"];
const CustomEditor = {
//------------------------------------------------------------------------
isMarkActive(editor, format) {
const marks = Editor.marks(editor);
return marks ? marks[format] === true : (marks[format] = false);
},
//------------------------------------------------------------------------
isBlockActive(editor, format, blockType = "type") {
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(
Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: (n) =>
!Editor.isEditor(n) &&
Element.isElement(n) &&
n[blockType] === format,
})
);
return !!match;
},
//------------------------------------------------------------------------
toggleMark(editor, format) {
const isActive = CustomEditor.isMarkActive(editor, format);
if (isActive) {
Editor.removeMark(editor, format);
} else {
Editor.addMark(editor, format, true);
}
},
//------------------------------------------------------------------------
toggleBlock(editor, format) {
const isActive = CustomEditor.isBlockActive(
editor,
format,
TEXT_ALIGN_TYPES.includes(format) ? "align" : "type"
);
console.log(isActive);
const isList = LIST_TYPES.includes(format);
Transforms.unwrapNodes(editor, {
match: (n) =>
!Editor.isEditor(n) &&
Element.isElement(n) &&
LIST_TYPES.includes(n.type) &&
!TEXT_ALIGN_TYPES.includes(format),
split: true,
});
let newProperties;
if (TEXT_ALIGN_TYPES.includes(format)) {
newProperties = {
...newProperties,
align: isActive ? undefined : format,
};
} else {
newProperties = {
...newProperties,
type: isActive ? "paragraph" : isList ? "list-item" : format,
};
}
//Transforms.setNodes < Element > (editor, newProperties);
Transforms.setNodes(editor, newProperties, {
match: (n) => Editor.isBlock(editor, n) && Element.isElement(n),
});
if (!isActive && isList) {
const block = { type: format, children: [] };
Transforms.wrapNodes(editor, block);
}
},
};
const initialValue = [
{
type: "paragraph",
children: [{ text: "A line of text in a paragraph." }],
},
];
const SlateTextEditor = () => {
const [editor] = useState(() => withReact(createEditor()));
const renderElement = useCallback((props) => <BlockElement {...props} />, []);
const renderLeaf = useCallback((props) => <Leaf {...props} />, []);
return (
<Slate editor={editor} initialValue={initialValue}>
<div>
<MarkButton editor={editor} format="bold" icon="format_bold" />
<MarkButton editor={editor} format="italic" icon="format_italic" />
<MarkButton
editor={editor}
format="underline"
icon="format_underlined"
/>
<MarkButton editor={editor} format="code" icon="code" />
<BlockButton editor={editor} format="heading-one" icon="looks_one" />
<BlockButton editor={editor} format="heading-two" icon="looks_two" />
<BlockButton editor={editor} format="block-quote" icon="format_quote" />
<BlockButton
editor={editor}
format="numbered-list"
icon="format_list_numbered"
/>
<BlockButton
editor={editor}
format="bulleted-list"
icon="format_list_bulleted"
/>
<BlockButton editor={editor} format="left" icon="format_align_left" />
<BlockButton
editor={editor}
format="center"
icon="format_align_center"
/>
<BlockButton editor={editor} format="right" icon="format_align_right" />
<BlockButton
editor={editor}
format="justify"
icon="format_align_justify"
/>
</div>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
spellCheck
autoFocus
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
// Replace the `onKeyDown` logic with our new commands.
switch (event.key) {
case "`": {
event.preventDefault();
CustomEditor.toggleBlock(editor, "heading-one");
break;
}
case "b": {
event.preventDefault();
CustomEditor.toggleMark(editor);
break;
}
}
}}
/>
</Slate>
);
};
//Buttons ----------------------------------------------------------------
const MarkButton = ({ editor, format, icon }) => {
return (
<button
onMouseDown={(event) => {
event.preventDefault();
CustomEditor.toggleMark(editor, format);
}}
title={format}
>
<Icon>{icon}</Icon>
</button>
);
};
//-------------------------------------------------------------------------
const BlockButton = ({ editor, format, icon }) => {
return (
<button
onMouseDown={(event) => {
event.preventDefault();
CustomEditor.toggleBlock(editor, format);
}}
title={format}
>
<Icon>{icon}</Icon>
</button>
);
};
//-------------------------------------------------------------------------
const Icon = ({ children }) => {
return <span className="material-icons">{children}</span>;
};
//Leaf and Block elements -------------------------------------------------------------------------
const BlockElement = ({ attributes, children, element }) => {
const style = { textAlign: element.align };
switch (element.type) {
case "block-quote":
return (
<blockquote style={style} {...attributes}>
{children}
</blockquote>
);
case "bulleted-list":
return (
<ul style={style} {...attributes}>
{children}
</ul>
);
case "heading-one":
console.log("W hedingu");
return (
<h1 style={style} {...attributes}>
{children}
</h1>
);
case "heading-two":
return (
<h2 style={style} {...attributes}>
{children}
</h2>
);
case "list-item":
return (
<li style={style} {...attributes}>
{children}
</li>
);
case "numbered-list":
return (
<ol style={style} {...attributes}>
{children}
</ol>
);
default:
return (
<p style={style} {...attributes}>
{children}
</p>
);
}
};
//-------------------------------------------------------------------------
const Leaf = ({ attributes, children, leaf }) => {
if (leaf.bold) {
children = <strong>{children}</strong>;
}
if (leaf.code) {
children = <code>{children}</code>;
}
if (leaf.italic) {
children = <em>{children}</em>;
}
if (leaf.underline) {
children = <u>{children}</u>;
}
return <span {...attributes}>{children}</span>;
};
//-------------------------------------------------------------------
export default SlateTextEditor;