编辑器增加撤销、重做功能,编辑优化

This commit is contained in:
暮光:城中城
2020-06-14 21:20:23 +08:00
parent fad50328b6
commit 39783b3467
4 changed files with 197 additions and 10 deletions

View File

@@ -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;
},
}
}
</script>

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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;