From 39783b34675ede8acda6de69d4c5e4bf5b7a2805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9A=AE=E5=85=89=EF=BC=9A=E5=9F=8E=E4=B8=AD=E5=9F=8E?= <806783409@qq.com> Date: Sun, 14 Jun 2020 21:20:23 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=92=A4=E9=94=80=E3=80=81=E9=87=8D=E5=81=9A=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E7=BC=96=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/editor2/MgEditor.vue | 66 ++++++++++- .../src/components/editor2/util/dom.js | 20 +++- .../src/components/editor2/util/undoInfo.js | 18 +++ .../src/components/editor2/util/undoRedo.js | 103 ++++++++++++++++++ 4 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoInfo.js create mode 100644 zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue b/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue index 2fafa044..108b3b5f 100644 --- a/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/MgEditor.vue @@ -44,6 +44,7 @@ import "./css/MgEditor.css"; import "./css/MgEditorIconfont.css"; import Dom from './util/dom'; + import UndoRedo from './util/undoRedo'; import toolbarCommon from './toolbar/common'; const $ = require("jquery"); @@ -56,6 +57,8 @@ editorRange: { startOffset: 0, endOffset: 0, + startDomIndex: -1, + endDomIndex: -1, }, userInput: {}, userInputStyle: { @@ -70,23 +73,44 @@ editorDom: [], editDom: {}, userInputData: '', + undoRedo: {}, }; }, mounted: function () { + this.undoRedo = new UndoRedo(this.editorDom); this.editor = this.$refs.mgEditor; this.userInput = this.$refs.userInput; this.editorDom.push(new Dom('text', 'head head-h1')); + document.body.addEventListener('click', e => { + for (let i = 0; i < this.editorDom.length; i++) { + this.editorDom[i].clearRange(); + } + this.editorToolbarStyle.display = 'none'; + }); // 监听输入框的特殊按键 this.userInput.addEventListener('keydown', e => { if (e.which == 13) { e.preventDefault(); this.editDom.keyEnter(this.editorDom, this.editorRange); + } else if (e.keyCode == 90 && e.ctrlKey) { + e.preventDefault(); + this.undoRedo.undo(); + this.$forceUpdate(); + } else if (e.keyCode == 89 && e.ctrlKey) { + e.preventDefault(); + this.undoRedo.redo(); + this.$forceUpdate(); } + // console.log(e) }); // 鼠标选中事件 this.editor.addEventListener('mouseup', e => { // 不延时还能获取到选中的文字(选择文字,单击选中文字的中间) - let selectionRange = window.getSelection().getRangeAt(0); + let selectionRange = this.getSelectionRange(); + if (selectionRange == null) { + this.hideToolbar(); + return; + } let selectText = selectionRange.toString(); if (!!selectText) { let startNode = toolbarCommon.getRootDom(selectionRange.startContainer); @@ -125,7 +149,11 @@ domTemp.setOffset(0, endOffset); } } + this.editorRange.startDomIndex = startIndex; + this.editorRange.endDomIndex = endIndex + 1; this.editorToolbarStyle.display = 'block'; + } else { + this.hideToolbar(); } // console.log("mouseup", selectText, e); }); @@ -140,6 +168,9 @@ setTimeout(() => event.target.lastChild.click(), 100); }, domClick(dom, event) { + setTimeout(() => this.domClickTimer(dom, event), 50); + }, + domClickTimer(dom, event) { this.editDom = dom; this.editDom.target = event.target; // 设置接收用户输入的输入框绝对位置 @@ -156,7 +187,11 @@ this.editorCursorStyle.height = computedStyle.fontSize; this.editorCursorStyle.display = 'block'; // 设置光标所在对象的位置 - let selectionRange = window.getSelection().getRangeAt(0); + let selectionRange = this.getSelectionRange(); + if (selectionRange == null) { + this.hideToolbar(); + return; + } let startNode = toolbarCommon.getRootDom(selectionRange.startContainer); let endNode = toolbarCommon.getRootDom(selectionRange.endContainer); let startIndex = startNode.getAttribute("index"); @@ -178,20 +213,24 @@ this.editorRange.endOffset = endOffset; console.log(startOffset, endOffset); if (startOffset == endOffset) { + this.hideToolbar(); setTimeout(() => this.userInput.focus(), 50); } }, userInputDataChange() { + if (!this.userInputData) return; // 如果在最后一个div里面输入,则改为非最后一个,然后在最后再加一行 if (this.editDom.type == 'locate') { this.editDom.type = 'text'; this.editDom.removeClass('locate'); this.editorDom.push(new Dom('locate', 'locate')); } + let beforeJson = JSON.stringify(this.editDom); let oldText = this.editDom.text || ''; // 如果文字的中间位置点击,则把内容放到指定位置 let startOffset = this.editorRange.startOffset; this.editDom.addText(startOffset, this.userInputData); + let afterJson = JSON.stringify(this.editDom); if (startOffset < oldText.length) { this.editorRange.startOffset = this.editorRange.endOffset = (startOffset + this.userInputData.length); } else { @@ -202,26 +241,43 @@ // let letterSpacing = this.userInputData.length * 0.52; // this.editorCursorStyle.left = (parseInt(this.editorCursorStyle.left) + (parseInt(fontSize) * newLength) + letterSpacing) + 'px'; this.userInputData = ''; + let editDomNode = toolbarCommon.getRootDom(this.editDom.target); + let editIndex = parseInt(editDomNode.getAttribute("index")); + this.undoRedo.execute(1, editIndex, beforeJson, afterJson); }, handleToolbarBold() { - for (let i = 0; i < this.editorDom.length; i++) { + for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { this.editorDom[i].addSelectionTextStyle('bold', 1); } this.editorToolbarStyle.display = 'none'; window.getSelection().removeAllRanges(); }, handleToolbarStrikeThrough() { - for (let i = 0; i < this.editorDom.length; i++) { + for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { this.editorDom[i].addSelectionTextStyle('strikethrough', 1); } this.editorToolbarStyle.display = 'none'; window.getSelection().removeAllRanges(); }, handleToolbarHn(hn) { - debugger + for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) { + this.editorDom[i].addSelectionTextHead(hn); + } this.editorToolbarStyle.display = 'none'; window.getSelection().removeAllRanges(); }, + hideToolbar() { + this.editorRange.startDomIndex = -1; + this.editorRange.endDomIndex = -1; + this.editorToolbarStyle.display = 'none'; + }, + getSelectionRange() { + let selection = window.getSelection(); + if (selection.rangeCount > 0) { + return selection.getRangeAt(0); + } + return null; + }, } } diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js index 83605fe5..2baf08ef 100644 --- a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/dom.js @@ -1,8 +1,7 @@ // 构造函数 -import TextStyle from "./textStyle"; import StyleRange from "./styleRange"; -function Dom(type = 'text', cls = '', text = '') { +function Dom(type = 'text', cls = '', text = '', styleRange = []) { this.type = type; this.text = text; this.target = ''; @@ -13,7 +12,7 @@ function Dom(type = 'text', cls = '', text = '') { this.dom = []; this.textStyle = []; // 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'} - this.styleRange = []; + this.styleRange = styleRange; } // 原型 @@ -29,9 +28,10 @@ Dom.prototype = { return this.clsSet.has(cls); }, addClass(cls) { - if (this.hasClass(cls)) return; + if (this.hasClass(cls)) return this; this.clsSet.add(cls); this.cls = Array.from(this.clsSet).join(" "); + return this; }, setOffset(start, end) { this.startOffset = start; @@ -41,6 +41,9 @@ Dom.prototype = { this.startOffset = 0; this.endOffset = this.text.length; }, + clearRange() { + this.startOffset = this.endOffset = -1; + }, addText(startOffset, data) { if (!data) return; // todo 删除选中的内容 @@ -58,6 +61,13 @@ Dom.prototype = { } this.computerStyleRangeToDom(); }, + addSelectionTextHead(hn) { + if (this.startOffset < 0 || this.endOffset < 0) { + return; + } + this.addClass('head').addClass('head-' + hn); + this.clearRange(); + }, addSelectionTextStyle(cls) { if (this.startOffset < 0 || this.endOffset < 0 || this.startOffset == this.endOffset) { return; @@ -97,7 +107,7 @@ Dom.prototype = { } } this.styleRange = styleRangeMerged; - this.startOffset = this.endOffset = -1; + this.clearRange(); this.computerStyleRangeToDom(); }, computerStyleRangeToDom() { diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoInfo.js b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoInfo.js new file mode 100644 index 00000000..5d0d6e85 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoInfo.js @@ -0,0 +1,18 @@ +// 构造函数 +function UndoInfo(type = 1, index = -1, before = '', after = '') { + // 操作类型 1=修改 2=添加 3=删除 + this.type = type; + // 数组下标 + this.index = index; + // 修改前内容json + this.before = before; + // 修改后内容json + this.after = after; +} + +// 原型 +UndoInfo.prototype = { + constructor: UndoInfo, +}; + +export default UndoInfo; diff --git a/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js new file mode 100644 index 00000000..2b4a5785 --- /dev/null +++ b/zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js @@ -0,0 +1,103 @@ +// 构造函数 + +import UndoInfo from "./undoInfo"; +import Dom from "./dom"; + +function UndoRedo(editorDom) { + this.editorDom = editorDom; + this.undoRedoList = []; + this.undoRedoIndex = -1; +} + +// 原型 +UndoRedo.prototype = { + constructor: UndoRedo, + execute(type, index, before, after) { + // 最多保留20步 + if (this.undoRedoList.length >= 20) { + this.undoRedoList.splice(0, 1); + } + this.undoRedoList.push(new UndoInfo(type, index, before, after)); + this.undoRedoIndex = this.undoRedoList.length - 1; + }, + undo() { + if (this.undoRedoIndex < 0 || this.undoRedoIndex >= this.undoRedoList.length) { + this.undoRedoIndex = this.undoRedoList.length - 1; + } + if (this.undoRedoIndex < 0) { + return; + } + let undoInfo = this.undoRedoList[this.undoRedoIndex]; + let changeContent = JSON.parse(undoInfo.before); + if (changeContent instanceof Array) { + changeContent.forEach(item => { + this.undoObjDomToEditor(undoInfo, item); + }); + } else { + this.undoObjDomToEditor(undoInfo, changeContent); + } + this.undoRedoIndex = Math.max(this.undoRedoIndex - 1, 0); + }, + redo() { + this.undoRedoIndex++; + if (this.undoRedoIndex < 0 || this.undoRedoIndex >= this.undoRedoList.length) { + this.undoRedoIndex--; + return; + } + let undoInfo = this.undoRedoList[this.undoRedoIndex]; + let changeContent = JSON.parse(undoInfo.after); + if (changeContent instanceof Array) { + changeContent.forEach(item => { + this.redoObjDomToEditor(undoInfo, item); + }); + } else { + this.redoObjDomToEditor(undoInfo, changeContent); + } + }, + redoObjDomToEditor(undoInfo, domObj) { + let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + if (undoInfo.type == 1) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoInfo.index) { + this.editorDom[undoInfo.index] = dom; + } + } else if (undoInfo.type == 2) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoInfo.index) { + if (this.editorDom.length == undoInfo.index) { + this.editorDom.push(dom); + } else if (this.editorDom.length > undoInfo.index) { + this.editorDom.splice(undoInfo.index, 0, dom); + } + } + } else if (undoInfo.type == 3) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoInfo.index) { + this.editorDom.splice(undoInfo.index, 1); + } + } + }, + undoObjDomToEditor(undoInfo, domObj) { + let dom = new Dom(domObj.type, domObj.cls, domObj.text, domObj.styleRange); + if (undoInfo.type == 1) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoInfo.index) { + this.editorDom[undoInfo.index] = dom; + } + } else if (undoInfo.type == 2) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length > undoInfo.index) { + this.editorDom.splice(undoInfo.index, 1); + } + } else if (undoInfo.type == 3) { + // 1=修改 2=添加 3=删除 + if (this.editorDom.length == undoInfo.index) { + this.editorDom.push(dom); + } else if (this.editorDom.length > undoInfo.index) { + this.editorDom.splice(undoInfo.index, 0, dom); + } + } + }, +}; + +export default UndoRedo;