富文本编辑器开发的心路历程

334 阅读4分钟

技术选型

为了方便 帮助中心文章、说明文档等文本的生成,我们觉得做一个编辑器。然而从这么多功能各异的编辑器中挑选出一个合适的并不容易。挑了几款比较常见的,如下

1. Draft.js — 能够满足基础需求的免费编辑器

优点

  • 易于理解的扁平化内容模型。
  • 构建在 Draft.js 上的高度可扩展及可定制的插件。
  • 自 2016 年以来,由 Facebook 支持的庞大且不断增长的开发者社区提供了许多的教程和支持。

缺点

  • 缺乏官方的移动端支持。
  • OSX 自定义键绑定的问题。
  • 当需要表格这样复杂的内容结构时,编辑器将会变慢,代码也会变得复杂。

2. Slate.js — 支持复杂内容格式的免费编辑器

优点

  • 可使用插件进行扩展。
  • 输出 JSON 格式的内容,更容易与其他模块集成。
  • 嵌套文档模型支持更复杂的内容结构,如表格、分页符和其他自定义功能。

缺点

  • 操作编辑器控件需要 UI 设置。
  • 仍处于 beta 阶段,在生产环境下站点的实践和信赖上可能会令人失望。

如果你希望找到带有自定义功能的内存优化的编辑器,Slate.js 是最佳的选择。

3.Quill.js — 带有主题的免费 API 的文本编辑器

优点

  • 易于设置和使用。
  • 跨平台和浏览器支持。
  • 预设编辑器样式的主题支持。
  • 输出 HTML 格式的内容,无需像其他编辑器一样解析。

缺点

  • 有限的定制功能。
  • 较少的更新和补丁。
  • 可能的 XSS 安全漏洞。

4.fluent-editor

优点

  • 一个基于 Quill 2.0 的富文本编辑器,在 Quill 基础上扩展了丰富的模块和格式,功能强大、开箱即用。
  • 基于Quill又封装了一层,使用简单
  • 解决了Quill使用时一些三方资源的依赖冲突问题(全部已经内置好)
  • 样式也做了一些调整,整体界面更好看

缺点

  • 没有toolbar自定义样式的功能
  • 扩展能力有局限(但也基本够使用了)

以上来源于: juejin.cn/post/704265…

quill

本文事例是基于react18 umi4开发的

import Quill, {
  DeltaStatic,
  Sources,
} from 'quill';
import 'react-quill/dist/quill.snow.css';
import "quill/dist/quill.core.css";
import ImageResize from '@taoqf/quill-image-resize-module'; // 图片放大收缩能力
import { ImageDrop } from 'quill-image-drop-module' // 拖拽功能


// 注册模块
Quill.register({
  'modules/imageResize': ImageResize,
  'modules/ImageDrop': ImageDrop
}, true)

const toolbarOptions = []
const formats = []

useEffect(() => {
    if (open && quillRef.current) {
      if (!!quill) { // 存在的话
        quill.on('editor-change', onEditorChange);
      } else {
        quill = new Quill(quillRef?.current, {
          debug: 'info',
          formats: formats,
          modules: {
            // syntax: true,
            ImageDrop: true,
            imageResize: {
              displaySize: true,
            },
            // toolbar: {
            //   container: '#toolbar'
            // }
            // toolbar: toolbarRef.current,
            toolbar: toolbarOptions,
          },
          placeholder: 'Type here...',
          theme: 'snow'
        });
      }
    }
})

  return (
      <DrawerForm
      title='新增文档'
      width={631}
      open={open}
      layout="horizontal"
      formRef={formRef}
      onOpenChange={onOpenChange}
      drawerProps={{
        destroyOnClose: true,
        maskClosable: false,
      }}
      request={async () => {
        return {}
      }}
      onFinish={async (values: any) => {
        return true
      }}>
      <div>
          <CustomButton></CustomButton>
          <div ref={quillRef}></div>
        </div>
    </DrawerForm>
  )

遇到的问题:

1、修改图片大小

一开始查询资料推荐用quill-image-resize-module这个组件

引入之后一直提示

image.png

针对这个问题 用

const ImageResize = **require**('quill-image-resize-module').default

 

console.**log**('ImageResize', ImageResize)

**Quill**.**register**({

'modules/imageResize': ImageResize,

'modules/ImageDrop': **ImageDrop**

})

发现不好用

有人说是资源加载失败于是我下载下来放到本地


import ImageResize from './image-resize.min.js';

引入本地的也不好用

去这个组件的官网

又开始报“:moduleClass is not a constructor

换了一个插件改成使用@taoqf/quill-image-resize-module终于可以了

image.png

下面这个不能用只能设置displaySize: true

ImageResize: {

modules: ['Resize', 'DisplaySize', 'Toolbar']

}
2、如何插入可添加行和列修改样式的表格

两种方法: www.cnblogs.com/Grewer/p/16…

第一种报【Cannot read properties of undefined (reading 'pop')

解决办法【github.com/soccerloway…

改了一下确实可以 但是直接改node_modules文件后面担心有问题

通过修改node_modules 解决了这个问题,但是插入表格调用insertTable时添加的不是table而是一堆p标签,看到issues里也遇到这种问题,但是没有解决方案。

第二种方法一直加载模块失败

3、quill 如果toolbar使用自定义的,弹窗一进去不能正常渲染

quill 实例的时机有问题,可通过下面fluentEditor里面遇到的问题1方法解决

react-quill

它是对quill的一层封装,使用起来更为方便简单

npm install react-quill --save

import React, { useState } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

function MyComponent() {
  const [value, setValue] = useState('');

  return <ReactQuill theme="snow" value={value} onChange={setValue} />;
}

用value来设置初始值,onchange里可以获取编辑的内容

遇到的问题,和quill遇到的问题基本一致

fluentEditor

遇到的问题

1、打开弹窗时没有正确渲染组件,只有修改代码保存一下才能正常展示

解决办法:解决办法,加了一个延时循环调用

2、生成的图片是base64,获取文档之后如何展示

使用的时候对于获取的base64值并不是都可以直接复制给src属性的。Buffer.from(data).toString('base64')转化出来的base64值就不可以直接赋值给图片的src属性。核心在于img标签的src值的格式必须为:

<img src=`data:image/png;base64,${base64数据}`/>