编辑器增加撤销、重做功能,编辑优化
This commit is contained in:
@@ -44,6 +44,7 @@
|
|||||||
import "./css/MgEditor.css";
|
import "./css/MgEditor.css";
|
||||||
import "./css/MgEditorIconfont.css";
|
import "./css/MgEditorIconfont.css";
|
||||||
import Dom from './util/dom';
|
import Dom from './util/dom';
|
||||||
|
import UndoRedo from './util/undoRedo';
|
||||||
import toolbarCommon from './toolbar/common';
|
import toolbarCommon from './toolbar/common';
|
||||||
|
|
||||||
const $ = require("jquery");
|
const $ = require("jquery");
|
||||||
@@ -56,6 +57,8 @@
|
|||||||
editorRange: {
|
editorRange: {
|
||||||
startOffset: 0,
|
startOffset: 0,
|
||||||
endOffset: 0,
|
endOffset: 0,
|
||||||
|
startDomIndex: -1,
|
||||||
|
endDomIndex: -1,
|
||||||
},
|
},
|
||||||
userInput: {},
|
userInput: {},
|
||||||
userInputStyle: {
|
userInputStyle: {
|
||||||
@@ -70,23 +73,44 @@
|
|||||||
editorDom: [],
|
editorDom: [],
|
||||||
editDom: {},
|
editDom: {},
|
||||||
userInputData: '',
|
userInputData: '',
|
||||||
|
undoRedo: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
this.undoRedo = new UndoRedo(this.editorDom);
|
||||||
this.editor = this.$refs.mgEditor;
|
this.editor = this.$refs.mgEditor;
|
||||||
this.userInput = this.$refs.userInput;
|
this.userInput = this.$refs.userInput;
|
||||||
this.editorDom.push(new Dom('text', 'head head-h1'));
|
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 => {
|
this.userInput.addEventListener('keydown', e => {
|
||||||
if (e.which == 13) {
|
if (e.which == 13) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.editDom.keyEnter(this.editorDom, this.editorRange);
|
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 => {
|
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();
|
let selectText = selectionRange.toString();
|
||||||
if (!!selectText) {
|
if (!!selectText) {
|
||||||
let startNode = toolbarCommon.getRootDom(selectionRange.startContainer);
|
let startNode = toolbarCommon.getRootDom(selectionRange.startContainer);
|
||||||
@@ -125,7 +149,11 @@
|
|||||||
domTemp.setOffset(0, endOffset);
|
domTemp.setOffset(0, endOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.editorRange.startDomIndex = startIndex;
|
||||||
|
this.editorRange.endDomIndex = endIndex + 1;
|
||||||
this.editorToolbarStyle.display = 'block';
|
this.editorToolbarStyle.display = 'block';
|
||||||
|
} else {
|
||||||
|
this.hideToolbar();
|
||||||
}
|
}
|
||||||
// console.log("mouseup", selectText, e);
|
// console.log("mouseup", selectText, e);
|
||||||
});
|
});
|
||||||
@@ -140,6 +168,9 @@
|
|||||||
setTimeout(() => event.target.lastChild.click(), 100);
|
setTimeout(() => event.target.lastChild.click(), 100);
|
||||||
},
|
},
|
||||||
domClick(dom, event) {
|
domClick(dom, event) {
|
||||||
|
setTimeout(() => this.domClickTimer(dom, event), 50);
|
||||||
|
},
|
||||||
|
domClickTimer(dom, event) {
|
||||||
this.editDom = dom;
|
this.editDom = dom;
|
||||||
this.editDom.target = event.target;
|
this.editDom.target = event.target;
|
||||||
// 设置接收用户输入的输入框绝对位置
|
// 设置接收用户输入的输入框绝对位置
|
||||||
@@ -156,7 +187,11 @@
|
|||||||
this.editorCursorStyle.height = computedStyle.fontSize;
|
this.editorCursorStyle.height = computedStyle.fontSize;
|
||||||
this.editorCursorStyle.display = 'block';
|
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 startNode = toolbarCommon.getRootDom(selectionRange.startContainer);
|
||||||
let endNode = toolbarCommon.getRootDom(selectionRange.endContainer);
|
let endNode = toolbarCommon.getRootDom(selectionRange.endContainer);
|
||||||
let startIndex = startNode.getAttribute("index");
|
let startIndex = startNode.getAttribute("index");
|
||||||
@@ -178,20 +213,24 @@
|
|||||||
this.editorRange.endOffset = endOffset;
|
this.editorRange.endOffset = endOffset;
|
||||||
console.log(startOffset, endOffset);
|
console.log(startOffset, endOffset);
|
||||||
if (startOffset == endOffset) {
|
if (startOffset == endOffset) {
|
||||||
|
this.hideToolbar();
|
||||||
setTimeout(() => this.userInput.focus(), 50);
|
setTimeout(() => this.userInput.focus(), 50);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
userInputDataChange() {
|
userInputDataChange() {
|
||||||
|
if (!this.userInputData) return;
|
||||||
// 如果在最后一个div里面输入,则改为非最后一个,然后在最后再加一行
|
// 如果在最后一个div里面输入,则改为非最后一个,然后在最后再加一行
|
||||||
if (this.editDom.type == 'locate') {
|
if (this.editDom.type == 'locate') {
|
||||||
this.editDom.type = 'text';
|
this.editDom.type = 'text';
|
||||||
this.editDom.removeClass('locate');
|
this.editDom.removeClass('locate');
|
||||||
this.editorDom.push(new Dom('locate', 'locate'));
|
this.editorDom.push(new Dom('locate', 'locate'));
|
||||||
}
|
}
|
||||||
|
let beforeJson = JSON.stringify(this.editDom);
|
||||||
let oldText = this.editDom.text || '';
|
let oldText = this.editDom.text || '';
|
||||||
// 如果文字的中间位置点击,则把内容放到指定位置
|
// 如果文字的中间位置点击,则把内容放到指定位置
|
||||||
let startOffset = this.editorRange.startOffset;
|
let startOffset = this.editorRange.startOffset;
|
||||||
this.editDom.addText(startOffset, this.userInputData);
|
this.editDom.addText(startOffset, this.userInputData);
|
||||||
|
let afterJson = JSON.stringify(this.editDom);
|
||||||
if (startOffset < oldText.length) {
|
if (startOffset < oldText.length) {
|
||||||
this.editorRange.startOffset = this.editorRange.endOffset = (startOffset + this.userInputData.length);
|
this.editorRange.startOffset = this.editorRange.endOffset = (startOffset + this.userInputData.length);
|
||||||
} else {
|
} else {
|
||||||
@@ -202,26 +241,43 @@
|
|||||||
// let letterSpacing = this.userInputData.length * 0.52;
|
// let letterSpacing = this.userInputData.length * 0.52;
|
||||||
// this.editorCursorStyle.left = (parseInt(this.editorCursorStyle.left) + (parseInt(fontSize) * newLength) + letterSpacing) + 'px';
|
// this.editorCursorStyle.left = (parseInt(this.editorCursorStyle.left) + (parseInt(fontSize) * newLength) + letterSpacing) + 'px';
|
||||||
this.userInputData = '';
|
this.userInputData = '';
|
||||||
|
let editDomNode = toolbarCommon.getRootDom(this.editDom.target);
|
||||||
|
let editIndex = parseInt(editDomNode.getAttribute("index"));
|
||||||
|
this.undoRedo.execute(1, editIndex, beforeJson, afterJson);
|
||||||
},
|
},
|
||||||
handleToolbarBold() {
|
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.editorDom[i].addSelectionTextStyle('bold', 1);
|
||||||
}
|
}
|
||||||
this.editorToolbarStyle.display = 'none';
|
this.editorToolbarStyle.display = 'none';
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
},
|
},
|
||||||
handleToolbarStrikeThrough() {
|
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.editorDom[i].addSelectionTextStyle('strikethrough', 1);
|
||||||
}
|
}
|
||||||
this.editorToolbarStyle.display = 'none';
|
this.editorToolbarStyle.display = 'none';
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
},
|
},
|
||||||
handleToolbarHn(hn) {
|
handleToolbarHn(hn) {
|
||||||
debugger
|
for (let i = this.editorRange.startDomIndex; i < this.editorRange.endDomIndex; i++) {
|
||||||
|
this.editorDom[i].addSelectionTextHead(hn);
|
||||||
|
}
|
||||||
this.editorToolbarStyle.display = 'none';
|
this.editorToolbarStyle.display = 'none';
|
||||||
window.getSelection().removeAllRanges();
|
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>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// 构造函数
|
// 构造函数
|
||||||
import TextStyle from "./textStyle";
|
|
||||||
import StyleRange from "./styleRange";
|
import StyleRange from "./styleRange";
|
||||||
|
|
||||||
function Dom(type = 'text', cls = '', text = '') {
|
function Dom(type = 'text', cls = '', text = '', styleRange = []) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.target = '';
|
this.target = '';
|
||||||
@@ -13,7 +12,7 @@ function Dom(type = 'text', cls = '', text = '') {
|
|||||||
this.dom = [];
|
this.dom = [];
|
||||||
this.textStyle = [];
|
this.textStyle = [];
|
||||||
// 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'}
|
// 一个范围的样式,例:{start: 1, end: 2, class: 'xx xxx'}
|
||||||
this.styleRange = [];
|
this.styleRange = styleRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原型
|
// 原型
|
||||||
@@ -29,9 +28,10 @@ Dom.prototype = {
|
|||||||
return this.clsSet.has(cls);
|
return this.clsSet.has(cls);
|
||||||
},
|
},
|
||||||
addClass(cls) {
|
addClass(cls) {
|
||||||
if (this.hasClass(cls)) return;
|
if (this.hasClass(cls)) return this;
|
||||||
this.clsSet.add(cls);
|
this.clsSet.add(cls);
|
||||||
this.cls = Array.from(this.clsSet).join(" ");
|
this.cls = Array.from(this.clsSet).join(" ");
|
||||||
|
return this;
|
||||||
},
|
},
|
||||||
setOffset(start, end) {
|
setOffset(start, end) {
|
||||||
this.startOffset = start;
|
this.startOffset = start;
|
||||||
@@ -41,6 +41,9 @@ Dom.prototype = {
|
|||||||
this.startOffset = 0;
|
this.startOffset = 0;
|
||||||
this.endOffset = this.text.length;
|
this.endOffset = this.text.length;
|
||||||
},
|
},
|
||||||
|
clearRange() {
|
||||||
|
this.startOffset = this.endOffset = -1;
|
||||||
|
},
|
||||||
addText(startOffset, data) {
|
addText(startOffset, data) {
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
// todo 删除选中的内容
|
// todo 删除选中的内容
|
||||||
@@ -58,6 +61,13 @@ Dom.prototype = {
|
|||||||
}
|
}
|
||||||
this.computerStyleRangeToDom();
|
this.computerStyleRangeToDom();
|
||||||
},
|
},
|
||||||
|
addSelectionTextHead(hn) {
|
||||||
|
if (this.startOffset < 0 || this.endOffset < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addClass('head').addClass('head-' + hn);
|
||||||
|
this.clearRange();
|
||||||
|
},
|
||||||
addSelectionTextStyle(cls) {
|
addSelectionTextStyle(cls) {
|
||||||
if (this.startOffset < 0 || this.endOffset < 0 || this.startOffset == this.endOffset) {
|
if (this.startOffset < 0 || this.endOffset < 0 || this.startOffset == this.endOffset) {
|
||||||
return;
|
return;
|
||||||
@@ -97,7 +107,7 @@ Dom.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.styleRange = styleRangeMerged;
|
this.styleRange = styleRangeMerged;
|
||||||
this.startOffset = this.endOffset = -1;
|
this.clearRange();
|
||||||
this.computerStyleRangeToDom();
|
this.computerStyleRangeToDom();
|
||||||
},
|
},
|
||||||
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