1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. 5 * 6 * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at: https://www.zimbra.com/license 9 * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 10 * have been added to cover use of software over a computer network and provide for limited attribution 11 * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. 12 * 13 * Software distributed under the License is distributed on an "AS IS" basis, 14 * WITHOUT WARRANTY OF ANY KIND, either express or implied. 15 * See the License for the specific language governing rights and limitations under the License. 16 * The Original Code is Zimbra Open Source Web Client. 17 * The Initial Developer of the Original Code is Zimbra, Inc. All rights to the Original Code were 18 * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015. 19 * 20 * All portions of the code are Copyright (C) 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * @overview 26 */ 27 28 /** 29 * @class 30 * Represents an undoable action (e.g. move an item) 31 * This class is a generic superclass that does very little on its own; the real work is being done in subclasses 32 * 33 * @extends ZmModel 34 */ 35 36 ZmAction = function() { 37 ZmModel.call(this, ZmEvent.S_ACTION); 38 this._complete = false; 39 }; 40 41 ZmAction.prototype = new ZmModel; 42 ZmAction.prototype.constructor = ZmAction; 43 44 ZmAction.ACTION_ZMACTION = "ZmAction"; 45 ZmAction.ACTION_ZMITEMACTION = "ZmItemAction"; 46 ZmAction.ACTION_ZMITEMMOVEACTION = "ZmItemMoveAction"; 47 ZmAction.ACTION_ZMITEMTRASHACTION = "ZmItemTrashAction"; 48 ZmAction.ACTION_ZMORGANIZERACTION = "ZmOrganizerAction"; 49 ZmAction.ACTION_ZMORGANIZERMOVEACTION = "ZmOrganizerMoveAction"; 50 ZmAction.ACTION_ZMCOMPOSITEACTION = "ZmCompositeAction"; 51 52 ZmAction.prototype.type = ZmAction.ACTION_ZMITEMACTION; 53 54 ZmAction.prototype.toString = function() { 55 return "ZmAction"; 56 }; 57 58 ZmAction.prototype.undo = function() { 59 //override me 60 }; 61 62 ZmAction.prototype.redo = function() { 63 //override me 64 }; 65 66 ZmAction.prototype.setComplete = function() { 67 if (!this._complete) { 68 this._complete = true; 69 this._notify(ZmEvent.E_COMPLETE); 70 } 71 }; 72 73 ZmAction.prototype.getComplete = function() { 74 return this._complete; 75 }; 76 77 ZmAction.prototype.onComplete = function(callback) { 78 if (this._complete) { 79 callback.run(this); 80 } else { 81 this.addChangeListener(new AjxListener(this, this._handleComplete, [callback])); 82 } 83 }; 84 85 ZmAction.prototype._handleComplete = function(callback, event) { 86 if (event.event===ZmEvent.E_COMPLETE) { 87 callback.run(this); 88 } 89 }; 90 91 /** 92 * @class 93 * Represents an undoable action on an item 94 * This class is a generic superclass that does very little on its own; the real work is being done in subclasses 95 * 96 * @extends ZmAction 97 * 98 * @param {ZmItem} item The item to perform the action on 99 * @param {String} op The operation to perform (e.g. "move" or "trash") 100 */ 101 102 ZmItemAction = function(item, op) { 103 if (!arguments.length) return; 104 ZmAction.call(this); 105 this._item = item; 106 this._op = op; 107 }; 108 109 ZmItemAction.prototype = new ZmAction; 110 ZmItemAction.prototype.constructor = ZmItemAction; 111 ZmItemAction.prototype.type = ZmAction.ACTION_ZMITEMACTION; 112 113 ZmItemAction.prototype.toString = function() { 114 return "ZmItemAction"; 115 }; 116 117 ZmItemAction.prototype.getItem = function() { 118 return this._item; 119 }; 120 121 ZmItemAction.prototype.getOp = function() { 122 return this._op; 123 }; 124 125 /** 126 * @class 127 * Represents an undoable action on an organizer 128 * This class is a generic superclass that does very little on its own; the real work is being done in subclasses 129 * 130 * @extends ZmAction 131 * 132 * @param {ZmOrganizer} organizer The organizer to perform the action on 133 * @param {String} op The operation to perform (e.g. "move") 134 */ 135 136 ZmOrganizerAction = function(organizer, op) { 137 if (!arguments.length) return; 138 ZmAction.call(this); 139 this._organizer = organizer; 140 this._op = op; 141 }; 142 143 ZmOrganizerAction.prototype = new ZmAction; 144 ZmOrganizerAction.prototype.constructor = ZmOrganizerAction; 145 ZmOrganizerAction.prototype.type = ZmAction.ACTION_ZMORGANIZERACTION; 146 147 ZmOrganizerAction.prototype.toString = function() { 148 return "ZmOrganizerAction"; 149 }; 150 151 ZmOrganizerAction.prototype.getOrganizer = function() { 152 return this._organizer; 153 }; 154 155 ZmOrganizerAction.prototype.getOp = function() { 156 return this._op; 157 }; 158 159 /** 160 * @class 161 * Represents an undoable move action on an item 162 * 163 * @extends ZmItemAction 164 * 165 * @param {ZmItem} item Item to perform the move on 166 * @param {int} fromFolderId Original folder id of the item 167 * @param {int} toFolderId Destination folder id of the item 168 * @param {String} op The operation to perform (e.g. "move") 169 */ 170 171 ZmItemMoveAction = function(item, fromFolderId, toFolderId, op) { 172 ZmItemAction.call(this, item, op); 173 this._fromFolderId = fromFolderId; 174 this._toFolderId = toFolderId; 175 }; 176 177 ZmItemMoveAction.prototype = new ZmItemAction; 178 ZmItemMoveAction.prototype.constructor = ZmItemMoveAction; 179 180 ZmItemMoveAction.prototype.type = ZmAction.ACTION_ZMITEMMOVEACTION; 181 182 ZmItemMoveAction.prototype.toString = function() { 183 return "ZmItemMoveAction"; 184 }; 185 186 ZmItemMoveAction.UNDO_MSG = { 187 "move" : ZmMsg.actionUndoMove, 188 "trash": ZmMsg.actionUndoTrash, 189 "spam": ZmMsg.actionUndoMarkAsJunk, 190 "!spam": ZmMsg.actionUndoMarkAsNotJunk 191 }; 192 193 ZmItemMoveAction.prototype.getFromFolderId = function() { 194 return this._fromFolderId; 195 }; 196 197 ZmItemMoveAction.prototype.getToFolderId = function() { 198 return this._toFolderId; 199 }; 200 201 ZmItemMoveAction.prototype._doMove = function(callback, errorCallback, folderId) { 202 203 var items = ZmItemMoveAction._realizeItems(this._item), // probably unnecessary since conv forces multipleUndo 204 list = items[0] && items[0].list; 205 206 list.moveItems({ 207 items: items, 208 folder: appCtxt.getById(folderId), 209 noUndo: true, 210 finalCallback: this._handleDoMove.bind(this, this._item.folderId, folderId), 211 fromFolderId: this._toFolderId 212 }); 213 }; 214 215 ZmItemMoveAction.prototype._handleDoMove = function(oldFolderId, newFolderId, params) { 216 var lists = []; 217 for (var id in params.idHash) { 218 var item = params.idHash[id]; 219 if (item instanceof ZmConv) 220 item.folderId = newFolderId; 221 var list = item && item.list; 222 if (AjxUtil.indexOf(lists, list)==-1) 223 lists.push(list); 224 } 225 for (var i=0; i<lists.length; i++) { 226 lists[i]._notify(ZmEvent.E_MOVE, {oldFolderId:oldFolderId}); 227 } 228 ZmListController.handleProgress({state:ZmListController.PROGRESS_DIALOG_CLOSE}); 229 ZmBaseController.showSummary(params.actionSummary); 230 }; 231 232 ZmItemMoveAction.prototype.undo = function(callback, errorCallback) { 233 this._doMove(callback, errorCallback, this._fromFolderId); 234 }; 235 236 ZmItemMoveAction.prototype.redo = function(callback, errorCallback) { 237 this._doMove(callback, errorCallback, this._toFolderId); 238 }; 239 240 ZmItemMoveAction.multipleUndo = function(actions, redo, fromFolderId) { 241 242 var sortingTable = {}; 243 for (var i = 0; i < actions.length; i++) { 244 var action = actions[i]; 245 if (action instanceof ZmItemMoveAction) { 246 var from = action.getFromFolderId(); 247 var to = action.getToFolderId(); 248 var item = action.getItem(); 249 var type = (item && item.list && item.list.type) || 0; 250 if (!sortingTable[from]) sortingTable[from] = {}; 251 if (!sortingTable[from][to]) sortingTable[from][to] = {}; 252 if (!sortingTable[from][to][type]) sortingTable[from][to][type] = []; 253 sortingTable[from][to][type].push(action); 254 } 255 } 256 257 for (var from in sortingTable) { 258 for (var to in sortingTable[from]) { 259 for (var type in sortingTable[from][to]) { 260 var subset = sortingTable[from][to][type]; 261 var items = []; 262 var list = null; 263 for (var i = 0; i < subset.length; i++) { 264 var action = subset[i]; 265 var item = action.getItem(); 266 items.push(item); 267 } 268 items = ZmItemMoveAction._realizeItems(items); 269 list = items[0] && items[0].list; 270 if (list) { 271 list.moveItems({ 272 items: items, 273 folder: appCtxt.getById(redo ? to : from), 274 noUndo: true, 275 fromFolderId: fromFolderId 276 }); 277 } 278 } 279 } 280 } 281 }; 282 283 ZmItemMoveAction.multipleRedo = function(actions) { 284 ZmItemMoveAction.multipleUndo(actions, true); 285 }; 286 287 // Creates ZmMailMsg out of anonymous msg-like objects 288 ZmItemMoveAction._realizeItems = function(items) { 289 290 var list, msg; 291 return AjxUtil.map(AjxUtil.toArray(items), function(item) { 292 if (item.isConvMsg) { 293 list = list || new ZmMailList(ZmItem.MSG); 294 msg = new ZmMailMsg(item.id, list, true); 295 msg.folderId = item.folderId; 296 return msg; 297 } 298 else { 299 return item; 300 } 301 }); 302 }; 303 304 /** 305 * @class 306 * Represents an undoable move action on an organizer 307 * 308 * @extends ZmOrganizerAction 309 * 310 * @param {ZmOrganizer} organizer Organizer to perform the move on 311 * @param {int} fromFolderId Original parent folder id of the organizer 312 * @param {int} toFolderId Destination parent folder id of the organizer 313 * @param {String} op The operation to perform (e.g. "move") 314 */ 315 316 ZmOrganizerMoveAction = function(organizer, fromFolderId, toFolderId, op) { 317 ZmOrganizerAction.call(this, organizer, op); 318 this._fromFolderId = fromFolderId; 319 this._toFolderId = toFolderId; 320 }; 321 322 ZmOrganizerMoveAction.prototype = new ZmOrganizerAction; 323 ZmOrganizerMoveAction.prototype.constructor = ZmOrganizerMoveAction; 324 325 ZmOrganizerMoveAction.prototype.type = ZmAction.ACTION_ZMORGANIZERMOVEACTION; 326 327 ZmOrganizerMoveAction.prototype.toString = function() { 328 return "ZmOrganizerMoveAction"; 329 }; 330 331 ZmOrganizerMoveAction.prototype.getFromFolderId = function() { 332 return this._fromFolderId; 333 }; 334 335 ZmOrganizerMoveAction.prototype.getToFolderId = function() { 336 return this._toFolderId; 337 }; 338 339 ZmOrganizerMoveAction.prototype._doMove = function(callback, errorCallback, folderId) { 340 var folder = appCtxt.getById(folderId); 341 if (folder) { 342 this._organizer.move(folder, true); 343 } 344 }; 345 346 ZmOrganizerMoveAction.prototype.undo = function(callback, errorCallback) { 347 this._doMove(callback, errorCallback, this._fromFolderId); 348 }; 349 350 ZmOrganizerMoveAction.prototype.redo = function(callback, errorCallback) { 351 this._doMove(callback, errorCallback, this._toFolderId); 352 }; 353 354 ZmOrganizerMoveAction.multipleUndo = function(actions, redo) { 355 for (var i=0; i<actions.length; i++) { 356 var action = actions[i]; 357 if (action instanceof ZmOrganizerMoveAction) { 358 action._doMove(null, null, redo ? this._toFolderId : this._fromFolderId); 359 } 360 } 361 }; 362 363 ZmOrganizerMoveAction.multipleRedo = function(actions) { 364 ZmItemMoveAction.multipleUndo(actions, true); 365 }; 366 367 /** 368 * @class 369 * Represents a collection of undoable actions that will be performed as a whole 370 * 371 * @extends ZmAction 372 * 373 */ 374 375 ZmCompositeAction = function(toFolderId) { 376 ZmAction.call(this); 377 this._actions = {}; 378 this._toFolderId = toFolderId; 379 }; 380 381 ZmCompositeAction.prototype = new ZmAction; 382 ZmCompositeAction.prototype.constructor = ZmCompositeAction; 383 ZmCompositeAction.prototype.type = ZmAction.ACTION_ZMCOMPOSITEACTION; 384 385 ZmCompositeAction.prototype.toString = function() { 386 return "ZmCompositeAction"; 387 }; 388 389 /** 390 * Add an action the the collection 391 * 392 * @param {ZmAction} action An action to add 393 */ 394 ZmCompositeAction.prototype.addAction = function(action) { 395 if (action && action!=this && action instanceof ZmAction) { 396 var type = action.type; 397 if (!this._actions[type]) 398 this._actions[type] = []; 399 this._actions[type].push(action); 400 } 401 }; 402 403 ZmCompositeAction.prototype.getActions = function(type) { 404 return this._actions[type] || []; 405 }; 406 407 ZmCompositeAction.prototype.hasActions = function(type) { 408 return this._actions[type] && this._actions[type].length>0; 409 }; 410 411 ZmCompositeAction.prototype.undo = function(callback, errorCallback) { 412 413 if (this.hasActions(ZmAction.ACTION_ZMITEMMOVEACTION)) { 414 ZmItemMoveAction.multipleUndo(this.getActions(ZmAction.ACTION_ZMITEMMOVEACTION), null, this._toFolderId); 415 } 416 417 if (this.hasActions(ZmAction.ACTION_ZMORGANIZERMOVEACTION)) { 418 ZmOrganizerMoveAction.multipleUndo(this.getActions(ZmAction.ACTION_ZMORGANIZERMOVEACTION)); 419 } 420 421 if (this.hasActions(ZmAction.ACTION_ZMCOMPOSITEACTION) || this.hasActions(ZmAction.ACTION_ZMITEMACTION)) { 422 var actions = this.getActions(ZmAction.ACTION_ZMCOMPOSITEACTION).concat(this.getActions(ZmAction.ACTION_ZMITEMACTION)); 423 for (var i=0; i<actions.length; i++) { 424 actions[i].undo(); 425 } 426 } 427 }; 428 429 ZmCompositeAction.prototype.redo = function(callback, errorCallback) { 430 431 if (this.hasActions(ZmAction.ACTION_ZMITEMMOVEACTION)) { 432 ZmItemMoveAction.multipleRedo(this.getActions(ZmAction.ACTION_ZMITEMMOVEACTION)); 433 } 434 435 if (this.hasActions(ZmAction.ACTION_ZMORGANIZERMOVEACTION)) { 436 ZmOrganizerMoveAction.multipleRedo(this.getActions(ZmAction.ACTION_ZMORGANIZERMOVEACTION)); 437 } 438 439 if (this.hasActions(ZmAction.ACTION_ZMCOMPOSITEACTION) || this.hasActions(ZmAction.ACTION_ZMITEMACTION) || this.hasActions(ZmAction.ACTION_ZMORGANIZERACTION)) { 440 var actions = this.getActions(ZmAction.ACTION_ZMCOMPOSITEACTION).concat(this.getActions(ZmAction.ACTION_ZMITEMACTION)).concat(this.getActions(ZmAction.ACTION_ZMORGANIZERACTION)); 441 for (var i=0; i<actions.length; i++) { 442 actions[i].redo(); 443 } 444 } 445 }; 446