分割线
分割线的功能比较简单
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键,就重新生成一个<blockquote>标签,由于<blockquote>前后有间隙,就不像一个正常的段落:
正常情况下,应该是一个<blockquote>标签,内部多个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) },
...
}
},
...
},
...
}