乐趣区

关于javascript:富文本编辑器-quilljs-开发四-自定义格式扩展

前言

鉴于各种繁冗的需要,quill.js 编辑器也面临着各种挑战,例如咱们须要增加“table”布局款式以适应邮件发送格局,手动扩大表情符号性能等等。本文将对这些可定制化性能进行解说和实现。

辨别 format 和 module

首先须要明确的是,咱们应该分明本人所需的扩大具体是什么?

比方想要新增一个 自定义 emoji, 那么设想一下步骤:

  1. 点击工具栏
  2. 弹出弹窗或者对应的 popover
  3. 在 2 中选中 emoji

这些步骤是一种常见的增加流程。

咱们须要明确的是,增加自定义表情符号必然须要一个相应的格局。

本文将以 format 为例,对此进行具体解说。

quill 的格局类型

说起 quill 的格局类型, 他的罕用格局能够分成 3 类:

  1. Inline
    常见的有 Bold, Color, Font 等等, 不占据一行的标签, 相似于 html 里 span 的个性, 是一个行内款式, Inline
    格局之间能够相互影响
  2. Block
    增加 Block 款式, 必然会占据一整行, 并且 Block 款式之间不能兼容(共存), 常见的有 List, Header, Code Block 等等
  3. Embeds
    媒体文件, 常见的有 Image, Video, Formula, 这类格局扩大的比拟少, 然而本次要加的 emoji 然而这种格局

自定义款式

新增 emoji.ts 文件来存储格局, 对于他的类型, 咱们抉择 Embeds 格局, 应用这种格局有以下起因:

  1. 他是一种独特的类型, 不能和色彩, 字体大小等等用在一起
  2. 须要和字体并列, 所以也不能是 Block 类型
import Quill from 'quill';

const Embed = Quill.import('blots/embed');

class EmojiBlot extends Embed {
  static blotName: string;
  static tagName: string;

  static create(value: HTMLImageElement) {const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.src);
    node.setAttribute('width', value.width);
    node.setAttribute('height', value.height);
    return node;
  }

  static formats(node: HTMLImageElement) {
    return {alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }

  static value(node: HTMLImageElement) {
    // 次要在有初始值时起作用
    return {alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }
}

EmojiBlot.blotName = 'emoji';
EmojiBlot.tagName = 'img';
EmojiBlot.className = 'emoji_icon'

export default EmojiBlot;

因为还有失常的图片类型会应用 img, 这里就须要加上 className, 来打消歧义
一般来说, 新开发的扩展性类型, 尽量都加上 className

这样一个 emoji 类型就创立实现了!

最初咱们注册到 Quill 上即可:

import EmojiBlot from "./formats/emoji";

Quill.register(EmojiBlot);

这里咱们在加上自定义的 popover, 用来点击获取 emoji:

    <Popover content={<div className={'emoji-popover'} onClick={proxyEmojiClick}>
  <img alt={'图片说明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img.png"/>
  <img alt={'图片说明'} width={32} height={32} src="https://grewer.github.io/dataSave/emoji/img_1.png"/>
</div>}>
  <button className="ql-emoji">emoji</button>
</Popover>

通过代理的形式, 来获取 dom 上的具体属性:

  const proxyEmojiClick = ev => {
  const img = ev.target
  if (img?.nodeName === 'IMG') {const quill = getEditor();
    const range = quill.getSelection();
    // 这里能够用 img 的属性, 也能够通过 data-* 来传递一些数据
    quill.insertEmbed(range.index, 'emoji', {
      alt: img.alt,
      src: img.src,
      width: img.width,
      height: img.height,
    });
    quill.setSelection(range.index + 1);
  }
}

展现下新增 emoji 的成果:

根底格局阐明

咱们的自定义格局都是基于 quill 的根底库: parchment

这里咱们就介绍下他的几个重要 API:

class Blot {
  // 在手动创立 / 初始值时, 都会触发 create 函数
  static create(value?: any): Node;

  // 从 domNode 上获取想要的数据
  static formats(domNode: Node);

  // static formats 返回的数据会被传递给 format
  // 此函数的作用是将数据设置到 domNode
  // 如果 name 是 quill 里的格局走默认逻辑是会被正确应用的
  // 如果是非凡的 name, 不解决就不会起效
  format(format: name, value: any);

  // 返回一个值, 通常在初始化的时候传给 static create
  // 通常实现一个自定义格局, value 和 format 应用一个即可达到目标
  value(): any;}

上述几个 API 便是创立自定义格局时罕用到的

详情可参考: https://www.npmjs.com/package/parchment#blots

在上文讲到了 formatvalue 的作用, 咱们也能够对于 EmojiBlot 做出一些革新:

class EmojiBlot extends Embed {
  static blotName: string;
  static tagName: string;

  static create(value: HTMLImageElement) {const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.src);
    node.setAttribute('width', value.width);
    node.setAttribute('height', value.height);
    return node;
  }

  static formats(node: HTMLImageElement) {
    return {alt: node.getAttribute('alt'),
      src: node.getAttribute('src'),
      width: node.getAttribute('width'),
      height: node.getAttribute('height'),
    };
  }


  format(name, value) {if (['alt', 'src', 'width', 'height'].includes(name)) {this.domNode.setAttribute(name, value);
    } else {super.format(name, value);
    }
  }
}

目前来说, 这两种计划都能实现咱们的 EmojiBlot

当然 format 的作用, 并不仅仅在于 新增属性到 dom 上, 也能够针对某些属性, 批改、删除 dom 上的信息

其余格局

下面咱们讲述了三个常见的格局: InlineEmbedsBlock, 其实在 quill 还有一些非凡的 blot:
如: TextBlotContainerBlotScrollBlot

其中 ScrollBlot 属于是所有 blot 的根节点:

class Scroll extends ScrollBlot {// ...}

Scroll.blotName = 'scroll';
Scroll.className = 'ql-editor';
Scroll.tagName = 'DIV';
Scroll.defaultChild = Block;
Scroll.allowedChildren = [Block, BlockEmbed, Container];

至于 TextBlot, 是在定义一些属性时罕用到的值:

例如源码中 CodeBlock 的局部:

CodeBlock.allowedChildren = [TextBlot, Break, Cursor];

意味着 CodeBlock 的格局下, 他的 子节点 , 只能是 文本, 换行, 光标
(换行符和光标都属于 EmbedBlot)

这样就管制住了子节点的类型, 防止构造错乱

ContainerBlot

最初要说一下 ContainerBlot, 这是一个在自定义节点时, 创立 Block 类型时常常会用到的值:

在源码中, 并没有默认的子节点配置, 所以导致看上去就像这样, 但其实 container 的自由度是十分强的

这里就给出一个我之前创立的函件格局例子:

在富文本中扩大格局生成能兼容大部分函件的外层格局, 格局要求:
格局占据肯定宽度, 如 500px, 须要让这部分居中, 格局内能够输出其余的款式

大家可能感觉简略, 只须要 div 套上, 再加上一个款式 widthtext-align 即可

然而这种计划不太适宜邮件的场景, 在桌面和挪动端渲染电子邮件大概有上百万种不同的组合形式。

所以最稳固的布局计划只有 table 布局

所以咱们开始创立一个 table 布局的外壳:

class WidthFormatTable extends Container {static create() {const node = super.create();
    node.setAttribute('cellspacing', 0);
    node.setAttribute('align', 'center');
    return node;
  }
}

WidthFormatTable.blotName = 'width-format-table';
WidthFormatTable.className = 'width-format-table';
WidthFormatTable.tagName = 'table';

有了 table 标签, 那么同样也会须要 trrd:

也是相似的创立办法:

class WidthFormatTR extends Container {
}

class WidthFormatTD extends Container {}

最初通过 API 将其关联起来:

WidthFormatTable.allowedChildren = [WidthFormatTR];

WidthFormatTR.allowedChildren = [WidthFormatTD];
WidthFormatTR.requiredContainer = WidthFormatTable;

WidthFormatTD.requiredContainer = WidthFormatTR;
WidthFormatTD.allowedChildren = [WidthFormat];

WidthFormat.requiredContainer = WidthFormatTD;

这一段的含意就是, 保障各个格局的父元素与子元素别离是什么, 不会呈现乱套的状况

格局中最初的主体:

class WidthFormat extends Block {static register() {Quill.register(WidthFormatTable);
    Quill.register(WidthFormatTR);
    Quill.register(WidthFormatTD);
  }
}


WidthFormat.blotName = 'width-format';
WidthFormat.className = 'width-format';
WidthFormat.tagName = 'div';

register 函数的作用就是在注册以后的 WidthFormat 格局时, 主动注册其余的依赖格局; 防止人多注册屡次

最初咱们新增一个按钮, 来格式化编辑器内容:

  const widthFormatHandle = () => {const editor = getEditor();
  editor.format('width-format', {})
}

展现下成果:

比拟遗憾的是, 同样作为 Block 格局, 这两类是不能兼容的, 也就是说在 width-format 格局中, 不能应用 List , Header , Code 这几项属性
集体吐槽几句, 之前尝试兼容过, 然而在 HTMLdelta 互相转换时被卡主了, 感觉转换的形式没做好

总结

demo 链接: 点此查看

本文介绍了 quill.js 在面临多种需要挑战时须要增加可定制化性能。quill.js 的罕用格局包含 Inline、Block 和 Embeds 三类,而
ContainerBlot 则是创立 Block 类型时罕用的值,具备极高的自由度。心愿本文可能帮忙读者更好地理解和思考富文本编辑的相干问题。

退出移动版