编辑器增加撤销、重做功能,编辑优化
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
103
zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js
Normal file
103
zyplayer-doc-ui/wiki-ui/src/components/editor2/util/undoRedo.js
Normal 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;
|
||||
Reference in New Issue
Block a user