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 * Creates a dialog containing a listview of items that were (hard) deleted by 30 * the user. 31 * @class 32 * This class represents a dumpster dialog. 33 * 34 * @param {DwtComposite} parent the parent 35 * @param {String} className the class name 36 * 37 * @extends ZmDialog 38 */ 39 ZmDumpsterDialog = function(parent, className) { 40 41 var params = { 42 parent: parent, 43 className: (className || "ZmDumpsterDialog"), 44 title: ZmMsg.recoverDeletedItems, 45 standardButtons: [DwtDialog.CANCEL_BUTTON] 46 }; 47 ZmDialog.call(this, params); 48 49 this.getButton(DwtDialog.CANCEL_BUTTON).setText(ZmMsg.close); 50 51 this._controller = new ZmDumpsterListController(this); 52 }; 53 54 ZmDumpsterDialog.prototype = new ZmDialog; 55 ZmDumpsterDialog.prototype.constructor = ZmDumpsterDialog; 56 57 ZmDumpsterDialog.prototype.toString = 58 function() { 59 return "ZmDumpsterDialog"; 60 }; 61 62 ZmDumpsterDialog.prototype.popup = 63 function(searchFor, types) { 64 this._searchTypes = types ? AjxUtil.toArray(types) : [ZmItem.MSG]; 65 this._searchFor = searchFor; 66 this.runSearchQuery(""); 67 68 ZmDialog.prototype.popup.call(this); 69 }; 70 71 72 ZmDumpsterDialog.prototype.runSearchQuery = 73 function(query) { 74 var types = this._searchTypes; 75 var searchFor = this._searchFor; 76 var params = { 77 query: "-in:/Junk " + query, // Users don't want/need to recover deleted spam. 78 searchFor: searchFor, 79 types: types, 80 sortBy: ZmSearch.DATE_DESC, 81 noRender: true, 82 inDumpster: true, 83 skipUpdateSearchToolbar: true, //don't update the main app search toolbar. Otherwise the main app is updated to weird stuff like the mixed view of everything. 84 callback: this._controller.show.bind(this._controller, [types]) 85 }; 86 this._controller.show(types, null); // Clear the list & set headers 87 appCtxt.getSearchController().search(params); 88 89 }; 90 91 ZmDumpsterDialog.prototype.popdown = 92 function() { 93 ZmDialog.prototype.popdown.call(this); 94 if (this._inputField) { 95 this._inputField.clear(); //clear for next time 96 } 97 98 this._controller.cleanup(); 99 }; 100 101 ZmDumpsterDialog.prototype._contentHtml = 102 function() { 103 this._inputContainerId = this._htmlElId + "_inputContainerId"; 104 this._searchButtonContainerId = this._htmlElId + "_searchButtonContainerId"; 105 return AjxTemplate.expand("share.Widgets#ZmDumpsterDialog", {id:this._htmlElId}); 106 }; 107 108 ZmDumpsterDialog.prototype._listSelectionListener = 109 function(ev) { 110 var sel = this._listview.getSelection() || []; 111 this._toolbar.enableAll((sel.length > 0)); 112 }; 113 114 ZmDumpsterDialog.prototype._handleInputFieldKeyDown = 115 function(ev) { 116 if (ev.keyCode == 13 || ev.keyCode == 3) { 117 this._controller._searchListener(); 118 } 119 }; 120 121 122 123 ZmDumpsterDialog.prototype._resetTabFocus = 124 function(){ 125 this._tabGroup.setFocusMember(this._inputField, true); 126 }; 127 128 /** 129 * adds non-standard elements to tab group. 130 */ 131 ZmDumpsterDialog.prototype._updateTabGroup = 132 function() { 133 this._tabGroup.addMember(this._inputField); 134 this._tabGroup.addMember(this._searchButton); 135 }; 136 137 ZmDumpsterDialog.prototype._initializeSearchBar = 138 function(listener) { 139 140 this._inputField = new DwtInputField({parent: this}); 141 this._inputField.addListener(DwtEvent.ONKEYUP, this._handleInputFieldKeyDown.bind(this));//this._controller._searchListener.bind(this._controller)); 142 143 document.getElementById(this._inputContainerId).appendChild(this._inputField.getHtmlElement()); 144 145 var el = document.getElementById(this._searchButtonContainerId); 146 var params = {parent:this, parentElement:el, id: "searchDumpsterButton"}; 147 148 var button = this._searchButton = new DwtButton(params); 149 el.style.paddingLeft="4px"; 150 151 button.setText(ZmMsg.search); 152 button.addSelectionListener(listener); 153 }; 154 155 ZmDumpsterDialog.prototype.getSearchText = 156 function() { 157 return this._inputField.getValue(); 158 }; 159 160 161 /** 162 * Listview showing deleted items 163 * 164 * @param parent 165 */ 166 ZmDumpsterListView = function(parent, controller) { 167 if (!arguments.length) return; 168 this._controller = controller; 169 var params = { 170 parent: parent, 171 controller: controller, 172 pageless: true, 173 view: this._getViewId(), 174 headerList: this._getHeaderList(), 175 type: this._getType(), 176 parentElement: (parent._htmlElId + "_listview") 177 }; 178 this._type = this._getType(); 179 180 ZmListView.call(this, params); 181 182 this._listChangeListener = new AjxListener(this, this._changeListener); 183 }; 184 185 ZmDumpsterListView.prototype = new ZmListView; 186 ZmDumpsterListView.prototype.constructor = ZmDumpsterListView; 187 188 ZmDumpsterListView.prototype.toString = 189 function() { 190 return "ZmDumpsterListView"; 191 }; 192 ZmDumpsterListView.prototype._getViewId = 193 function() { 194 var type = this._getType(); 195 var appName = ZmItem.APP[type]; 196 return "dumpster" + appName; 197 }; 198 199 ZmDumpsterListView.prototype._getCellId = 200 function(item, field) { 201 return this._getFieldId(item, field); 202 }; 203 204 ZmDumpsterListView.prototype._getType = 205 function() { 206 throw "ZmDumpsterListView.prototype._getType must be overridden by all inheriting classes"; 207 }; 208 209 ZmDumpsterListView.createView = function(view, parent, controller) { 210 var app = view.replace(/^dumpster/,""); 211 switch (app) { 212 case ZmApp.MAIL: 213 return new ZmDumpsterMailListView(parent, controller); 214 case ZmApp.CONTACTS: 215 return new ZmDumpsterContactListView(parent, controller); 216 case ZmApp.CALENDAR: 217 return new ZmDumpsterCalendarListView(parent, controller); 218 case ZmApp.TASKS: 219 return new ZmDumpsterTaskListView(parent, controller); 220 case ZmApp.BRIEFCASE: 221 return new ZmDumpsterBriefcaseListView(parent, controller); 222 } 223 }; 224 225 226 /** 227 * Listview showing deleted mail items 228 * 229 * @param parent 230 * @param controller 231 */ 232 ZmDumpsterMailListView = function(parent, controller) { 233 ZmDumpsterListView.call(this, parent, controller); 234 }; 235 236 ZmDumpsterMailListView.prototype = new ZmDumpsterListView; 237 ZmDumpsterMailListView.prototype.constructor = ZmDumpsterMailListView; 238 ZmDumpsterMailListView.prototype.toString = 239 function() { 240 return "ZmDumpsterMailListView"; 241 }; 242 243 ZmDumpsterMailListView.prototype._getHeaderList = 244 function() { 245 return [ 246 (new DwtListHeaderItem({field:ZmItem.F_FROM, text:ZmMsg.from, width:ZmMsg.COLUMN_WIDTH_FROM_MLV})), 247 (new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})), 248 (new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.received, width:ZmMsg.COLUMN_WIDTH_DATE})) 249 ]; 250 }; 251 252 ZmDumpsterMailListView.prototype._getType = 253 function() { 254 return ZmItem.MSG; 255 }; 256 257 ZmDumpsterMailListView.prototype._getCellContents = 258 function(htmlArr, idx, item, field, colIdx, params) { 259 var content; 260 if (field == ZmItem.F_FROM) { 261 var fromAddr = item.getAddress(AjxEmailAddress.FROM); 262 if (fromAddr) { 263 content = "<span style='white-space:nowrap'>"; 264 var name = fromAddr.getName() || fromAddr.getDispName() || fromAddr.getAddress(); 265 content += AjxStringUtil.htmlEncode(name); 266 content += "</span>"; 267 } 268 } 269 else if (field == ZmItem.F_SUBJECT) { 270 var subj = item.subject || ZmMsg.noSubject; 271 272 content = AjxStringUtil.htmlEncode(subj); 273 } 274 if (content) { 275 htmlArr[idx++] = content; 276 } else { 277 idx = ZmListView.prototype._getCellContents.apply(this, arguments); 278 } 279 return idx; 280 }; 281 282 ZmDumpsterMailListView.prototype._getToolTip = 283 function(params) { 284 return AjxStringUtil.htmlEncode(params.item.getFragment()); 285 }; 286 287 288 /** 289 * Listview showing deleted contact items 290 * 291 * @param parent 292 * @param controller 293 */ 294 ZmDumpsterContactListView = function(parent, controller) { 295 ZmDumpsterListView.call(this, parent, controller); 296 }; 297 298 ZmDumpsterContactListView.prototype = new ZmDumpsterListView; 299 ZmDumpsterContactListView.prototype.constructor = ZmDumpsterContactListView; 300 ZmDumpsterContactListView.prototype.toString = 301 function() { 302 return "ZmDumpsterContactListView"; 303 }; 304 305 ZmDumpsterContactListView.prototype._getHeaderList = 306 function() { 307 return [ 308 (new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name})), 309 (new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.email, width: 200})) 310 ]; 311 }; 312 313 ZmDumpsterContactListView.prototype._getType = 314 function() { 315 return ZmItem.CONTACT; 316 }; 317 318 ZmDumpsterContactListView.prototype._getCellContents = 319 function(htmlArr, idx, item, field, colIdx, params) { 320 var content; 321 if (field == ZmItem.F_NAME) { 322 var name = ZmContact.computeFileAs(item); 323 content = AjxStringUtil.htmlEncode(name); 324 } 325 else if (field == ZmItem.F_EMAIL) { 326 var email = item.getEmail(); 327 content = email && AjxStringUtil.htmlEncode(email) || " "; 328 } 329 if (content) { 330 htmlArr[idx++] = content; 331 } else { 332 idx = ZmListView.prototype._getCellContents.apply(this, arguments); 333 } 334 return idx; 335 }; 336 337 338 /** 339 * Listview showing deleted calendar items 340 * 341 * @param parent 342 * @param controller 343 */ 344 ZmDumpsterCalendarListView = function(parent, controller) { 345 ZmDumpsterListView.call(this, parent, controller); 346 }; 347 348 ZmDumpsterCalendarListView.prototype = new ZmDumpsterListView; 349 ZmDumpsterCalendarListView.prototype.constructor = ZmDumpsterCalendarListView; 350 ZmDumpsterCalendarListView.prototype.toString = 351 function() { 352 return "ZmDumpsterCalendarListView"; 353 }; 354 355 ZmDumpsterCalendarListView.prototype._getHeaderList = 356 function() { 357 return [ 358 (new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})), 359 (new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.date, width:ZmMsg.COLUMN_WIDTH_DATE_CAL})) 360 ]; 361 }; 362 363 ZmDumpsterCalendarListView.prototype._getType = 364 function() { 365 return ZmItem.APPT; 366 }; 367 368 ZmDumpsterCalendarListView.prototype._getCellContents = 369 function(htmlArr, idx, item, field, colIdx, params) { 370 var content; 371 if (field == ZmItem.F_SUBJECT) { 372 var subj = item.name; 373 content = AjxStringUtil.htmlEncode(subj); 374 } 375 else if (field == ZmItem.F_DATE) { 376 content = item.startDate != null 377 ? AjxDateUtil.simpleComputeDateStr(item.startDate) 378 : " "; 379 } 380 if (content) { 381 htmlArr[idx++] = content; 382 } else { 383 idx = ZmListView.prototype._getCellContents.apply(this, arguments); 384 } 385 return idx; 386 }; 387 388 ZmDumpsterCalendarListView.prototype._getToolTip = 389 function(params) { 390 return params.item.fragment; 391 }; 392 393 394 /** 395 * Listview showing deleted task items 396 * 397 * @param parent 398 * @param controller 399 */ 400 ZmDumpsterTaskListView = function(parent, controller) { 401 ZmDumpsterListView.call(this, parent, controller); 402 }; 403 404 ZmDumpsterTaskListView.prototype = new ZmDumpsterListView; 405 ZmDumpsterTaskListView.prototype.constructor = ZmDumpsterTaskListView; 406 ZmDumpsterTaskListView.prototype.toString = 407 function() { 408 return "ZmDumpsterTaskListView"; 409 }; 410 411 ZmDumpsterTaskListView.prototype._getHeaderList = 412 function() { 413 return [ 414 (new DwtListHeaderItem({field:ZmItem.F_SUBJECT, text:ZmMsg.subject})), 415 (new DwtListHeaderItem({field:ZmItem.F_STATUS, text:ZmMsg.status, width:ZmMsg.COLUMN_WIDTH_STATUS_TLV})), 416 (new DwtListHeaderItem({field:ZmItem.F_DATE, text:ZmMsg.date, width:ZmMsg.COLUMN_WIDTH_DATE_DUE_TLV})) 417 ]; 418 }; 419 420 ZmDumpsterTaskListView.prototype._getType = 421 function() { 422 return ZmItem.TASK; 423 }; 424 425 ZmDumpsterTaskListView.prototype._getCellContents = 426 function(htmlArr, idx, item, field, colIdx, params) { 427 var content; 428 if (field == ZmItem.F_SUBJECT) { 429 var subj = item.name; 430 content = AjxStringUtil.htmlEncode(subj); 431 } 432 else if (field == ZmItem.F_STATUS) { 433 var status = item.status; 434 content = ZmCalItem.getLabelForStatus(item.status); 435 } 436 else if (field == ZmItem.F_DATE) { 437 content = item.endDate != null 438 ? AjxDateUtil.simpleComputeDateStr(item.endDate) 439 : " "; 440 } 441 if (content) { 442 htmlArr[idx++] = content; 443 } else { 444 idx = ZmListView.prototype._getCellContents.apply(this, arguments); 445 } 446 return idx; 447 }; 448 449 ZmDumpsterTaskListView.prototype._getToolTip = 450 function(params) { 451 return params.item.fragment; 452 }; 453 454 455 /** 456 * Listview showing deleted briefcase items 457 * 458 * @param parent 459 * @param controller 460 */ 461 ZmDumpsterBriefcaseListView = function(parent, controller) { 462 ZmDumpsterListView.call(this, parent, controller); 463 }; 464 465 ZmDumpsterBriefcaseListView.prototype = new ZmDumpsterListView; 466 ZmDumpsterBriefcaseListView.prototype.constructor = ZmDumpsterBriefcaseListView; 467 ZmDumpsterBriefcaseListView.prototype.toString = 468 function() { 469 return "ZmDumpsterBriefcaseListView"; 470 }; 471 472 ZmDumpsterBriefcaseListView.prototype._getHeaderList = 473 function() { 474 return [ 475 (new DwtListHeaderItem({field:ZmItem.F_NAME, text:ZmMsg.name})), 476 (new DwtListHeaderItem({field:ZmItem.F_FILE_TYPE, text:ZmMsg.type, width:ZmMsg.COLUMN_WIDTH_TYPE_DLV})), 477 (new DwtListHeaderItem({field:ZmItem.F_SIZE, text:ZmMsg.size, width:ZmMsg.COLUMN_WIDTH_SIZE_DLV})) 478 ]; 479 }; 480 481 ZmDumpsterBriefcaseListView.prototype._getType = 482 function() { 483 return ZmItem.BRIEFCASE_ITEM; 484 }; 485 486 ZmDumpsterBriefcaseListView.prototype._getCellContents = 487 function(htmlArr, idx, item, field, colIdx, params) { 488 var content; 489 if (field == ZmItem.F_NAME) { 490 var name = item.name; 491 content = AjxStringUtil.htmlEncode(name); 492 } 493 else if (field == ZmItem.F_FILE_TYPE) { 494 if (item.isFolder) { 495 content = ZmMsg.folder; 496 } else { 497 var mimeInfo = item.contentType ? ZmMimeTable.getInfo(item.contentType) : null; 498 content = mimeInfo ? mimeInfo.desc : " "; 499 } 500 } 501 else if (field == ZmItem.F_SIZE) { 502 var size = item.size; 503 content = AjxUtil.formatSize(size); 504 } 505 506 if (content) { 507 htmlArr[idx++] = content; 508 } else { 509 idx = ZmListView.prototype._getCellContents.apply(this, arguments); 510 } 511 return idx; 512 }; 513 514 515 /** 516 * Controller for the ZmDumpsterListView 517 * 518 * @param container [DwtControl] container this controller "controls" 519 */ 520 ZmDumpsterListController = function(container) { 521 ZmListController.call(this, container, appCtxt.getApp(ZmApp.MAIL)); 522 }; 523 524 ZmDumpsterListController.prototype = new ZmListController; 525 ZmDumpsterListController.prototype.constructor = ZmDumpsterListController; 526 527 ZmDumpsterListController.prototype.toString = 528 function() { 529 return "ZmDumpsterListController"; 530 }; 531 532 ZmDumpsterListController.prototype.show = 533 function(types, results) { 534 535 this._appName = ZmItem.APP[types[0]]; // All types should be in the same app 536 var view = "dumpster" + this._appName; 537 this.setCurrentViewId(view); 538 for (var id in this._toolbar) { 539 this._toolbar[id].setVisible(id == view); 540 } 541 for (var id in this._listView) { 542 this._listView[id].setVisible(id == view); 543 } 544 if (results) { 545 var searchResults = results.getResponse(); 546 547 // call base class 548 ZmListController.prototype.show.call(this, searchResults, view); 549 550 this._setup(view); 551 this._initializeSearchBar(); 552 this._container._updateTabGroup(); 553 this._container._inputField.getInputElement().focus(); //this is the only focus way I could get it to focus on the input element. 554 555 var list = searchResults.getResults(searchResults.type); 556 557 this.setList(list); 558 this.setHasMore(searchResults.getAttribute("more")); 559 this.getCurrentView().set(list); 560 } else { 561 this.cleanup(); 562 } 563 }; 564 565 ZmDumpsterListController.prototype.cleanup = 566 function() { 567 var currentView = this.getCurrentView(); 568 if (currentView) { 569 currentView.removeAll(); 570 } 571 }; 572 573 ZmDumpsterListController.prototype._createNewView = 574 function(view) { 575 return ZmDumpsterListView.createView(view, this._container, this); 576 }; 577 578 ZmDumpsterListController.prototype._setViewContents = 579 function(view) { 580 this._listView[view].set(this._list); 581 }; 582 583 ZmDumpsterListController.prototype._searchListener = 584 function() { 585 var dialog = this._container; 586 var keywords = dialog.getSearchText(); 587 dialog.runSearchQuery(keywords); 588 }; 589 590 ZmDumpsterListController.prototype._initializeSearchBar = 591 function() { 592 if (this._searchBarInitialized) { 593 return; 594 } 595 this._searchBarInitialized = true; 596 var dialog = this._container; 597 dialog._initializeSearchBar(this._searchListener.bind(this)); 598 }; 599 600 601 ZmDumpsterListController.prototype._initializeToolBar = 602 function(view) { 603 if (this._toolbar[view]) { return; } 604 605 var overrides = {}; 606 overrides[ZmOperation.MOVE] = {showImageInToolbar: false, 607 showTextInToolbar : true, 608 textKey: "recoverTo"}; 609 var tbParams = { 610 parent: this._container, 611 className: "ZmDumpsterDialog-toolbar", 612 buttons: [ZmOperation.MOVE], 613 overrides: overrides, 614 posStyle: Dwt.RELATIVE_STYLE, 615 context: view, 616 controller: this, 617 parentElement: (this._container._htmlElId + "_toolbar") 618 }; 619 var tb = this._toolbar[view] = new ZmButtonToolBar(tbParams); 620 tb.addSelectionListener(ZmOperation.MOVE, new AjxListener(this, this._moveListener)); 621 622 }; 623 624 ZmDumpsterListController.prototype._doMove = 625 function(items, folder, attrs, isShiftKey) { 626 if (!attrs) { 627 attrs = {}; 628 } 629 attrs.op = "recover"; 630 attrs.l = folder.id; 631 632 ZmListController.prototype._doMove.call(this, items, folder, attrs, isShiftKey, true); 633 }; 634 635 ZmDumpsterListController.prototype._getMoreSearchParams = 636 function(params) { 637 params.inDumpster = true; 638 }; 639 640 ZmDumpsterListController.prototype._getMoveParams = 641 function(dlg) { 642 var params = ZmListController.prototype._getMoveParams.call(this, dlg); 643 params.appName = this._appName; 644 params.overviewId = dlg.getOverviewId(this._appName); 645 params.treeIds = [ZmApp.ORGANIZER[this._appName]]; 646 params.acceptFolderMatch = true; 647 params.showDrafts = true; 648 var omit = {}; 649 omit[ZmFolder.ID_SPAM] = true; 650 //bug:60237 remote folders should be excluded from the recovery folder selection 651 var folderTree = appCtxt.getFolderTree(); 652 if (!folderTree) { return params; } 653 var folders = folderTree.getByType(ZmApp.ORGANIZER[this._appName]); 654 for (var i = 0; i < folders.length; i++) { 655 var folder = folders[i]; 656 if(folder.link && folder.isRemote()) { 657 omit[folder.id] = true; 658 } 659 } 660 params.omit = omit; 661 return params; 662 }; 663