富文本编辑器-3-基础文本模块

分割线

分割线的功能比较简单
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键,就重新生成一个

标签,由于
前后有间隙,就不像一个正常的段落:


效果

html

正常情况下,应该是一个

标签,内部多个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 }
  1. 设置Blockquote的默认子元素为BlockquoteItem。插入元素时,由于默认target是Block,而不是Blockquote,会调用其replace方法,插入默认子元素BlockquoteItem。
  2. 按enter时,则会调用insertBefore方法继续插入BlockquoteItem。
  3. 再次点击工具栏上的段落图标,则当前行的父元素展开,变为默认的p元素。
  4. 删除子元素时,如果前后都已经没有内容,也会删除段落元素。

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) },
                 ...
              }
            },
            ...
          },
         ...
      }

你可能感兴趣的:(富文本编辑器-3-基础文本模块)