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