共计 5680 个字符,预计需要花费 15 分钟才能阅读完成。
前言
鉴于各种繁冗的需要,quill.js
编辑器也面临着各种挑战,例如咱们须要增加“table”布局款式以适应邮件发送格局,手动扩大表情符号性能等等。本文将对这些可定制化性能进行解说和实现。
辨别 format 和 module
首先须要明确的是,咱们应该分明本人所需的扩大具体是什么?
比方想要新增一个 自定义 emoji, 那么设想一下步骤:
- 点击工具栏
- 弹出弹窗或者对应的 popover
- 在 2 中选中 emoji
这些步骤是一种常见的增加流程。
咱们须要明确的是,增加自定义表情符号必然须要一个相应的格局。
本文将以 format
为例,对此进行具体解说。
quill 的格局类型
说起 quill 的格局类型, 他的罕用格局能够分成 3 类:
- Inline
常见的有Bold
,Color
,Font
等等, 不占据一行的标签, 相似于 html 里 span 的个性, 是一个行内款式,Inline
格局之间能够相互影响 - Block
增加Block
款式, 必然会占据一整行, 并且Block
款式之间不能兼容(共存), 常见的有List
,Header
,Code Block
等等 - Embeds
媒体文件, 常见的有Image
,Video
,Formula
, 这类格局扩大的比拟少, 然而本次要加的emoji
然而这种格局
自定义款式
新增 emoji.ts 文件来存储格局, 对于他的类型, 咱们抉择 Embeds
格局, 应用这种格局有以下起因:
- 他是一种独特的类型, 不能和色彩, 字体大小等等用在一起
- 须要和字体并列, 所以也不能是
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
在上文讲到了 format
和 value
的作用, 咱们也能够对于 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 上的信息
其余格局
下面咱们讲述了三个常见的格局: Inline
、Embeds
、Block
, 其实在 quill
还有一些非凡的 blot
:
如: TextBlot
、ContainerBlot
、ScrollBlot
其中 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
套上, 再加上一个款式 width
和 text-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
标签, 那么同样也会须要 tr
和 rd
:
也是相似的创立办法:
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
这几项属性
集体吐槽几句, 之前尝试兼容过, 然而在 HTML
和 delta
互相转换时被卡主了, 感觉转换的形式没做好
总结
demo 链接: 点此查看
本文介绍了 quill.js 在面临多种需要挑战时须要增加可定制化性能。quill.js 的罕用格局包含 Inline、Block 和 Embeds 三类,而
ContainerBlot 则是创立 Block 类型时罕用的值,具备极高的自由度。心愿本文可能帮忙读者更好地理解和思考富文本编辑的相干问题。