最近我花了点时间,把之前使用旧版的 slate 框架积累的一些经验整理了下,开源一个基于 slate 框架的 react 技术栈的高扩展性的富文本编辑器。
它的高扩展性主要在于:
简要 demo 如下
import * as React from "react";
import ReactDom from "react-dom";
import EasyEditor from "@camol/easy-editor";
class Editor extends React.Component {
html = "";
handleChange = (v: any) => {
console.log("change=>>>", v);
if (this.editorRef.current) {
// value to html
console.log(this.editorRef.current.convertor.serialize(v.change.value));
}
};
render() {
return <EasyEditor value={"<p>123</p>"} onChange={this.handleChange} />;
}
}
ReactDom.render(<Editor />, document.getElementById("root"));
value
支持 slate 的 Value
实例,也支持 html 。所以,你可以调用 value.toJSON() 取得 json 格式的数据 或者转成 html 存入数据库,回显时可以直接使用。
目前,编辑器已经内置了一些工具,如文字加粗、斜体、下划线、文字居中等功能。支持图片和视频的插入,资源地址可以通过 beforeUpload
自定义上传逻辑。从剪贴板内复制粘贴图片(包括 word 内复制)等上传文件部分都会尝试调用该函数以获取上传后的资源地址。不使用自定义上传时,图片地址默认使用 base64 格式。
悬浮工具栏,考虑到选中不同节点时的渲染不同,还没考虑好如何设计,暂时注释掉了这部分功能,后面会完善。
// 自定义 插入视频的操作按钮
class AudioControl extends React.Component {
inputRef = React.createRef();
handleClick = () => {
if (inputRef.current) {
inputRef.current.click();
}
};
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files[0];
e.target.value = "";
if (file) {
if (this.props.beforeUpload) {
let url = await this.props.beforeUpload(file);
if (url) {
let change = this.props.change.focus().insertInline({
object: "inline",
type: "audio",
isVoid: true,
data: {
src: url,
},
});
this.props.update(change);
}
}
}
};
render() {
return (
<span onMouseDown={this.handleClick}>
<span className="tool-insert-video" />
<input
type="file"
style={{ width: 0, height: 0, opacity: 0 }}
ref={this.inputRef}
onChange={this.handleChange}
/>
</span>
);
}
}
<EasyEditor
value={"<p>123</p>"}
onChange={this.handleChange}
controls={[
["bold", "u", "image"],
[
{
type: "audio",
component: (change, update, beforeUpload) => {
return (
<AudioControl
change={change}
update={update}
beforeUpload={beforeUpload}
/>
);
},
},
],
]}
/>
使用自定义节点渲染,可以实现一些高级功能,如图片拖拽调整大小,图片悬浮、左环绕、右环绕等功能,或者表格的拖拽调整等,又或者是类似石墨文档等添加文件附件,展示在文档中的功能。前面部分功能已经在编辑器内实现了。
这里我简要展示下如何自定义渲染 audio 标签:
import * as React from "react";
import { DefaultTreeElement } from "parse5";
const plugin = {
type: "node", // node, mark
object: "inline", // block, inline
nodeType: "super-audio", // 自定义节点类型
// 自动解析 html 中 audio 标签,生成 super-audio 节点
importer(el: DefaultTreeElement, next: Function): any {
if (el.tagName.toLowerCase() === "audio") {
return {
object: "inline", // block 、inline,
type: "super-audio",
isVoid: true,
data: {
src: el?.attrs?.find((n) => n.name === "src")?.value,
},
nodes: next(el.childNodes),
};
}
},
// 调用 editor.convertor.serialize(value) 会调用该方法将 super-audio 节点 转成 对应的 html 存入数据库
exporter(node: any, children: any): any {
let { className, src } = node.data.toJS();
return <audio src={src} className={className}></audio>;
},
// 自定义渲染方式
render(
editor: any,
props: { attributes: any; children: any; node: any; isSelected: any }
): any {
// @ts-ignore
const { attributes, children, node, isSelected } = props;
const src = node.data.get("src");
return (
<span {...attributes}>
<audio src={src} controls>
{children}
</audio>
</span>
);
},
};
export default plugin;
<EasyEditor
value="<audio src='xxxxx.mp3'></audio><p> </p>"
...
plugins={[audioPlugin]}
/>
目前编辑器中的视频播放插件就是使用该特性实现的,集成了 plyr-react,支持 mp4 、webm (其他格式后面会支持)。
虽然可能还有些问题,但我后面会长期维护的,希望对需要的同学们有帮助 :)。
最后贴一下该项目的 github 地址,求支持,求 star !