开源一个React技术栈的高扩展性富文本编辑器

1,979 阅读3分钟

最近我花了点时间,把之前使用旧版的slate框架积累的一些经验整理了下,开源一个基于slate框架的react技术栈的高扩展性的富文本编辑器。

它的高扩展性主要在于:

  1. 支持自定义工具栏;
  2. 支持自定义节点渲染 ;

简要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!

github.com/kanweiwei/e…