1 /** 2 * undo,redo manager for kissy editor 3 * @author yiminghe@gmail.com 4 */ 5 KISSY.add("editor/plugin/undo/cmd", function (S, Editor) { 6 var arrayCompare = Editor.Utils.arrayCompare, 7 UA = S.UA, 8 LIMIT = 30; 9 10 /** 11 * 当前编辑区域状态,包括 html 与选择区域(光标位置) 12 * @param editor 13 */ 14 function Snapshot(editor) { 15 var contents = editor.get("document")[0].body.innerHTML, 16 self = this, 17 selection; 18 if (contents) { 19 selection = editor.getSelection(); 20 } 21 //内容html 22 self.contents = contents; 23 //选择区域书签标志 24 self.bookmarks = selection && selection.createBookmarks2(true); 25 } 26 27 S.augment(Snapshot, { 28 /** 29 * 编辑状态间是否相等 30 * @param otherImage 31 */ 32 equals:function (otherImage) { 33 var self = this, 34 thisContents = self.contents, 35 otherContents = otherImage.contents; 36 37 if (thisContents != otherContents){ 38 return false; 39 } 40 41 var bookmarksA = self.bookmarks, 42 bookmarksB = otherImage.bookmarks; 43 44 if (bookmarksA || bookmarksB) { 45 if (!bookmarksA || !bookmarksB || bookmarksA.length != bookmarksB.length) 46 return false; 47 48 for (var i = 0; i < bookmarksA.length; i++) { 49 var bookmarkA = bookmarksA[ i ], 50 bookmarkB = bookmarksB[ i ]; 51 52 if ( 53 bookmarkA.startOffset != bookmarkB.startOffset || 54 bookmarkA.endOffset != bookmarkB.endOffset || 55 !arrayCompare(bookmarkA.start, bookmarkB.start) || 56 !arrayCompare(bookmarkA.end, bookmarkB.end)) { 57 return false; 58 } 59 } 60 } 61 62 return true; 63 } 64 }); 65 66 /** 67 * 通过编辑器的save与restore事件,编辑器实例的历史栈管理,与键盘监控 68 * @param editor 69 */ 70 function UndoManager(editor) { 71 // redo undo history stack 72 /** 73 * 编辑器状态历史保存 74 */ 75 var self = this; 76 self.history = []; 77 //当前所处状态对应的历史栈内下标 78 self.index = -1; 79 self.editor = editor; 80 //键盘输入做延迟处理 81 self.bufferRunner = S.buffer(self.save, 500, self); 82 self._init(); 83 } 84 85 var //editingKeyCodes = { /*Backspace*/ 8:1, /*Delete*/ 46:1 }, 86 modifierKeyCodes = { /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1 }, 87 // Arrows: L, T, R, B 88 navigationKeyCodes = { 37:1, 38:1, 39:1, 40:1, 33:1, 34:1 }, 89 zKeyCode = 90, 90 yKeyCode = 89; 91 92 93 S.augment(UndoManager, { 94 /** 95 * 监控键盘输入,buffer处理 96 */ 97 _keyMonitor:function () { 98 var self = this, 99 editor = self.editor; 100 101 editor.docReady(function () { 102 editor.get("document").on("keydown", function (ev) { 103 var keyCode = ev.keyCode; 104 if (keyCode in navigationKeyCodes 105 || keyCode in modifierKeyCodes) { 106 return; 107 } 108 // ctrl+z,撤销 109 if (keyCode === zKeyCode && (ev.ctrlKey || ev.metaKey)) { 110 if (false !== editor.fire("beforeRedo")) { 111 self.restore(-1); 112 } 113 ev.halt(); 114 return; 115 } 116 // ctrl+y,重做 117 if (keyCode === yKeyCode && (ev.ctrlKey || ev.metaKey)) { 118 if (false !== editor.fire("beforeUndo")) { 119 self.restore(1); 120 } 121 ev.halt(); 122 return; 123 } 124 if (editor.fire("beforeSave", {buffer:1}) !== false) { 125 self.save(1); 126 } 127 }); 128 }); 129 }, 130 131 _init:function () { 132 var self = this; 133 self._keyMonitor(); 134 //先save一下,why?? 135 //初始状态保存,异步,必须等use中已经 set 了编辑器中初始代码 136 //必须在从 textarea 复制到编辑区域前,use所有plugin,为了过滤插件生效 137 //而这段代码必须在从 textarea 复制到编辑区域后运行,所以设个延迟 138 setTimeout(function () { 139 self.save(); 140 }, 0); 141 }, 142 143 /** 144 * 保存历史 145 */ 146 save:function (buffer) { 147 var editor = this.editor; 148 149 // 代码模式下不和可视模式下混在一起 150 if (editor.get("mode") != Editor.WYSIWYG_MODE) { 151 return; 152 } 153 154 155 if (!editor.get("document")) { 156 return; 157 } 158 159 if (buffer) { 160 this.bufferRunner(); 161 return; 162 } 163 164 var self = this, 165 history = self.history, 166 index = self.index; 167 168 //前面的历史抛弃 169 if (history.length > index + 1) 170 history.splice(index + 1, history.length - index - 1); 171 172 var last = history[history.length - 1], 173 current = new Snapshot(editor); 174 175 if (!last || !last.equals(current)) { 176 if (history.length === LIMIT) { 177 history.shift(); 178 } 179 history.push(current); 180 self.index = index = history.length - 1; 181 editor.fire("afterSave", {history:history, index:index}); 182 } 183 }, 184 185 /** 186 * @param d 1.向前撤销 ,-1.向后重做 187 */ 188 restore:function (d) { 189 190 // 代码模式下不和可视模式下混在一起 191 if (this.editor.get("mode") != Editor.WYSIWYG_MODE) { 192 return; 193 } 194 195 var self = this, 196 history = self.history, 197 editor = self.editor, 198 editorDomBody = editor.get("document")[0].body, 199 snapshot = history[self.index + d]; 200 201 if (snapshot) { 202 editorDomBody.innerHTML = snapshot.contents; 203 if (snapshot.bookmarks) 204 editor.getSelection().selectBookmarks(snapshot.bookmarks); 205 else if (UA['ie']) { 206 // IE BUG: If I don't set the selection to *somewhere* after setting 207 // document contents, then IE would create an empty paragraph at the bottom 208 // the next time the document is modified. 209 var $range = editorDomBody.createTextRange(); 210 $range.collapse(true); 211 $range.select(); 212 } 213 var selection = editor.getSelection(); 214 // 将当前光标,选择区域滚动到可视区域 215 if (selection) { 216 selection.scrollIntoView(); 217 } 218 self.index += d; 219 editor.fire(d > 0 ? "afterUndo" : "afterRedo", { 220 history:history, 221 index:self.index 222 }); 223 editor.notifySelectionChange(); 224 } 225 226 return snapshot; 227 } 228 }); 229 230 231 return { 232 init:function (editor) { 233 if (!editor.hasCommand("save")) { 234 var undoRedo = new UndoManager(editor); 235 editor.addCommand("save", { 236 exec:function (_, buffer) { 237 editor.focus(); 238 undoRedo.save(buffer); 239 } 240 }); 241 editor.addCommand("undo", { 242 exec:function () { 243 editor.focus(); 244 undoRedo.restore(-1); 245 } 246 }); 247 editor.addCommand("redo", { 248 exec:function () { 249 editor.focus(); 250 undoRedo.restore(1); 251 } 252 }); 253 } 254 } 255 }; 256 }, { 257 requires:['editor'] 258 }); 259