/* 编辑区域 */ import $ from '../util/dom-core.js' import { getPasteText, getPasteHtml, getPasteImgs } from '../util/paste-handle.js' import { UA, isFunction, replaceHtmlSymbol } from '../util/util.js' // 获取一个 elem.childNodes 的 JSON 数据 function getChildrenJSON($elem) { const result = [] const $children = $elem.childNodes() || [] // 注意 childNodes() 可以获取文本节点 $children.forEach(curElem => { let elemResult const nodeType = curElem.nodeType // 文本节点 if (nodeType === 3) { elemResult = curElem.textContent elemResult = replaceHtmlSymbol(elemResult) } // 普通 DOM 节点 if (nodeType === 1) { elemResult = {} // tag elemResult.tag = curElem.nodeName.toLowerCase() // attr const attrData = [] const attrList = curElem.attributes || {} const attrListLength = attrList.length || 0 for (let i = 0; i < attrListLength; i++) { const attr = attrList[i] attrData.push({ name: attr.name, value: attr.value }) } elemResult.attrs = attrData // children(递归) elemResult.children = getChildrenJSON($(curElem)) } result.push(elemResult) }) return result } // 构造函数 function Text(editor) { this.editor = editor } // 修改原型 Text.prototype = { constructor: Text, // 初始化 init: function () { // 绑定事件 this._bindEvent() }, // 清空内容 clear: function () { this.html('


') }, // 获取 设置 html html: function (val) { const editor = this.editor const $textElem = editor.$textElem let html if (val == null) { html = $textElem.html() // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 ​ ,这里替换掉 html = html.replace(/\u200b/gm, '') return html } else { $textElem.html(val) // 初始化选取,将光标定位到内容尾部 editor.initSelection() } }, // 获取 JSON getJSON: function () { const editor = this.editor const $textElem = editor.$textElem return getChildrenJSON($textElem) }, // 获取 设置 text text: function (val) { const editor = this.editor const $textElem = editor.$textElem let text if (val == null) { text = $textElem.text() // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 ​ ,这里替换掉 text = text.replace(/\u200b/gm, '') return text } else { $textElem.text(`

${val}

`) // 初始化选取,将光标定位到内容尾部 editor.initSelection() } }, // 追加内容 append: function (html) { const editor = this.editor const $textElem = editor.$textElem $textElem.append($(html)) // 初始化选取,将光标定位到内容尾部 editor.initSelection() }, // 绑定事件 _bindEvent: function () { // 实时保存选取 this._saveRangeRealTime() // 按回车建时的特殊处理 this._enterKeyHandle() // 清空时保留


this._clearHandle() // 粘贴事件(粘贴文字,粘贴图片) this._pasteHandle() // tab 特殊处理 this._tabHandle() // img 点击 this._imgHandle() // 拖拽事件 this._dragHandle() }, // 实时保存选取 _saveRangeRealTime: function () { const editor = this.editor const $textElem = editor.$textElem // 保存当前的选区 function saveRange(e) { // 随时保存选区 editor.selection.saveRange() // 更新按钮 ative 状态 editor.menus.changeActive() } // 按键后保存 $textElem.on('keyup', saveRange) $textElem.on('mousedown', e => { // mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区 $textElem.on('mouseleave', saveRange) }) $textElem.on('mouseup', e => { saveRange() // 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件 $textElem.off('mouseleave', saveRange) }) }, // 按回车键时的特殊处理 _enterKeyHandle: function () { const editor = this.editor const $textElem = editor.$textElem function insertEmptyP ($selectionElem) { const $p = $('


') $p.insertBefore($selectionElem) editor.selection.createRangeByElem($p, true) editor.selection.restoreSelection() $selectionElem.remove() } // 将回车之后生成的非

的顶级标签,改为

function pHandle(e) { const $selectionElem = editor.selection.getSelectionContainerElem() const $parentElem = $selectionElem.parent() if ($parentElem.html() === '
') { // 回车之前光标所在一个

.....

,忽然回车生成一个空的


// 而且继续回车跳不出去,因此只能特殊处理 insertEmptyP($selectionElem) return } if (!$parentElem.equal($textElem)) { // 不是顶级标签 return } const nodeName = $selectionElem.getNodeName() if (nodeName === 'P') { // 当前的标签是 P ,不用做处理 return } if ($selectionElem.text()) { // 有内容,不做处理 return } // 插入

,并将选取定位到

,删除当前标签 insertEmptyP($selectionElem) } $textElem.on('keyup', e => { if (e.keyCode !== 13) { // 不是回车键 return } // 将回车之后生成的非

的顶级标签,改为

pHandle(e) }) //

回车时 特殊处理 function codeHandle(e) { const $selectionElem = editor.selection.getSelectionContainerElem() if (!$selectionElem) { return } const $parentElem = $selectionElem.parent() const selectionNodeName = $selectionElem.getNodeName() const parentNodeName = $parentElem.getNodeName() if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') { // 不符合要求 忽略 return } if (!editor.cmd.queryCommandSupported('insertHTML')) { // 必须原生支持 insertHTML 命令 return } // 处理:光标定位到代码末尾,联系点击两次回车,即跳出代码块 if (editor._willBreakCode === true) { // 此时可以跳出代码块 // 插入

,并将选取定位到

const $p = $('


') $p.insertAfter($parentElem) editor.selection.createRangeByElem($p, true) editor.selection.restoreSelection() // 修改状态 editor._willBreakCode = false e.preventDefault() return } const _startOffset = editor.selection.getRange().startOffset // 处理:回车时,不能插入
而是插入 \n ,因为是在 pre 标签里面 editor.cmd.do('insertHTML', '\n') editor.selection.saveRange() if (editor.selection.getRange().startOffset === _startOffset) { // 没起作用,再来一遍 editor.cmd.do('insertHTML', '\n') } const codeLength = $selectionElem.html().length if (editor.selection.getRange().startOffset + 1 === codeLength) { // 说明光标在代码最后的位置,执行了回车操作 // 记录下来,以便下次回车时候跳出 code editor._willBreakCode = true } // 阻止默认行为 e.preventDefault() } $textElem.on('keydown', e => { if (e.keyCode !== 13) { // 不是回车键 // 取消即将跳转代码块的记录 editor._willBreakCode = false return } //
回车时 特殊处理 codeHandle(e) }) }, // 清空时保留


_clearHandle: function () { const editor = this.editor const $textElem = editor.$textElem $textElem.on('keydown', e => { if (e.keyCode !== 8) { return } const txtHtml = $textElem.html().toLowerCase().trim() if (txtHtml === '


') { // 最后剩下一个空行,就不再删除了 e.preventDefault() return } }) $textElem.on('keyup', e => { if (e.keyCode !== 8) { return } let $p const txtHtml = $textElem.html().toLowerCase().trim() // firefox 时用 txtHtml === '
' 判断,其他用 !txtHtml 判断 if (!txtHtml || txtHtml === '
') { // 内容空了 $p = $('


') $textElem.html('') // 一定要先清空,否则在 firefox 下有问题 $textElem.append($p) editor.selection.createRangeByElem($p, false, true) editor.selection.restoreSelection() } }) }, // 粘贴事件(粘贴文字 粘贴图片) _pasteHandle: function () { const editor = this.editor const config = editor.config const pasteFilterStyle = config.pasteFilterStyle const pasteTextHandle = config.pasteTextHandle const ignoreImg = config.pasteIgnoreImg const $textElem = editor.$textElem // 粘贴图片、文本的事件,每次只能执行一个 // 判断该次粘贴事件是否可以执行 let pasteTime = 0 function canDo() { var now = Date.now() var flag = false if (now - pasteTime >= 100) { // 间隔大于 100 ms ,可以执行 flag = true } pasteTime = now return flag } function resetTime() { pasteTime = 0 } // 粘贴文字 $textElem.on('paste', e => { if (UA.isIE()) { return } else { // 阻止默认行为,使用 execCommand 的粘贴命令 e.preventDefault() } // 粘贴图片和文本,只能同时使用一个 if (!canDo()) { return } // 获取粘贴的文字 let pasteHtml = getPasteHtml(e, pasteFilterStyle, ignoreImg) let pasteText = getPasteText(e) pasteText = pasteText.replace(/\n/gm, '
') const $selectionElem = editor.selection.getSelectionContainerElem() if (!$selectionElem) { return } const nodeName = $selectionElem.getNodeName() // code 中只能粘贴纯文本 if (nodeName === 'CODE' || nodeName === 'PRE') { if (pasteTextHandle && isFunction(pasteTextHandle)) { // 用户自定义过滤处理粘贴内容 pasteText = '' + (pasteTextHandle(pasteText) || '') } editor.cmd.do('insertHTML', `

${pasteText}

`) return } // 先放开注释,有问题再追查 ———— // // 表格中忽略,可能会出现异常问题 // if (nodeName === 'TD' || nodeName === 'TH') { // return // } if (!pasteHtml) { // 没有内容,可继续执行下面的图片粘贴 resetTime() return } try { // firefox 中,获取的 pasteHtml 可能是没有