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

2020-06-23 14:21:42 +08:00
 camol

最近我花了点时间,把之前使用旧版的 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 !

https://github.com/kanweiwei/easy-editor

1739 次点击
所在节点    React
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/684115

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX