编辑器撤销(undo)与重做(redo)插件的设计与实现 ,涉及三个方面:
?
1. 编辑器状态定义
?
不仅要包含当前编辑器的内容(body.innerHTML),还要考虑编辑区域用户的选中状态,用户选中每个区域进行高亮等操作,则操作前要把当前选中区域以及内容html都要保存下来:
?
?
?
/** * 当前编辑区域状态,包括html与选择区域 * @param editor */ function Snapshot(editor) { var contents = editor.getData(),selection = contents && editor.getSelection(); //内容html this.contents = contents; //选择区域书签标志 this.bookmarks = selection && selection.createBookmarks2(true); }?
2.编辑状态历史的管理
历史管理器维护着编辑状态的栈数据, 其他插件通过触发编辑器的 save 与 restore 事件来通知历史管理器更新内部的状态。
当 save 操作时,历史管理器将当前状态添加到编辑状态栈即可,并更新内部游标。
?
?
restore 操作时,需要利用管理器内部的游标来在状态栈间前(undo)后(redo)游走,用游标处的状态替换当前状态。
?
?
注意点在于,当进行了re/undo操作后,再进行save状态保存,需要对状态栈进行清理,将当前游标以后的历史状态删掉,防止出现历史分支:
?
?
?
?
另一点在于:对于键盘的连续输入,可buffer处理,只有当空闲一定时间后才进行保存,避免细微冗余信息。
?
/** * 通过编辑器的save与restore事件,编辑器实例的历史栈管理,与键盘监控 * @param editor */ function UndoManager(editor) { //redo undo history stack /** * 编辑器状态历史保存 */ this.history = []; this.index = 0; this.editor = editor; this.bufferTimer = new BufferTimer(500, this.save, this); this._init(); } S.augment(UndoManager, { /** * 监控键盘输入,buffer处理 * @param ev */ _keyMonitor: function (ev) { //ctrl+z,撤销 //ctrl+y,重做 //其他可见字符buffer处理 }, _init: function () { var self = this, editor = self.editor; //外部通过editor触发save|restore,管理器捕获事件处理 editor.on("save", function (ev) { if (ev.buffer) //键盘操作需要缓存 self.bufferTimer.run(); else { //其他立即save self.save(); } }); //un/re do editor.on("restore", this.restore, this); self._keyMonitor(); //先save一下 self.save(); }, /** * 保存历史 */ save: function () { //游标后面的历史抛弃 //超过占最大容量,shift队列出列 //当前状态和栈顶状态不同,入栈 //更新游标 //触发afterSave事件 }, /** * * @param ev * ev.d :1.向前撤销 ,-1.向后重做 */ restore: function (ev) { //更新游标,用游标所在状态替换当前状态 } });
?
3.工具栏 ui 与功能调用
RestoreUI 封装工具栏撤销与重做的功能与表现,使用 attribute 抽象出三状态按钮(可用并选中,可用,禁用),监听通过2触发的编辑器 afterSave,afterRestore 事件,通过游标位置和历史栈大小的比较,来决定redo ,undo按钮的禁用与可用状态.
?
/** * save,restore完,更新工具栏状态 */ editor.on("afterSave", this._respond, this); editor.on("afterRestore", this._respond, this);?
undo:当游标不在状态栈底部时可用(index>0&&history.length>0)
redo:当游标不在状态栈顶部时可用(index < history.length-1)
当点击工具栏按钮时,触发editor的restore事件,注意参数,redo为向后restore,undo为向前restore。
?
/** * 触发重做或撤销动作,都是restore,方向不同 */ self.el.on("click", function() { editor.fire("restore", { d:RedoMap[self.text] }); });?
?
4.整合
当编辑器实例生成后,全局空间事件通知undo/redo插件,插件对每个编辑器生成对应的历史管理器与工具栏按钮:
?
KE.on("instanceCreated", function(ev) { var editor = ev.editor; /** * 编辑器历史中央管理 */ new UndoManager(editor); /** * 撤销工具栏按钮 */ new RestoreUI(editor, "undo"); /** * 重做工具栏按钮 */ new RestoreUI(editor, "redo"); });
?
?
?
插件源码
demo
?
?