Slate.js 入门(四)- 自定义格式

1,106 阅读3分钟

我正在参加「掘金·启航计划」

前言

在上一篇文章中,我们学习了如何在富文本编辑器中添加自定义格式,在不同容器中展示不同的文本,但 Slate 不仅仅可以改变「区块」。

在本篇文章,你将学会如何添加自己想要的文本格式,例如粗体、斜体、代码或删除线。

所以我们从之前的栗子开始,你可以先回顾一下之前的文章:

文本加粗

在很多文本编辑器中,都有这么快捷键:ctrl + B -- 文本加粗。接下来,我们通过 Slate 的 onKeyDown 监听键盘动作,当用户按下 control + B 时,让编辑器将用户选中的文本替换为加粗格式:

<Editable
    renderElement={renderElement}
    onKeyDown={(event) => {
        if (!event.ctrlKey) {
            return;
        }
        switch (event.key) {
                // 当 "`" 按下时, 替换区块类型
            case "`": {
                event.preventDefault();
                const [match] = Editor.nodes(editor, {
                    match: (n) => n.type === "code",
                });
                Transforms.setNodes(
                    editor,
                    { type: match ? "paragraph" : "code" },
                    {
                        match: (n) =>
                        Editor.isBlock(editor, n),
                    }
                );
                break;
            }
​
                // 当 "B" 按下时,将选中的文本加粗
            case "b": {
                event.preventDefault();
                Transforms.setNodes(
                    editor,
                    { bold: true },
                    // 应用到文本节点中,如果选中部分和文本节点一部分重叠,将文本节点拆分
                    {
                        match: (n) => Text.isText(n),
                        split: true,
                    }
                );
                break;
            }
        }
    }}
    />

现在,我们已经添加成功,但是,如果你选择文本后按下 Ctrl + B 会发现富文本并没有发生任何变化。因为现在还没有告诉 Slate 如何渲染「粗体」标记。

Slate 认为每个段落 paragraph 是一颗树,对于你添加的任何格式,Slate 都会将其解析为「叶子」,一个段落可以包含不同格式的文本,就像一片片不同的叶子。你需要做的就是告诉 Slate 如何将不同的「叶子」渲染出来,就像之前的渲染区块元素一样。

定义一个 Leaf 组件:

// 定义一个 React 组件来渲染带有粗体文本的"叶子"
const Leaf = props => {
  return (
    <span
      {...props.attributes}
      style={{ fontWeight: props.leaf.bold ? 'bold' : 'normal' }}
    >
      {props.children}
    </span>
  )
}

定义完成后,需要告诉 Slate 关于「粗体」叶子的事情,我们将 renderLeaf 属性传递给富文本编辑器:

const renderLeaf = useCallback(props => {
    return <Leaf {...props} />
}, [])
​
//...
​
<Editable 
    renderLeaf={renderLeaf}
/>

现在再次按下 ctrl + B,你就能看到文本变为粗体了。

2022-09-27-18-17-19.gif

别忘了,当用户加粗选中文本后,再次按下 ctrl + B 时,选中文本应该回到上一次的状态,即「未加粗」标记。

为此,先判断选中文本的加粗标记状态,如果已加粗,设置为 false;如果未加粗,设置为 true

const marks = Editor.marks(editor);
Transforms.setNodes(
    editor,
    { bold: marks.bold ? false : true },
    {
        match: (n) => Text.isText(n),
        split: true,
    }
);

这样就能根据选中内容当前的标记状态来更新加粗标记状态,效果如下:

2022-09-27-18-44-37.gif

完整代码如下:

const initialValue = [
    {
        type: "paragraph",
        children: [{ text: "这是一段初始文字。" }],
    },
];
​
const CodeElement = (props) => {
    return (
        <pre {...props.attributes} style={{ background: "#dadada" }}>
            <code>{props.children}</code>
        </pre>
    );
};
​
const DefaultElement = (props) => {
    return <p {...props.attributes}>{props.children}</p>;
};
​
const Leaf = (props) => {
    return (
        <span
            {...props.attributes}
            style={{ fontWeight: props.leaf.bold ? "bold" : "normal" }}
        >
            {props.children}
        </span>
    );
};
​
const App = () => {
    // 创建一个在渲染中保持不变的 slate editor 对象
    const [editor] = useState(() => withReact(createEditor()));
    
    const renderElement = useCallback((props) => {
        console.log("renderElement-props", props);
        switch (props.element.type) {
            case "code":
                return <CodeElement {...props} />;
            default:
                return <DefaultElement {...props} />;
        }
    }, []);
​
    const renderLeaf = useCallback((props) => {
        return <Leaf {...props} />;
    }, []);
​
    return (
        <div className="container">
            <h3>Slate 编辑器</h3>
            <div className="editor-wrapper">
                <Slate
                    editor={editor}
                    value={initialValue}
                >
                    <Editable
                        renderElement={renderElement}
                        renderLeaf={renderLeaf}
                        onKeyDown={(event) => {
                            if (!event.ctrlKey) {
                                return;
                            }
​
                            switch (event.key) {
                                case "`": {
                                    event.preventDefault();
                                    const [match] = Editor.nodes(editor, {
                                        match: (n) => n.type === "code",
                                    });
                                    Transforms.setNodes(
                                        editor,
                                        { type: match ? "paragraph" : "code" },
                                        {
                                            match: (n) =>
                                                Editor.isBlock(editor, n),
                                        }
                                    );
                                    break;
                                }
​
                                case "b": {
                                    event.preventDefault();
                                    const marks = Editor.marks(editor);
                                    Transforms.setNodes(
                                        editor,
                                        { bold: marks.bold ? false : true },
                                        {
                                            match: (n) => Text.isText(n),
                                            split: true,
                                        }
                                    );
                                    break;
                                }
                            }
                        }}
                    />
                </Slate>
            </div>
        </div>
    );
};

最后

看到这里,相信你对 Slate 编辑器的「自定义标记」已经有了一定的了解。接下来,你可以参考上面的例子,尝试着去实现一个「文本倾斜」的自定义格式。

参考

docs.slatejs.org/walkthrough…