我正在参加「掘金·启航计划」
前言
到目前为止,我所写的文章都是对 Slate 富文本编辑器一次性逻辑的编写。但 Slate 的强大功能之一,是你可以根据自己的期望对富文本的「特定域」逻辑通过代码进行封装,从而进减少一次性代码的编写。
在之前的入门指南中,我们通过对 onKeyDown 事件的监听,实现了 Slate 富文本编辑器处理格式化代码块和粗体标记的功能,不过你会发现我们总是直接使用内置的编辑器助手来实现它,而不是使用「命令」的方式,比如通过工具栏按钮。
Slate 允许你拓展内置编辑器对象,来处理你的自定义富文本命令,甚至你可以使用预先打包的「插件」来实现一系列特定的功能。
我们从先前的应用例子开始,在此之前,可以先回顾一下之前的文章:
- Slate.js 入门(一) - 实现一个简单的富文本编辑器
- Slate.js 入门(二) - 添加事件绑定
- Slate.js 入门(三) - 自定义元素
- Slate.js 入门(四) - 自定义格式
编辑器的命令
封装特定域
从上一个例子我们可以知道,富文本编辑器已具有「代码块」和「粗体格式」的概念。但是这些东西都是在 onKeyDown 处理程序中一次性定义的。如果你想在其他地方复用该逻辑,你需要将它单独抽离。
我们可以创建自定义辅助函数来抽离逻辑,实现这些「特定域」:
const CustomEditor = {
// 判断文本是否已加粗?
isBoldMarkActive(editor) {
const [match] = Editor.nodes(editor, {
match: n => n.bold === true,
universal: true,
})
return !!match
},
// 判断文本是否为"代码块"?
isCodeBlockActive(editor) {
const [match] = Editor.nodes(editor, {
match: n => n.type === 'code',
})
return !!match
},
toggleBoldMark(editor) {
const isActive = CustomEditor.isBoldMarkActive(editor)
Transforms.setNodes(
editor,
{ bold: isActive ? null : true },
{ match: n => Text.isText(n), split: true }
)
},
toggleCodeBlock(editor) {
const isActive = CustomEditor.isCodeBlockActive(editor)
Transforms.setNodes(
editor,
{ type: isActive ? null : 'code' },
{ match: n => Editor.isBlock(editor, n) }
)
},
}
对 onKeyDown 方法作出如下修改:
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
event.preventDefault();
CustomEditor.toggleCodeBlock(editor);
break;
}
case "b": {
event.preventDefault();
CustomEditor.toggleBoldMark(editor);
break;
}
}
}}
修改完成后,你会发现之前添加的「代码块」和「加粗」的功能依然可用,但我们将这些逻辑都封装到了 CustomEditor 对象里,代码可读性得到了提高,也方便开发者后续操作。
命令-工具栏按钮
你可以从任何能够访问到编辑器对象 editor 的地方调用它。例如,我们创建两个工具栏按钮,分别为「加粗」和「代码块」,完整代码如下:
const CustomEditor = {
// 判断文本是否已加粗?
isBoldMarkActive(editor) {
const [match] = Editor.nodes(editor, {
match: (n) => n.bold === true,
universal: true,
});
return !!match;
},
// 判断文本是否为"代码块"?
isCodeBlockActive(editor) {
const [match] = Editor.nodes(editor, {
match: (n) => n.type === "code",
});
return !!match;
},
toggleBoldMark(editor) {
const isActive = CustomEditor.isBoldMarkActive(editor);
Transforms.setNodes(
editor,
{ bold: isActive ? null : true },
{ match: (n) => Text.isText(n), split: true }
);
},
toggleCodeBlock(editor) {
const isActive = CustomEditor.isCodeBlockActive(editor);
Transforms.setNodes(
editor,
{ type: isActive ? null : "code" },
{ match: (n) => Editor.isBlock(editor, n) }
);
},
};
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}
onChange={(v) => console.log(v)}
>
<div>
<button
onMouseDown={(event) => {
event.preventDefault();
CustomEditor.toggleBoldMark(editor);
}}
style={{ marginRight: 12 }}
>
加粗
</button>
<button
onMouseDown={(event) => {
event.preventDefault();
CustomEditor.toggleCodeBlock(editor);
}}
>
代码块
</button>
</div>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={(event) => {
if (!event.ctrlKey) {
return;
}
switch (event.key) {
case "`": {
event.preventDefault();
CustomEditor.toggleCodeBlock(editor);
break;
}
case "b": {
event.preventDefault();
CustomEditor.toggleBoldMark(editor);
break;
}
}
}}
/>
</Slate>
</div>
</div>
);
};
接下来,我们就可以通过工具栏按钮,也就是「命令」的方式来控制文本的格式啦~
最终效果如下:
最后
接下来,我们在编辑器的后续开发中就能提高开发效率,即使添加了许多拓展功能,也不需要很大的工作量。除此之外,我们将所有的命令逻辑和可视化组件隔离出来,使得代码更易于维护。