美文网首页quill
富文本编辑器-3-基础文本模块

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

作者: minxuan | 来源:发表于2018-06-23 16:44 被阅读0次

    分割线

    分割线的功能比较简单
    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>前后有间隙,就不像一个正常的段落:


    效果
    html

    正常情况下,应该是一个<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 }
    
    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-基础文本模块

        本文链接:https://www.haomeiwen.com/subject/dwgryftx.html