V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
camol
V2EX  ›  React

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

  •  
  •   camol · 2020-06-23 14:21:42 +08:00 · 1663 次点击
    这是一个创建于 1374 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1876 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 16:26 · PVG 00:26 · LAX 09:26 · JFK 12:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.