2

ckeditor

vue-quill-editor

粗略了解一下编译器的底层



















ckeditor的使用

<template>
  <div :style="containerStyle" class="rich-text-editor">
    <div v-if="textInput" class="rich-text-editor-wrap">
      <div v-show="!isFocus && !isFullScreen" class="preview-container">
        <el-input
          v-model="textValue"
          :placeholder="placeholder"
          @focus="showEditor"
        ></el-input>
      </div>
      <div v-show="isFocus || isFullScreen">
        <textarea :id="editorId"></textarea>
      </div>
    </div>
    <div v-else class="rich-text-editor-wrap">
      <textarea :id="editorId"></textarea>
    </div>
  </div>
</template>
<script>
import { CKEDITOR } from ''@/util/ckeditor.js''
let count = 0
export default {
  props: {
    width: {
      type: String,
      default: ''100%''
    },
    height: {
      type: String,
      default: ''100%''
    },
    textInput: {
      type: Boolean,
      default: false
    },
    autoSaveKey: {
      type: String,
      default: ''''
    },
    value: {
      type: String,
      default: ''''
    },
    // 工具栏:Basic、Full、Common
    toolbar: {
      type: [String, Array],
      default: ''Common''
    },
    placeholder: {
      type: String,
      default: ''''
    },
    readonly: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    editor: {
      handler (val) {
        if (val === null) {
          this.initRichText()
        }
      }
    },
    value (val) {
      if (this.itemValue !== val) {
        this.itemValue = val
        this.setValue()
      }
    }
  },
  data () {
    return {
      editorId: ''rich-text-editor-'' + ++count,
      itemValue: this.value,
      editor: null,
      copyPopover: null,
      copyValue: '''',
      isFocus: false,
      textValue: '''',
      // 是否全屏
      isFullScreen: false
    }
  },
  computed: {
    containerStyle () {
      let style = {}
      if (this.width.indexOf(''%'') < 0 && !this.textInput) {
        style.minWidth = this.width
      }
      if (this.height.indexOf(''%'') < 0 && !this.textInput) {
        style.minHeight = this.height
      }
      return style
    }
  },
  methods: {
    initRichText () {
      let $ = window.jQuery
      let config = {
        height: this.height,
        width: this.width,
        autoSaveKey: this.autoSaveKey,
        placeholder: this.placeholder,
        readOnly: this.readonly
      }
      if (this.isTextInput === true) {
        config.startupFocus = true
      }
      if (this.toolbar !== ''Common'') {
        config.toolbar = this.toolbar
      }
      let editor = CKEDITOR.replace(this.editorId, config)
      this.editor = editor
      let self = this
      if (this.editor && this.editor !== null) {
        // CKEDITOR.on(''instanceReady'', function (e) {
        // 设置值
        // 加载完毕事件
        editor.on(''loaded'', () => {
          this.$emit(''loaded'', editor)
        })
        // 设置值
        if (this.itemValue) {
          this.setValue()
        }
        // 内容改变事件
        editor.on(''change'', function (evt) {
          let value = evt.editor.getData()
          let textValue = evt.editor.document.getBody().getText()
          self.handleChange(value, textValue)
        })
        // 粘贴事件
        editor.on(''paste'', function (evt) {
          let data = evt.data
          let type = data.type
          let value = data.dataValue
          if (type === ''html'') {
            // html内容
            try {
              let editor = evt.editor
              let $obj = $(value)
              if ($obj.is(''img'')) {
                let src = $obj.attr(''src'')
                let maxWidth = window.editorImageMaxWidth
                $(''<img/>'')
                  .attr(''src'', src)
                  .load(function () {
                    let width = this.width
                    if (maxWidth && width > maxWidth) {
                      $obj.css(''width'', maxWidth + ''px'')
                      $obj.css(''height'', ''auto'')
                    }
                    editor.insertHtml($obj.prop(''outerHTML''))
                  })
                return false
              }
            } catch (e) {
              console.error(e)
            }
          }
          value && self.createCopyPopover(value)
        })
        // 键盘输入事件
        editor.on(''key'', function (evt) {
          self.removeCopyPopover()
        })
        // 最大化最小化事件
        editor.on(''maximize'', function (evt) {
          self.isFullScreen = !self.isFullScreen
          if (window.fullScreenEditor) {
            window.fullScreenEditor = null
          } else {
            window.fullScreenEditor = evt.editor
          }
        })
        // 失去焦点事件
        editor.on(''blur'', function (evt) {
          // self.validate(''blur'')
          if (
            window.fullScreenEditor == null ||
            window.fullScreenEditor === undefined
          ) {
            self.$emit(''blur'')
          }
          self.isFocus = false
        })
        // 获得焦点事件
        editor.on(''focus'', function (evt) {
          self.isFocus = true
        })
        // })
      }
    },
    showEditor () {
      this.isFocus = true
      this.$nextTick(() => {
        if (this.editor && this.editor !== null) {
          this.editor.focus()
        }
      })
    },
    木易楊@:
handleChange (value, textValue) {
      this.itemValue = value
      this.textValue = textValue
      this.validate(''change'')
      // 判断是否为全部空格
      let newValue = ''''
      if (
        !textValue.trim() &&
        !value.includes(''<img'') &&
        !value.includes(''<table'')
      ) {
        newValue = ''''
      } else {
        newValue = value
      }
      this.$emit(''input'', value)
      this.$emit(''change'', newValue)
    },
    setValue () {
      this.editor.setData(this.itemValue)
      this.textValue = this.getText()
    },

木易楊@:
getText () {
      if (!this.itemValue) {
        return ''''
      }
      if (this.editor.document) {
        try {
          return this.editor.document.getBody().getText()
        } catch (e) {
          console.info(e)
        }
      }
      let html = this.itemValue
      if (!html.startsWith(''<'')) {
        html = ''<div>'' + html + ''</div>''
      }
      return window.jQuery(html).text()
    },
    getEditorDom () {
      return document.getElementById(''cke_'' + this.editorId)
    },
    getOffsetTop (obj) {
      obj = obj || this.getEditorDom()
      let offsetTop = obj.offsetTop || 0
      return offsetTop === 0
        ? obj.parentNode
          ? this.getOffsetTop(obj.parentNode)
          : 0
        : offsetTop
    },
    getOffsetLeft (obj) {
      obj = obj || this.getEditorDom()
      let offsetLeft = obj.offsetLeft || 0
      return offsetLeft === 0
        ? obj.parentNode
          ? this.getOffsetLeft(obj.parentNode)
          : 0
        : offsetLeft
    },
    getCenterPosition () {
      let offsetTop = this.getOffsetTop()
      let getOffsetLeft = this.getOffsetLeft()
      let height = this.getEditorDom().offsetHeight
      let width = this.getEditorDom().offsetWidth
      return {
        top: offsetTop + height / 2,
        left: getOffsetLeft + width / 2
      }
    },
getEditorDocument () {
      return this.editor.document.$
    },
    createCopyPopover (value) {
      this.removeCopyPopover()
      let div = document.createElement(''div'')
      div.innerHTML = `
          <ul>
            <li class="source-text-li"><i class="source-text"></i><span>保留源格式</span></li>
            <li class="plain-text-li"><i class="plain-text"></i><span>保留纯文本</span></li>
          </ul>
        `
      div.className = ''cke_popover''
      let ckeInner = this.getEditorDom().querySelector(''.cke_inner'')
      div.style.position = ''absolute''
      div.style.top = ''50%''
      div.style.left = ''50%''
      ckeInner.appendChild(div)
      this.copyPopover = div
      this.copyValue = value
      // 监听事件
      let self = this
      div
        .querySelector(''.source-text-li'')
        .addEventListener(''click'', function () {
          self.removeCopyPopover()
        })
      div.querySelector(''.plain-text-li'').addEventListener(''click'', function () {
        self.removeCopyPopover()
        self.editor.execCommand(''undo'')
        div = document.createElement(''div'')
        div.innerHTML = self.copyValue
        self.editor.insertHtml(div.innerText)
      })
    },
    removeCopyPopover () {
      if (this.copyPopover != null) {
        this.copyPopover.remove()
        this.copyPopover = null
      }
    },
    getParentVNode (vnode, tagName) {
      if (!vnode) {
        vnode = this
      }
      if (!vnode.$vnode || !vnode.$vnode.componentOptions) {
        return null
      }
      let tag = vnode.$vnode.componentOptions.tag
      if (tag === tagName) {
        return vnode
      }
      if (!vnode.$parent) {
        return null
      }
      return this.getParentVNode(vnode.$parent, tagName)
    },
    getElFormItem (vnode) {
      return this.getParentVNode(null, ''el-form-item'')
    },
    getElForm (vnode) {
      return this.getParentVNode(null, ''el-form'')
    },
    validate (eventName) {
      let elFormItem = this.getElFormItem()
      if (elFormItem) {
        let elForm = this.getElForm(elFormItem)
        let prop = elFormItem.prop
        if (prop && elForm.rules) {
          for (let name in elForm.rules) {
            if (name === prop) {
              let isNeddValidate = false
              elForm.rules[name].forEach(rule => {
                if (rule.trigger === eventName) {
                  isNeddValidate = true
                }
              })
              isNeddValidate && elForm.validateField(prop)
            }
          }
        }
      }
    }
  },
  mounted () {
    this.initRichText()
  }
}
</script>
<style lang="less">

/* 去除路径显示 */
.cke_path {
  display: none;
}

.cke_popover {
  font-size: 13px;

  ul {
    background: #fff;
    list-style: none;
    border: 1px solid #ccc;
    border-bottom: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;

    li {
      padding: 5px;
      border-bottom: 1px solid #ccc;
      cursor: pointer;
      height: 20px;
      line-height: 20px;

      > span {
        vertical-align: middle;
      }

      i {
        display: inline-block;
        width: 15px;
        height: 15px;
        vertical-align: middle;
        background-size: 15px 15px;
        margin-right: 5px;
      }

      i.plain-text {
        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAkElEQVQ4T+3TPQ4BYRCH8d8egiO4gUqzhUajcwiVOIQLyN5CLRqi1WodgUvIJitZrx0iS7fTzswzn/9MS8ta5ksBG/Q+QG+YPWJSwAUF1shxrALHOGCJOQYR4IQprgGgjy2GEaCstK+cTR2Urgl2EaA+fgR4WtG7K3SA5j94+bG/LXGFUVLujEXaws/F9LU47xkkIBGRZtDbAAAAAElFTkSuQmCC);
      }

      i.source-text {
        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHUlEQVQ4T6XTsSuGURTH8c+LFUUZKIsyCKNdSozYTUoZRBZKIrEoWSx2k8IqKX+CEItJGSVMDOjqvk/X7cHw3ul5zjnP957fOb+nosZTqfF7KeAJW9iP0Do0xudXfKABGxhFc8ilgD0s4THp6rsIz0msFfNYzgF9uMokreITa1l8CGc5oBfXSWGQ8BDfO6KEaroHNzmgH5cJYAyLGMA4jpJcUZvOoGgrFh5iBvc4x3ACKJWQAjqxgikcYALduIuQUkA6xLDOerzEdc1hGwsRUNSmEqq6wq5PMZi0fIt2tOENf85gBF3YTQCzaMEFjn8DrGMnM1KZ04ORNjGdrzFY+SQ67D3OoCkSwiyClcOZjE4MHf2wctlt/8Zq/hu/ABKzNxFN8ClKAAAAAElFTkSuQmCC);
      }
    }

    li:hover {
      background-color: #eee;
    }
  }
}
</style>

使用

<rich-text-editor width="81%" v-model="formModel.march" :toolbar="toolbar"></rich-text-editor>

data(){
  return{
    toolbar: [
        { name: ''basicstyles'', items: [''Bold'', ''Italic'', ''Underline'', ''Strike'', ''Subscript'', ''Superscript'', ''RemoveFormat''] },
        { name: ''editing'', items: [''Find'', ''Replace'', ''SelectAll''] },
        { name: ''document'', items: [''Source'', ''DocProps'', ''Preview''] },
        { name: ''paragraph'', items: [''NumberedList'', ''Outdent'', ''Indent'', ''Blockquote'', ''CreateDiv'', ''JustifyLeft'', ''JustifyCenter'', ''JustifyRight'', ''JustifyBlock'', ''BidiLtr'', ''BidiRtl''] },
        { name: ''forms'', items: [ ''Form'', ''Checkbox'', ''Radio'', ''TextField'', ''Textarea'', ''Select'' ] },
        { name: ''links'', items: [ ''Link'', ''Unlink'', ''Anchor'', ''uploadbutton'' ] },
        { name: ''insert'', items: [ ''Table'', ''HorizontalRule'', ''Smiley'', ''SpecialChar'', ''PageBreak'' ] },
        { name: ''styles'', items: [ ''Styles'', ''Format'', ''Font'', ''FontSize'' ] },
        { name: ''clipboard'', items: [''Cut'', ''Copy'', ''Paste'', ''PasteText'', ''Undo'', ''Redo''] },
        { name: ''colors'', items: [''TextColor'', ''BGColor''] }
      ]
  }
}

CKEditor (Toolbar Definition)工具栏自定义配置

工具栏的定义英汉对照说明:

Source = 源码模式  
 
Save = 保存(提交表单)  
NewPage = 新建  
Preview = 预览  
- = 分割线  
Templates = 模板  
Cut = 剪切  
Copy = 复制  
Paste = 粘贴  
PasteText = 粘贴为无格式文本  
PasteFromWord = 从 MS WORD 粘贴  
  
Print = 打印  
SpellChecker = 拼写检查  
Scayt = 即时拼写检查  
Undo = 撤销  
Redo = 重做  

Find = 查找  
Replace = 替换  
 
SelectAll = 全选  
RemoveFormat = 清除格式  
Form = 表单  
Checkbox = 复选框  
Radio = 单选框  
TextField = 单行文本  
Textarea = 多行文本  
Select = 列表/菜单  
Button = 按钮  
ImageButton = 图片按钮  
HiddenField = 隐藏域  

Bold = 加粗  
Italic = 倾斜  
Underline = 下划线  
Strike = 删除线  
 
Subscript = 下标  
Superscript = 上标  
NumberedList = 编号列表  
BulletedList = 项目列表  
  
Outdent = 减少缩进量  
Indent = 增加缩进量  
Blockquote = 块引用  
CreateDiv = 创建DIV容器  
JustifyLeft = 左对齐  
JustifyCenter = 居中  
JustifyRight = 右对齐  
JustifyBlock = 两端对齐  
BidiLtr = 文字方向从左到右  
BidiRtl = 文字方向从右到左  
Link = 插入/编辑超链接(上传文件)  
Unlink = 取消超链接  
Anchor = 插入/编辑锚点链接  
Image = 图像(上传)  
Flash = 动画(上传)  
Table = 表格  
HorizontalRule = 插入水平线  
Smiley = 插入表情  
SpecialChar = 插入特殊符号  
PageBreak = 插入分页符  
 
Styles = 样式快捷方式  
Format = 文本格式  
Font = 字体  
FontSize = 文字大小  
TextColor = 文字颜色  
BGColor = 背景颜色  
Maximize = 全屏编辑模式  
ShowBlocks = 显示区块  
 
About = 显示关于

本文内容图片来自https://www.zhihu.com/questio...


HappyCodingTop
526 声望847 粉丝

Talk is cheap, show the code!!


引用和评论

0 条评论