VueJS 集成 Medium Editor (自定义编辑器按钮)
in 码农技术宅 with 0 comment

VueJS 集成 Medium Editor (自定义编辑器按钮)

in 码农技术宅 with 0 comment

0x00 前言

VueJS 社区里面关于富文本编辑器的集成也不少了,但是之前小调研了一下,基本上就是 quill,medium-editor,因为之前用 AngularJS 用过 medium-editor,并且需要自定义某些按钮,而且最好还是选中弹出式的,所以就决定用 medium-editor

社区里面 star 较多的就是这个了: vue-medium-editor,但是打开它官网,看了看文档,越看越别扭,来看看它用法:

<!-- index.html -->
<medium-editor :text='myText' :options='options' custom-tag='h2' v-on:edit='applyTextEdit'>

gosh,传这么多参数,我只想要一个简单的 editor 啊

打开源码一看,就 62 行,所以决定自己动手来一个简单点的

0x01 最简版

最简版,其实就在 vue 组件中实例化一下 medium-editor 就可以了

<template>
  <div class="textEditor" @input="handleInput">
  </div>
</template>
<script>
/* eslint-disable no-new */
import MediumEditor from 'medium-editor'
export default {
  props: {
    value: String,
    options: {
      type: Object,
      default: () => ({})
    }
  },
  data () {
    return {
      editor: null // 用来存放 editor 
    }
  },
  watch: {
    // refer: https://github.com/FranzSkuffka/vue-medium-editor/blob/master/index.js
    value (newVal, oldVal) {
      if (newVal !== this.$el.innerHTML) { // 用 $el.innerHTML 来解决 v-html 的光标跳到行首的问题
        this.$el.innerHTML = newVal || ''
      }
    }
  },
  methods: {
    handleInput (e) {
      this.$emit('input', e.target.innerHTML)
    }
  },
  mounted () {
    // 处理初始值的情况
    this.$el.innerHTML = this.value
    // 这里当然可以自定义 options 啦
    this.editor = new MediumEditor(this.$el, Object.assign({}, this.options))
    // medium-editor 的 api,监听内容改变化
    this.editor.subscribe('editableInput', this.handleInput)
  },
  beforeDestroy () {
    this.editor.unsubscribe('editableInput', this.handleInput)
    this.editor.destroy()
  }
}
</script>

完成~,是不是很简单~~哈哈,最简版是一个 v-html 控制的,但是会有自动跳转到首行的问题,所以这里是最终版,细节问题看注释啦

0x02 用法

咋用呢?很简单,在其他组件中这样:

<text-editor v-model="vm.richText"></text-editor>

当然 你首先得安装 medium-editor的 js 和 css了

0x03 自定义 button

下面是我项目中用到的自定义 button 的相关代码,是一个 buttonBuilder:

import MediumEditor from 'medium-editor'
import rangy from 'rangy/lib/rangy-core.js'
import 'rangy/lib/rangy-classapplier'
import 'rangy/lib/rangy-highlighter'
import 'rangy/lib/rangy-selectionsaverestore'
import 'rangy/lib/rangy-textrange'
import 'rangy/lib/rangy-serializer'
const pHash = {
  p1: { name: 'p1', class: 'fs-36' },
  p2: { name: 'p2', class: 'fs-30' },
  p3: { name: 'p3', class: 'fs-24' },
  p4: { name: 'p4', class: 'fs-18' },
  p5: { name: 'p5', class: 'fs-14' },
  p6: { name: 'p6', class: 'fs-12' }
}
function pButtonCreator (p) {
  return MediumEditor.Extension.extend({
    name: p.name,
    init: function () {
      this.classApplier = rangy.createClassApplier(p.class, {
        elementTagName: 'span',
        normalize: false
      })
      this.button = this.document.createElement('button')
      this.button.classList.add('medium-editor-action')
      this.button.innerHTML = p.name
      this.button.title = p.class
      this.on(this.button, 'click', this.handleClick.bind(this))
    },
    getButton: function () {
      return this.button
    },
    clearFontSize: function () {
      MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) {
        if (el.nodeName.toLowerCase() === 'span' && el.hasAttribute('class')) {
          el.removeAttribute('class')
        }
      })
    },
    handleClick: function (event) {
      this.clearFontSize()
      this.classApplier.toggleSelection()
      // Ensure the editor knows about an html change so watchers are notified
      // ie: <textarea> elements depend on the editableInput event to stay synchronized
      this.base.checkContentChanged()
    }
  })
}
export default {
  P1: pButtonCreator(pHash['p1']),
  P2: pButtonCreator(pHash['p2']),
  P3: pButtonCreator(pHash['p3']),
  P4: pButtonCreator(pHash['p4']),
  P5: pButtonCreator(pHash['p5']),
  P6: pButtonCreator(pHash['p6'])
}

简单来说就是给选中的文字加一些 class (上面是 fs-xx 之类的),其中需要引一个鼠标选中的库 rangy,挺烦人的也是,然后在 text-editor 中这样用:

先实例化

import ButtonBuilder from './buttonBuilder'
var editorOptions = {
  toolbar: {
    buttons: ['bold', 'italic', 'underline', 'removeFormat', 'p3', 'p4', 'p5', 'p6']
  },
  buttonLabels: 'fontawesome', // use font-awesome icons for other buttons
  extensions: {
    p3: new ButtonBuilder.P3(),
    p4: new ButtonBuilder.P4(),
    p5: new ButtonBuilder.P5(),
    p6: new ButtonBuilder.P6()
  },
  placeholder: false
}

再放到 editor 上

this.editor = new MediumEditor(this.$el, Object.assign({}, editorOptions, this.options))

当然上面实例化的步骤不一定要写到这个组件里面,配置 options 也可以从组件外传入

0x04 细节和坑

  1. 这里用到了 v-model 的自定义实现,详见官方文档:v-model
    简单来说呢就是 props: value ,和 this.$emit('input', model) 就可以实现在组件中模拟 v-model 啦
  2. 多个 editor 使用的自定义button 实例的问题。由于我自己应用的时候有两个挨着的 <text-editor>,用的上面的代码会导致两个 editor 实例用的是同一个 button 实例,这会导致一个很严重的问题:即编辑下面编辑器的内容,可能会修改的上面的编辑器!!
    要解决这个也很简单,修改这一行:
this.editor = new MediumEditor(this.$el, Object.assign({}, _.cloneDeep(editorOptions), this.options))

将自定义的 options 深复制一下,这里借助了 lodash 的函数。

0xFF 参考链接

Comments are closed.