/* 编辑器构造函数 */ import $ from '../util/dom-core.js' import _config from '../config.js' import Menus from '../menus/index.js' import Text from '../text/index.js' import Command from '../command/index.js' import selectionAPI from '../selection/index.js' import UploadImg from './upload/upload-img.js' import { arrForEach, objForEach } from '../util/util.js' import { getRandom } from '../util/util.js' // id,累加 let editorId = 1 // 构造函数 function Editor(toolbarSelector, textSelector) { if (toolbarSelector == null) { // 没有传入任何参数,报错 throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档') } // id,用以区分单个页面不同的编辑器对象 this.id = 'wangEditor-' + editorId++ this.toolbarSelector = toolbarSelector this.textSelector = textSelector // 自定义配置 this.customConfig = {} } // 修改原型 Editor.prototype = { constructor: Editor, // 初始化配置 _initConfig: function () { // _config 是默认配置,this.customConfig 是用户自定义配置,将它们 merge 之后再赋值 let target = {} this.config = Object.assign(target, _config, this.customConfig) // 将语言配置,生成正则表达式 const langConfig = this.config.lang || {} const langArgs = [] objForEach(langConfig, (key, val) => { // key 即需要生成正则表达式的规则,如“插入链接” // val 即需要被替换成的语言,如“insert link” langArgs.push({ reg: new RegExp(key, 'img'), val: val }) }) this.config.langArgs = langArgs }, // 初始化 DOM _initDom: function () { const toolbarSelector = this.toolbarSelector const $toolbarSelector = $(toolbarSelector) const textSelector = this.textSelector const config = this.config const zIndex = config.zIndex // 定义变量 let $toolbarElem, $textContainerElem, $textElem, $children if (textSelector == null) { // 只传入一个参数,即是容器的选择器或元素,toolbar 和 text 的元素自行创建 $toolbarElem = $('
') $textContainerElem = $('
') // 将编辑器区域原有的内容,暂存起来 $children = $toolbarSelector.children() // 添加到 DOM 结构中 $toolbarSelector.append($toolbarElem).append($textContainerElem) // 自行创建的,需要配置默认的样式 $toolbarElem.css('background-color', '#f1f1f1') .css('border', '1px solid #ccc') $textContainerElem.css('border', '1px solid #ccc') .css('border-top', 'none') .css('height', '300px') } else { // toolbar 和 text 的选择器都有值,记录属性 $toolbarElem = $toolbarSelector $textContainerElem = $(textSelector) // 将编辑器区域原有的内容,暂存起来 $children = $textContainerElem.children() } // 编辑区域 $textElem = $('
') $textElem.attr('contenteditable', 'true') .css('width', '100%') .css('height', '100%') // 初始化编辑区域内容 if ($children && $children.length) { $textElem.append($children) } else { $textElem.append($('


')) } // 编辑区域加入DOM $textContainerElem.append($textElem) // 设置通用的 class $toolbarElem.addClass('w-e-toolbar') $textContainerElem.addClass('w-e-text-container') $textContainerElem.css('z-index', zIndex) $textElem.addClass('w-e-text') // 添加 ID const toolbarElemId = getRandom('toolbar-elem') $toolbarElem.attr('id', toolbarElemId) const textElemId = getRandom('text-elem') $textElem.attr('id', textElemId) // 记录属性 this.$toolbarElem = $toolbarElem this.$textContainerElem = $textContainerElem this.$textElem = $textElem this.toolbarElemId = toolbarElemId this.textElemId = textElemId // 记录输入法的开始和结束 let compositionEnd = true $textContainerElem.on('compositionstart', () => { // 输入法开始输入 compositionEnd = false }) $textContainerElem.on('compositionend', () => { // 输入法结束输入 compositionEnd = true }) // 绑定 onchange $textContainerElem.on('click keyup', () => { // 输入法结束才出发 onchange compositionEnd && this.change && this.change() }) $toolbarElem.on('click', function () { this.change && this.change() }) //绑定 onfocus 与 onblur 事件 if(config.onfocus || config.onblur){ // 当前编辑器是否是焦点状态 this.isFocus = false $(document).on('click', (e) => { //判断当前点击元素是否在编辑器内 const isChild = $textElem.isContain($(e.target)) //判断当前点击元素是否为工具栏 const isToolbar = $toolbarElem.isContain($(e.target)) const isMenu = $toolbarElem[0] == e.target ? true : false if (!isChild) { //若为选择工具栏中的功能,则不视为成blur操作 if(isToolbar && !isMenu){ return } if(this.isFocus){ this.onblur && this.onblur() } this.isFocus = false }else{ if(!this.isFocus){ this.onfocus && this.onfocus() } this.isFocus = true } }) } }, // 封装 command _initCommand: function () { this.cmd = new Command(this) }, // 封装 selection range API _initSelectionAPI: function () { this.selection = new selectionAPI(this) }, // 添加图片上传 _initUploadImg: function () { this.uploadImg = new UploadImg(this) }, // 初始化菜单 _initMenus: function () { this.menus = new Menus(this) this.menus.init() }, // 添加 text 区域 _initText: function () { this.txt = new Text(this) this.txt.init() }, // 初始化选区,将光标定位到内容尾部 initSelection: function (newLine) { const $textElem = this.$textElem const $children = $textElem.children() if (!$children.length) { // 如果编辑器区域无内容,添加一个空行,重新设置选区 $textElem.append($('


')) this.initSelection() return } const $last = $children.last() if (newLine) { // 新增一个空行 const html = $last.html().toLowerCase() const nodeName = $last.getNodeName() if ((html !== '
' && html !== '') || nodeName !== 'P') { // 最后一个元素不是


,添加一个空行,重新设置选区 $textElem.append($('


')) this.initSelection() return } } this.selection.createRangeByElem($last, false, true) this.selection.restoreSelection() }, // 绑定事件 _bindEvent: function () { // -------- 绑定 onchange 事件 -------- let onChangeTimeoutId = 0 let beforeChangeHtml = this.txt.html() const config = this.config // onchange 触发延迟时间 let onchangeTimeout = config.onchangeTimeout onchangeTimeout = parseInt(onchangeTimeout, 10) if (!onchangeTimeout || onchangeTimeout <= 0) { onchangeTimeout = 200 } const onchange = config.onchange if (onchange && typeof onchange === 'function'){ // 触发 change 的有三个场景: // 1. $textContainerElem.on('click keyup') // 2. $toolbarElem.on('click') // 3. editor.cmd.do() this.change = function () { // 判断是否有变化 let currentHtml = this.txt.html() if (currentHtml.length === beforeChangeHtml.length) { // 需要比较每一个字符 if (currentHtml === beforeChangeHtml) { return } } // 执行,使用节流 if (onChangeTimeoutId) { clearTimeout(onChangeTimeoutId) } onChangeTimeoutId = setTimeout(() => { // 触发配置的 onchange 函数 onchange(currentHtml) beforeChangeHtml = currentHtml }, onchangeTimeout) } } // -------- 绑定 onblur 事件 -------- const onblur = config.onblur if (onblur && typeof onblur === 'function') { this.onblur = function () { const currentHtml = this.txt.html() onblur(currentHtml) } } // -------- 绑定 onfocus 事件 -------- const onfocus = config.onfocus if (onfocus && typeof onfocus === 'function') { this.onfocus = function () { onfocus() } } }, // 创建编辑器 create: function () { // 初始化配置信息 this._initConfig() // 初始化 DOM this._initDom() // 封装 command API this._initCommand() // 封装 selection range API this._initSelectionAPI() // 添加 text this._initText() // 初始化菜单 this._initMenus() // 添加 图片上传 this._initUploadImg() // 初始化选区,将光标定位到内容尾部 this.initSelection(true) // 绑定事件 this._bindEvent() }, // 解绑所有事件(暂时不对外开放) _offAllEvent: function () { $.offAll() } } export default Editor