分割线
分割线的功能比较简单
divider.js
import Quill from 'quill'
let BlockEmbed = Quill.import('blots/block/embed')
class Divider extends BlockEmbed { }
Divider.blotName = 'divider'
Divider.tagName = 'hr'
export default Divider
扩展embed类,设置其blotName和tagName(tagName为hr,给hr设置样式)
index.js
import Quill from 'quill'
import Divider from './divider.js'
Quill.register({
'formats/divider': Divider
})
class ToolbarDivider {
constructor (quill) {
this.quill = quill
this.toolbar = quill.getModule('toolbar')
if (typeof this.toolbar !== 'undefined') {
this.toolbar.addHandler('divider', this.insertDivider)
}
}
insertDivider () {
let range = this.quill.getSelection(true)
this.quill.insertText(range.index, '\n', Quill.sources.USER)
this.quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER)
this.quill.setSelection(range.index + 2, Quill.sources.SILENT)
}
}
Quill.register('modules/divider', ToolbarDivider)
编写ToolbarDivider类,在toolbar模块添加分割线的handler
格式刷
不需要样式类,只需要handler。实现逻辑为:
第一次点击格式刷,如果选中区域有格式,则保存选中区域格式format,设置按键状态为选中。
用户选中一段文字后,将format作用到选择的文本上,并清空format和重置按键状态。
如果点击格式刷后,再次点击,则取消格式刷功能。
export const copyFormat = (quill, copyFormatting) => {
// 点击格式刷,如果有选中区域且有样式,则保存其样式,按键状态改为选中。
// 再次点击,删除样式,按键取消选中。
if (copyFormatting.value === 'un-active') {
let range = quill.getSelection(true)
if (range == null || range.length === 0) return
let format = quill.getFormat(range)
if (Object.keys(format).length === 0) return
setCopyFormatting(quill, copyFormatting, 'active', format)
} else {
setCopyFormatting(quill, copyFormatting, 'un-active', null)
}
}
// 设置copyFormatting: 修改保存的样式、按键状态、粘贴样式的处理程序
export const setCopyFormatting = (quill, copyFormatting, value, format) => {
copyFormatting.value = value
copyFormatting.format = format
let toolbar = quill.getModule('toolbar').container
let brushBtn = toolbar.querySelector('.ql-formatBrush')
if (value === 'active') {
brushBtn.classList.add('ql-btn-active')
quill.on('selection-change', pasteFormatHandler)
} else {
brushBtn.classList.remove('ql-btn-active')
quill.off('selection-change', pasteFormatHandler)
}
function pasteFormatHandler (range, oldRange, source) {
return pasteFormat(range, oldRange, source, quill, copyFormatting)
}
}
// 粘贴样式的处理程序: 如果选中范围且有保存样式,则粘贴样式,并初始化copyFormatting
export const pasteFormat = (range, oldRange, source, quill, copyFormatting) => {
if (range && copyFormatting.format) {
if (range.length === 0) {
} else {
quill.formatText(range.index, range.length + 1, copyFormatting.format)
setCopyFormatting(quill, copyFormatting, 'un-active', null)
}
} else {
// console.log('Cursor not in the editor')
}
}
index.js没有太多内容,这里不再列出。
段落
Quill默认已有段落的格式,但是实现比较简单,点击Enter键,就重新生成一个
标签,由于前后有间隙,就不像一个正常的段落:
正常情况下,应该是一个
标签,内部多个p标签,这样更合理。那么我们就需要两种样式类:Blockquote, BlockquoteItem。其中 Blockquote是容器,要继承于Container。而BlockquoteItem直接继承Block(Quill blots中默认的p元素),但还需要给BlockquoteItem设置一个className,以和普通的p元素区分开。这是因为Quill默认是用tagName来区分不同的类型的,如果设置了className,则优先以className区分。
Blockquote, BlockquoteItem的代码如下所示:// import Parchment from 'parchment' import Quill from 'quill' let Block = Quill.import('blots/block') let Container = Quill.import('blots/container') let Parchment = Quill.import('parchment') class BlockquoteItem extends Block { static formats (domNode) { return domNode.tagName === this.tagName ? undefined : super.formats(domNode) } format (name, value) { if (name === Blockquote.blotName && !value) { // 设置blockquote: 'false',去掉blockquote样式 this.replaceWith(Parchment.create(this.statics.scope)) } else { // 设置blockquote: 'blockquote',blockquote样式 super.format(name, value) } } remove () { // 删除及删除父元素 if (this.prev == null && this.next == null) { this.parent.remove() } else { super.remove() } } replaceWith (name, value) { this.parent.isolate(this.offset(this.parent), this.length()) if (name === this.parent.statics.blotName) { // enter添加blockquote-item时,将其放入一个blockquote中 this.parent.replaceWith(name, value) return this } else { // 点击按键去掉样式时,将父元素展开,该行变成默认的p元素 this.parent.unwrap() return super.replaceWith(name, value) } } } BlockquoteItem.blotName = 'blockquote-item' BlockquoteItem.tagName = 'p' BlockquoteItem.className = 'blockquote-item' class Blockquote extends Container { /* 继承container,没有formats,在toolbar.js中无法切换样式 */ static formats (domNode) { return 'blockquote' } formats () { return { [this.statics.blotName]: this.statics.formats(this.domNode) } } /* 前面插入:如果是BlockquoteItem,直接插入,否则插入到Blockquote外部 */ insertBefore (blot, ref) { if (blot instanceof BlockquoteItem) { super.insertBefore(blot, ref) } else { let index = ref == null ? this.length() : ref.offset(this) let after = this.split(index) after.parent.insertBefore(blot, after) } } /* 如果下个元素与当前元素一样,则合并 */ optimize (context) { super.optimize(context) let next = this.next if (next != null && next.prev === this && next.statics.blotName === this.statics.blotName && next.domNode.tagName === this.domNode.tagName) { next.moveChildren(this) next.remove() } } /* 如果不是一种blot,则将target的内容移动到当前blot中 */ replace (target) { if (target.statics.blotName !== this.statics.blotName) { let item = Parchment.create(this.statics.defaultChild) target.moveChildren(item) this.appendChild(item) } super.replace(target) } } Blockquote.blotName = 'blockquote' Blockquote.scope = Parchment.Scope.BLOCK_BLOT Blockquote.tagName = 'blockquote' Blockquote.defaultChild = 'blockquote-item' Blockquote.allowedChildren = [BlockquoteItem] export { BlockquoteItem, Blockquote as default }
- 设置Blockquote的默认子元素为BlockquoteItem。插入元素时,由于默认target是Block,而不是Blockquote,会调用其replace方法,插入默认子元素BlockquoteItem。
- 按enter时,则会调用insertBefore方法继续插入BlockquoteItem。
- 再次点击工具栏上的段落图标,则当前行的父元素展开,变为默认的p元素。
- 删除子元素时,如果前后都已经没有内容,也会删除段落元素。
index.js没有太多内容,这里不再列出。
撤回和重做
撤回和重做的功能,只需要添加handler就可以实现,所以只有一个index.js
import Quill from 'quill' let Block = Quill.import('blots/block') class Redo extends Block { } Redo.blotName = 'redo' class Undo extends Block { } Undo.blotName = 'undo' Quill.register({ 'formats/redo': Redo, 'formats/undo': Undo }) class UndoRedo { constructor (quill) { this.quill = quill this.toolbar = quill.getModule('toolbar') if (typeof this.toolbar !== 'undefined') { this.toolbar.addHandler('redo', this.redo) this.toolbar.addHandler('undo', this.undo) } } redo () { this.quill.history.redo() } undo () { this.quill.history.undo() } } Quill.register('modules/undo_redo', UndoRedo)
需要注意的是,需要写上没有实际意义的Redo和Undo类,因为Quill在生成Toolbar时,是根据格式列表来绑定handler的。如果注册这两类,则无法用这种模块化的方式实现redo和undo。当然,如果只是为了实现单一业务,你也可以直接在初始化的时候编写handler:
init () { ··· this.options = { modules: { toolbar: { container: this.$refs.toolbar, handlers: { 'undo': () => { undo(this.quill) }, 'redo': () => { redo(this.quill) }, ... } }, ... }, ... }