1 /* 2 * ***** BEGIN LICENSE BLOCK ***** 3 * Zimbra Collaboration Suite Web Client 4 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 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) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved. 21 * ***** END LICENSE BLOCK ***** 22 */ 23 24 /** 25 * Creates a new, empty double pane controller. 26 * @constructor 27 * @class 28 * This class manages the two-pane view. The top pane contains a list view of 29 * items, and the bottom pane contains the selected item content. 30 * 31 * @author Parag Shah 32 * 33 * @param {DwtControl} container the containing shell 34 * @param {ZmApp} mailApp the containing application 35 * @param {constant} type type of controller 36 * @param {string} sessionId the session id 37 * @param {ZmSearchResultsController} searchResultsController containing controller 38 * 39 * @extends ZmMailListController 40 */ 41 ZmDoublePaneController = function(container, mailApp, type, sessionId, searchResultsController) { 42 43 if (arguments.length == 0) { return; } 44 45 ZmMailListController.apply(this, arguments); 46 47 if (this.supportsDnD()) { 48 this._dragSrc = new DwtDragSource(Dwt.DND_DROP_MOVE); 49 this._dragSrc.addDragListener(this._dragListener.bind(this)); 50 } 51 52 this._listSelectionShortcutDelayAction = new AjxTimedAction(this, this._listSelectionTimedAction); 53 this._listeners[ZmOperation.KEEP_READING] = this._keepReadingListener.bind(this); 54 }; 55 56 ZmDoublePaneController.prototype = new ZmMailListController; 57 ZmDoublePaneController.prototype.constructor = ZmDoublePaneController; 58 59 ZmDoublePaneController.prototype.isZmDoublePaneController = true; 60 ZmDoublePaneController.prototype.toString = function() { return "ZmDoublePaneController"; }; 61 62 ZmDoublePaneController.LIST_SELECTION_SHORTCUT_DELAY = 300; 63 64 ZmDoublePaneController.RP_IDS = [ZmSetting.RP_BOTTOM, ZmSetting.RP_RIGHT, ZmSetting.RP_OFF]; 65 66 ZmDoublePaneController.DEFAULT_TAB_TEXT = ZmMsg.conversation; 67 68 /** 69 * Displays the given list of mail items in a two-pane view where one pane shows the list 70 * and the other shows the currently selected mail item (msg or conv). This method takes 71 * care of displaying the list. Displaying an item is typically handled via selection. 72 * 73 * @param {ZmSearchResults} results the current search results 74 * @param {ZmMailList} mailList list of mail items 75 * @param {AjxCallback} callback the client callback 76 * @param {Boolean} markRead if <code>true</code>, mark msg read 77 * 78 */ 79 ZmDoublePaneController.prototype.show = 80 function(results, mailList, callback, markRead) { 81 82 ZmMailListController.prototype.show.call(this, results); 83 84 var mlv = this._listView[this._currentViewId]; 85 86 // if search was run as a result of a <refresh> block rather than by the user, preserve 87 // what's in the reading pane as long as it's still in the list of results 88 var s = results && results.search; 89 var isRefresh = s && (s.isRefresh || s.isRedo); 90 var refreshSelItem = (isRefresh && mlv && mlv.hasItem(s.selectedItem) && s.selectedItem); 91 if (this._doublePaneView) { 92 if (!refreshSelItem) { 93 this._doublePaneView._itemView.reset(); 94 } 95 } 96 this.setList(mailList); 97 this._setup(this._currentViewId); 98 mlv = this._listView[this._currentViewId]; //might have been created in the _setup call 99 mlv.reset(); //called to reset the groups (in case "group by" is used, to clear possible previous items in it - bug 77154 100 101 this._displayResults(this._currentViewId, null, refreshSelItem); 102 103 if (refreshSelItem) { 104 mlv.setSelection(refreshSelItem, true); 105 this._resetOperations(this._toolbar[this._currentViewId], 1) 106 } 107 else { 108 var dpv = this._doublePaneView; 109 var readingPaneOn = this.isReadingPaneOn(); 110 if (dpv.isReadingPaneVisible() != readingPaneOn) { 111 dpv.setReadingPane(); 112 } 113 // clear the item view, unless it's showing something selected 114 if (!this._itemViewCurrent()) { 115 dpv.clearItem(); 116 } 117 } 118 119 if (callback) { 120 callback.run(); 121 } 122 }; 123 124 // returns true if the item shown in the reading pane is selected in the list view 125 ZmDoublePaneController.prototype._itemViewCurrent = 126 function() { 127 128 var dpv = this._doublePaneView; 129 var mlv = dpv._mailListView; 130 mlv._restoreState(); 131 var item = dpv.getItem(); 132 if (item) { 133 var sel = mlv.getSelection(); 134 for (var i = 0, len = sel.length; i < len; i++) { 135 var listItem = sel[i]; 136 if (listItem.id == item.id) { 137 return true; 138 } 139 } 140 } 141 return false; 142 }; 143 144 ZmDoublePaneController.prototype.switchView = 145 function(view, force) { 146 if (view === ZmSetting.RP_OFF || view === ZmSetting.RP_BOTTOM || view === ZmSetting.RP_RIGHT) { 147 this._mailListView._colHeaderActionMenu = null; 148 var oldView = this._getReadingPanePref(); 149 if (view !== oldView) { 150 var convView = this._convView; 151 if (convView) { 152 var replyView = convView._replyView; 153 if (replyView && view === ZmSetting.RP_OFF) { 154 // reset the replyView with the warning before turning off the pane 155 if (!force && !convView._controller.popShield(null, this.switchView.bind(this, view, true))) { 156 // redo setChecked on the oldView menu item if user cancels 157 this._readingPaneViewMenu.getMenuItem(oldView)._setChecked(true); 158 return; 159 } 160 this._readingPaneViewMenu.getMenuItem(view)._setChecked(true); 161 replyView.reset(); 162 } 163 } 164 this._setReadingPanePref(view); 165 this._doublePaneView.setReadingPane(true); 166 if (replyView && view !== ZmSetting.RP_OFF) { 167 replyView._resized(); 168 } 169 } 170 } else { 171 ZmMailListController.prototype.switchView.apply(this, arguments); 172 } 173 this._resetNavToolBarButtons(); 174 }; 175 176 /** 177 * Clears the conversation view, which actually just clears the message view. 178 */ 179 ZmDoublePaneController.prototype.reset = 180 function() { 181 if (this._doublePaneView) { 182 this._doublePaneView.reset(); 183 } 184 var lv = this._listView[this._currentViewId]; 185 if (lv) { 186 lv._itemToSelect = lv._selectedItem = null; 187 } 188 }; 189 190 ZmDoublePaneController.prototype._handleResponseSwitchView = 191 function(item) { 192 this._doublePaneView.setItem(item); 193 }; 194 195 // called after a delete has occurred. 196 // Return value indicates whether view was popped as a result of a delete 197 ZmDoublePaneController.prototype.handleDelete = 198 function() { 199 return false; 200 }; 201 202 ZmDoublePaneController.prototype.handleKeyAction = 203 function(actionCode, ev) { 204 205 DBG.println(AjxDebug.DBG3, "ZmDoublePaneController.handleKeyAction"); 206 var lv = this._listView[this._currentViewId]; 207 208 switch (actionCode) { 209 210 case DwtKeyMap.SELECT_NEXT: 211 case DwtKeyMap.SELECT_PREV: 212 if (lv) { 213 return lv.handleKeyAction(actionCode, ev); 214 } 215 break; 216 217 default: 218 return ZmMailListController.prototype.handleKeyAction.apply(this, arguments); 219 } 220 return true; 221 }; 222 223 // Private and protected methods 224 225 ZmDoublePaneController.prototype._createDoublePaneView = 226 function() { 227 // overload me 228 }; 229 230 // Creates the conv view, which is not a standard list view (it's a two-pane 231 // sort of thing). 232 ZmDoublePaneController.prototype._initialize = 233 function(view) { 234 // set up double pane view (which creates the MLV and MV) 235 if (!this._doublePaneView){ 236 var dpv = this._doublePaneView = this._createDoublePaneView(); 237 this._mailListView = dpv.getMailListView(); 238 dpv.addInviteReplyListener(this._inviteReplyListener); 239 dpv.addShareListener(this._shareListener); 240 dpv.addSubscribeListener(this._subscribeListener); 241 } 242 243 ZmMailListController.prototype._initialize.call(this, view); 244 }; 245 246 ZmDoublePaneController.prototype._initializeNavToolBar = 247 function(view) { 248 var toolbar = this._toolbar[view]; 249 this._itemCountText[ZmSetting.RP_BOTTOM] = toolbar.getButton(ZmOperation.TEXT); 250 if (AjxEnv.isFirefox) { 251 this._itemCountText[ZmSetting.RP_BOTTOM].setScrollStyle(Dwt.CLIP); 252 } 253 }; 254 255 ZmDoublePaneController.prototype._getRightSideToolBarOps = 256 function() { 257 var list = []; 258 if (appCtxt.isChildWindow) { 259 return list; 260 } 261 list.push(ZmOperation.KEEP_READING); 262 list.push(ZmOperation.VIEW_MENU); 263 return list; 264 }; 265 266 ZmDoublePaneController.prototype._getActionMenuOps = 267 function() { 268 var list = []; 269 list = list.concat(this._msgOps()); 270 list.push(ZmOperation.SEP); 271 list = list.concat(this._deleteOps()); 272 list.push(ZmOperation.SEP); 273 list = list.concat(this._standardActionMenuOps()); 274 list.push(ZmOperation.SEP); 275 list = list.concat(this._flagOps()); 276 list.push(ZmOperation.SEP); 277 list.push(ZmOperation.REDIRECT); 278 list.push(ZmOperation.EDIT_AS_NEW); // bug #28717 279 list.push(ZmOperation.SEP); 280 list = list.concat(this._createOps()); 281 list.push(ZmOperation.SEP); 282 list = list.concat(this._otherOps()); 283 if (this.getCurrentViewType() == ZmId.VIEW_TRAD) { 284 list.push(ZmOperation.SHOW_CONV); 285 } 286 //list.push(ZmOperation.QUICK_COMMANDS); 287 return list; 288 }; 289 290 // Returns the already-created message list view. 291 ZmDoublePaneController.prototype._createNewView = 292 function() { 293 if (this._mailListView && this._dragSrc) { 294 this._mailListView.setDragSource(this._dragSrc); 295 } 296 return this._mailListView; 297 }; 298 299 /** 300 * Returns the double-pane view. 301 * 302 * @return {ZmDoublePaneView} double-pane view 303 */ 304 ZmDoublePaneController.prototype.getCurrentView = 305 function() { 306 return this._doublePaneView; 307 }; 308 ZmDoublePaneController.prototype.getReferenceView = ZmDoublePaneController.prototype.getCurrentView; 309 310 /** 311 * Returns the item view. 312 * 313 * @return {ZmMailItemView} item view 314 */ 315 ZmDoublePaneController.prototype.getItemView = 316 function() { 317 return this._doublePaneView && this._doublePaneView._itemView; 318 }; 319 320 ZmDoublePaneController.prototype._getTagMenuMsg = 321 function(num) { 322 return AjxMessageFormat.format(ZmMsg.tagMessages, num); 323 }; 324 325 ZmDoublePaneController.prototype._getMoveDialogTitle = 326 function(num) { 327 return AjxMessageFormat.format(ZmMsg.moveMessages, num); 328 }; 329 330 // Add reading pane to focus ring 331 ZmDoublePaneController.prototype._initializeTabGroup = 332 function(view) { 333 if (this._tabGroups[view]) { return; } 334 335 ZmListController.prototype._initializeTabGroup.apply(this, arguments); 336 337 if (this._view[view] !== this.getItemView()) { 338 this._tabGroups[view].addMember(this.getItemView().getTabGroupMember()); 339 } 340 }; 341 342 ZmDoublePaneController.prototype._setViewContents = 343 function(view) { 344 this._doublePaneView.setList(this._list); 345 }; 346 347 ZmDoublePaneController.prototype._displayItem = 348 function(item) { 349 350 if (!item._loaded) { return; } 351 352 // cancel timed mark read action on previous msg 353 appCtxt.killMarkReadTimer(); 354 355 this._doublePaneView.setItem(item); 356 this._handleMarkRead(item); 357 this._curItem = item; 358 }; 359 ZmDoublePaneController.prototype._displayMsg = ZmDoublePaneController.prototype._displayItem; 360 361 362 ZmDoublePaneController.prototype._markReadAction = 363 function(msg) { 364 this._doMarkRead([msg], true); 365 }; 366 367 ZmDoublePaneController.prototype._preHideCallback = 368 function() { 369 // cancel timed mark read action on view change 370 appCtxt.killMarkReadTimer(); 371 return ZmController.prototype._preHideCallback.call(this); 372 }; 373 374 // Adds a "Reading Pane" menu to the View menu 375 ZmDoublePaneController.prototype._setupReadingPaneMenu = 376 function(view, menu) { 377 378 var readingPaneMenuItem = menu.createMenuItem(Dwt.getNextId("READING_PANE_"), { 379 text: ZmMsg.readingPane, 380 style: DwtMenuItem.NO_STYLE 381 }), 382 readingPaneMenu = new ZmPopupMenu(readingPaneMenuItem); 383 384 var miParams = { 385 text: ZmMsg.readingPaneAtBottom, 386 style: DwtMenuItem.RADIO_STYLE, 387 radioGroupId: "RP" 388 }; 389 var ids = ZmDoublePaneController.RP_IDS; 390 var pref = this._getReadingPanePref(); 391 for (var i = 0; i < ids.length; i++) { 392 var id = ids[i]; 393 if (!readingPaneMenu._menuItems[id]) { 394 miParams.text = ZmMailListController.READING_PANE_TEXT[id]; 395 miParams.image = ZmMailListController.READING_PANE_ICON[id]; 396 var mi = readingPaneMenu.createMenuItem(id, miParams); 397 mi.setData(ZmOperation.MENUITEM_ID, id); 398 mi.addSelectionListener(this._listeners[ZmOperation.VIEW]); 399 if (id == pref) { 400 mi.setChecked(true, true); 401 } 402 } 403 } 404 405 readingPaneMenuItem.setMenu(readingPaneMenu); 406 407 return readingPaneMenu; 408 }; 409 410 ZmDoublePaneController.prototype._displayResults = 411 function(view, newTab, refreshSelItem) { 412 413 var elements = this.getViewElements(view, this._doublePaneView); 414 415 if (!refreshSelItem) { 416 this._doublePaneView.setReadingPane(); 417 } 418 419 var tabId = newTab && Dwt.getNextId(); 420 this._setView({ view: view, 421 noPush: this.isSearchResults, 422 viewType: this._currentViewType, 423 elements: elements, 424 hide: this._elementsToHide, 425 tabParams: newTab && this._getTabParams(tabId, this._tabCallback.bind(this)), 426 isAppView: this._isTopLevelView()}); 427 if (this.isSearchResults) { 428 // if we are switching views, make sure app view mgr is up to date on search view's components 429 appCtxt.getAppViewMgr().setViewComponents(this.searchResultsController.getCurrentViewId(), elements, true); 430 } 431 this._resetNavToolBarButtons(view); 432 433 if (newTab) { 434 var buttonText = (this._conv && this._conv.subject) ? this._conv.subject.substr(0, ZmAppViewMgr.TAB_BUTTON_MAX_TEXT) : ZmDoublePaneController.DEFAULT_TAB_TEXT; 435 var avm = appCtxt.getAppViewMgr(); 436 avm.setTabTitle(view, buttonText); 437 } 438 439 // always allow derived classes to reset size after loading 440 var sz = this._doublePaneView.getSize(); 441 this._doublePaneView._resetSize(sz.x, sz.y); 442 }; 443 444 ZmDoublePaneController.prototype._getTabParams = 445 function(tabId, tabCallback) { 446 return { 447 id: tabId, 448 image: "ConvView", 449 textPrecedence: 85, 450 tooltip: ZmDoublePaneController.DEFAULT_TAB_TEXT, 451 tabCallback: tabCallback 452 }; 453 }; 454 455 456 /** 457 * Loads and displays the given message. If the message was unread, it gets marked as 458 * read, and the conversation may be marked as read as well. Note that we request no 459 * busy overlay during the SOAP call - that's so that a subsequent click within the 460 * double-click threshold can be processed. Otherwise, it's very difficult to generate 461 * a double click because the first click triggers a SOAP request and the ensuing busy 462 * overlay blocks the receipt of the second click. 463 * 464 * @param msg [ZmMailMsg] msg to load 465 * 466 * @private 467 */ 468 ZmDoublePaneController.prototype._doGetMsg = 469 function(msg) { 470 if (!msg) { return; } 471 if (msg.id == this._pendingMsg) { return; } 472 473 msg._loadPending = true; 474 this._pendingMsg = msg.id; 475 var respCallback = new AjxCallback(this, this._handleResponseDoGetMsg, msg); 476 msg.load({callback:respCallback, noBusyOverlay:true}); 477 }; 478 479 ZmDoublePaneController.prototype._handleResponseDoGetMsg = 480 function(msg) { 481 if (this._pendingMsg && (msg.id != this._pendingMsg)) { return; } 482 msg._loadPending = false; 483 this._pendingMsg = null; 484 this._doublePaneView.setItem(msg); 485 }; 486 487 ZmDoublePaneController.prototype._resetOperations = 488 function(parent, num) { 489 ZmMailListController.prototype._resetOperations.call(this, parent, num); 490 var isDraft = this.isDraftsFolder(); 491 if (num == 1) { 492 var item = this._mailListView.getSelection()[0]; 493 if (item) { 494 isDraft = item.isDraft; 495 } 496 parent.enable(ZmOperation.SHOW_ORIG, true); 497 if (appCtxt.get(ZmSetting.FILTERS_ENABLED)) { 498 var isSyncFailuresFolder = this.isSyncFailuresFolder(); 499 parent.enable(ZmOperation.ADD_FILTER_RULE, !isSyncFailuresFolder); 500 } 501 } 502 503 parent.enable(ZmOperation.DETACH, (appCtxt.get(ZmSetting.DETACH_MAILVIEW_ENABLED) && num == 1 && !isDraft)); 504 parent.enable(ZmOperation.TEXT, true); 505 parent.enable(ZmOperation.KEEP_READING, this._keepReading(true)); 506 507 if (appCtxt.isWebClientOffline()) { 508 parent.enable( 509 [ 510 ZmOperation.ACTIONS_MENU, 511 ZmOperation.VIEW_MENU, 512 ZmOperation.DETACH, 513 ZmOperation.SHOW_ORIG, 514 ZmOperation.SHOW_CONV, 515 ZmOperation.PRINT, 516 ZmOperation.ADD_FILTER_RULE, 517 ZmOperation.CREATE_APPT, 518 ZmOperation.CREATE_TASK, 519 ZmOperation.CONTACT, 520 ZmOperation.REDIRECT 521 ], 522 false 523 ); 524 } 525 }; 526 527 ZmDoublePaneController.prototype._resetOperation = 528 function(parent, op, num) { 529 if (parent && op == ZmOperation.KEEP_READING) { 530 parent.enable(ZmOperation.KEEP_READING, this._keepReading(true)); 531 } 532 }; 533 534 // top level view means this view is allowed to get shown when user clicks on 535 // app icon in app toolbar - overload to not allow this. 536 ZmDoublePaneController.prototype._isTopLevelView = 537 function() { 538 var sessionId = this.getSessionId(); 539 return (!sessionId || (sessionId == ZmApp.MAIN_SESSION)); 540 }; 541 542 // All items in the list view are gone - show "No Results" and clear reading pane 543 ZmDoublePaneController.prototype._handleEmptyList = 544 function(listView) { 545 this.reset(); 546 ZmMailListController.prototype._handleEmptyList.apply(this, arguments); 547 }; 548 549 // List listeners 550 551 // Clicking on a message in the message list loads and displays it. 552 ZmDoublePaneController.prototype._listSelectionListener = 553 function(ev) { 554 555 var handled = ZmMailListController.prototype._listSelectionListener.call(this, ev); 556 557 var currView = this._listView[this._currentViewId]; 558 559 if (!handled && ev.detail == DwtListView.ITEM_DBL_CLICKED) { 560 var item = ev.item; 561 if (!item) { return; } 562 563 var cs = appCtxt.isOffline && appCtxt.getCurrentSearch(); 564 if (cs && cs.isMultiAccount()) { 565 appCtxt.accountList.setActiveAccount(item.getAccount()); 566 } 567 568 var div = this._mailListView.getTargetItemDiv(ev); 569 this._mailListView._itemSelected(div, ev); 570 571 if (appCtxt.get(ZmSetting.SHOW_SELECTION_CHECKBOX)) { 572 this._mailListView.setSelectionHdrCbox(false); 573 } 574 575 var respCallback = new AjxCallback(this, this._handleResponseListSelectionListener, item); 576 var folder = appCtxt.getById(item.getFolderId()); 577 if (item.isDraft && folder && folder.id == ZmFolder.ID_DRAFTS && (!folder || !folder.isReadOnly())) { 578 this._doAction({ev:ev, action:ZmOperation.DRAFT}); 579 return true; 580 } else if (appCtxt.get(ZmSetting.OPEN_MAIL_IN_NEW_WIN)) { 581 this._detachListener(null, respCallback); 582 return true; 583 } else { 584 var respCallback = 585 this._handleResponseListSelectionListener.bind(this, item); 586 var ctlr = 587 AjxDispatcher.run(item.type === ZmItem.CONV ? 588 "GetConvController" : "GetMsgController", 589 item.nId); 590 ctlr.show(item, this, respCallback, true); 591 return true; 592 } 593 } else if (!handled) { 594 if (this.isReadingPaneOn()) { 595 // Give the user a chance to zip around the list view via shortcuts without having to 596 // wait for each successively selected msg to load, by waiting briefly for more list 597 // selection shortcut actions. An event will have the 'kbNavEvent' property set if it's 598 // the result of a shortcut. 599 if (ev.kbNavEvent && ZmDoublePaneController.LIST_SELECTION_SHORTCUT_DELAY) { 600 if (this._listSelectionShortcutDelayActionId) { 601 AjxTimedAction.cancelAction(this._listSelectionShortcutDelayActionId); 602 } 603 this._listSelectionShortcutDelayActionId = AjxTimedAction.scheduleAction(this._listSelectionShortcutDelayAction, 604 ZmDoublePaneController.LIST_SELECTION_SHORTCUT_DELAY); 605 } else { 606 this._setSelectedItem(); 607 } 608 return true; 609 } else { 610 var msg = currView.getSelection()[0]; 611 if (msg) { 612 this._doublePaneView.resetMsg(msg); 613 } 614 } 615 } 616 617 return handled; 618 }; 619 620 ZmDoublePaneController.prototype._handleResponseListSelectionListener = 621 function(item) { 622 if (item.type == ZmItem.MSG && item._loaded && item.isUnread && 623 (appCtxt.get(ZmSetting.MARK_MSG_READ) == ZmSetting.MARK_READ_NOW)) { 624 625 this._list.markRead([item], true); 626 } 627 // make sure correct msg is displayed in msg pane when user returns 628 this._setSelectedItem(); 629 }; 630 631 ZmDoublePaneController.prototype._listSelectionTimedAction = 632 function() { 633 if (this._listSelectionShortcutDelayActionId) { 634 AjxTimedAction.cancelAction(this._listSelectionShortcutDelayActionId); 635 } 636 this._setSelectedItem(); 637 }; 638 639 /** 640 * Handles selection of a row by loading the item. 641 * 642 * @param {hash} params params for loading the item 643 * 644 * @private 645 */ 646 ZmDoublePaneController.prototype._setSelectedItem = 647 function(params) { 648 var selCnt = this._listView[this._currentViewId].getSelectionCount(); 649 if (selCnt == 1) { 650 var respCallback = this._handleResponseSetSelectedItem.bind(this); 651 this._getLoadedMsg(params, respCallback); 652 } 653 }; 654 655 ZmDoublePaneController.prototype._handleResponseSetSelectedItem = 656 function(msg) { 657 658 if (msg) { 659 // bug 41196 660 if (appCtxt.isOffline) { 661 // clear the new-mail badge every time user reads a msg regardless 662 // of number of unread messages across all accounts 663 this._app.clearNewMailBadge(); 664 665 // offline mode, reset new mail notifier if user reads a msg from that account 666 var acct = msg.getAccount(); 667 668 // bug: 46873 - set active account when user clicks on item w/in cross-account search 669 var cs = appCtxt.getCurrentSearch(); 670 if (cs && cs.isMultiAccount()) { 671 var active = acct || appCtxt.accountList.defaultAccount 672 appCtxt.accountList.setActiveAccount(active); 673 } 674 675 if (acct && acct.inNewMailMode) { 676 acct.inNewMailMode = false; 677 var allContainers = appCtxt.getOverviewController()._overviewContainer; 678 for (var i in allContainers) { 679 allContainers[i].updateAccountInfo(acct, true, true); 680 } 681 } 682 } 683 684 if (!this.isReadingPaneOn()) { 685 return; 686 } 687 // make sure list view has this msg 688 var lv = this._listView[this._currentViewId]; 689 var id = (lv.type == ZmItem.CONV && msg.type == ZmItem.MSG) ? msg.cid : msg.id; 690 if (lv.hasItem(id)) { 691 this._displayMsg(msg); 692 } 693 } 694 }; 695 696 ZmDoublePaneController.prototype._listActionListener = 697 function(ev) { 698 ZmMailListController.prototype._listActionListener.call(this, ev); 699 700 if (!this.isReadingPaneOn()) { 701 // reset current message 702 var msg = this._listView[this._currentViewId].getSelection()[0]; 703 if (msg) { 704 this._doublePaneView.resetMsg(msg); 705 } 706 } 707 }; 708 709 ZmDoublePaneController.prototype._doDelete = 710 function(items, hardDelete, attrs, confirmDelete) { 711 this._listView[this._currentViewId]._itemToSelect = this._getNextItemToSelect(); 712 ZmMailListController.prototype._doDelete.apply(this, arguments); 713 }; 714 715 ZmDoublePaneController.prototype._doMove = 716 function(items, destinationFolder, attrs, isShiftKey) { 717 718 // if user moves a non-selected item via DnD, don't change selection 719 var dndUnselectedItem = false; 720 if (items && items.length == 1) { 721 var lv = this.getListView(); 722 var selection = lv && lv.getSelection(); 723 if (selection && selection.length) { 724 dndUnselectedItem = true; 725 var moveItem = items[0]; 726 var id = moveItem.id; 727 var msgIdMap = {}; 728 var numSelectedInConv = 0; 729 if ((moveItem.type === ZmId.ITEM_CONV) && moveItem.msgIds) { 730 // If the moved item is a conversation, we need to check whether the selected items are all messages 731 // within the conversation. Create a hash for more efficient testing. 732 for (var i = 0; i < moveItem.msgIds.length; i++) { 733 msgIdMap[moveItem.msgIds[i]] = true; 734 } 735 } 736 // AjxUtil.intersection doesn't work with objects, so check IDs 737 for (var i = 0; i < selection.length; i++) { 738 if (selection[i].id == id) { 739 dndUnselectedItem = false; 740 } 741 if (msgIdMap[selection[i].id]) { 742 numSelectedInConv++; 743 } 744 } 745 if (numSelectedInConv == selection.length) { 746 // All the selected items are messages within a moved conversation, use the normal getNextItemToSelect 747 dndUnselectedItem = false; 748 } 749 } 750 } 751 if (!dndUnselectedItem) { 752 this._listView[this._currentViewId]._itemToSelect = this._getNextItemToSelect(); 753 } 754 ZmMailListController.prototype._doMove.apply(this, arguments); 755 }; 756 757 ZmDoublePaneController.prototype._keepReadingListener = 758 function(ev) { 759 this.handleKeyAction(ZmKeyMap.KEEP_READING, ev); 760 }; 761 762 ZmDoublePaneController.prototype._keepReading = function(ev) {}; 763 764 // Set enabled state of the KEEP_READING button 765 ZmDoublePaneController.prototype._checkKeepReading = 766 function() { 767 // done on timer so item view has had change to lay out and resize 768 setTimeout(this._resetOperation.bind(this, this._toolbar[this._currentViewId], ZmOperation.KEEP_READING), 250); 769 }; 770 771 ZmDoublePaneController.handleScroll = 772 function(ev) { 773 var target = DwtUiEvent.getTarget(ev); 774 var messagesView = DwtControl.findControl(target); 775 var controller = messagesView && messagesView._controller; 776 if (controller && controller._checkKeepReading) { 777 controller._checkKeepReading(); 778 } 779 }; 780 781 ZmDoublePaneController.prototype._dragListener = 782 function(ev) { 783 ZmListController.prototype._dragListener.call(this, ev); 784 if (ev.action == DwtDragEvent.DRAG_END) { 785 this._resetOperations(this._toolbar[this._currentViewId], this._doublePaneView.getSelection().length); 786 } 787 }; 788 789 ZmDoublePaneController.prototype._draftSaved = 790 function(msg, resp) { 791 if (resp) { 792 if (!msg) { 793 msg = new ZmMailMsg(); 794 } 795 msg._loadFromDom(resp); 796 } 797 appCtxt.cacheSet(msg.id, msg); 798 this._redrawDraftItemRows(msg); 799 var displayedMsg = this._doublePaneView.getMsg(); 800 if (displayedMsg && displayedMsg.id == msg.id) { 801 this._doublePaneView.reset(); 802 this._doublePaneView.setItem(msg, null, true); 803 } 804 }; 805 806 ZmDoublePaneController.prototype._redrawDraftItemRows = 807 function(msg) { 808 this._listView[this._currentViewId].redrawItem(msg); 809 this._listView[this._currentViewId].setSelection(msg, true); 810 }; 811 812 ZmDoublePaneController.prototype.selectFirstItem = 813 function() { 814 this._doublePaneView._selectFirstItem(); 815 }; 816 817 ZmDoublePaneController.prototype._getDefaultFocusItem = 818 function() { 819 return this.getListView(); 820 }; 821 822 /** 823 * Returns the item that should be selected after a move/delete. Finds 824 * the first non-selected item after the first selected item. 825 * 826 * @param {hash} omit hash of item IDs to exclude from being next selected item 827 */ 828 ZmDoublePaneController.prototype._getNextItemToSelect = 829 function(omit) { 830 831 omit = omit || {}; 832 var listView = this._listView[this._currentViewId]; 833 var numSelected = listView.getSelectionCount(); 834 if (numSelected) { 835 var selection = listView.getSelection(); 836 var selIds = {}; 837 for (var i = 0; i < selection.length; i++) { 838 selIds[selection[i].id] = true; 839 } 840 var setting = appCtxt.get(ZmSetting.SELECT_AFTER_DELETE); 841 var goingUp = (setting == ZmSetting.DELETE_SELECT_PREV || (setting == ZmSetting.DELETE_SELECT_ADAPT && 842 (this.lastListAction == DwtKeyMap.SELECT_PREV || this.lastListAction == ZmKeyMap.PREV_UNREAD))); 843 if (goingUp && (numSelected == 1)) { 844 var idx = listView._getRowIndex(selection[selection.length - 1]); 845 var childNodes = listView._parentEl.childNodes; 846 for (var i = idx - 1; i >= 0; i--) { 847 var item = listView.getItemFromElement(childNodes[i]); 848 if (item && !selIds[item.id] && !omit[item.id] && !(item.cid && (selIds[item.cid] || omit[item.cid]))) { 849 return item; 850 } 851 } 852 return ZmMailListView.FIRST_ITEM; 853 } else { 854 var idx = listView._getRowIndex(selection[0]); 855 var childNodes = listView._parentEl.childNodes; 856 for (var i = idx + 1; i < childNodes.length; i++) { 857 var item = listView.getItemFromElement(childNodes[i]); 858 if (item && !selIds[item.id] && !omit[item.id] && !(item.cid && (selIds[item.cid] || omit[item.cid]))) { 859 return item; 860 } 861 } 862 return ZmMailListView.LAST_ITEM; 863 } 864 } 865 return ZmMailListView.FIRST_ITEM; 866 }; 867 868 ZmDoublePaneController.prototype._setItemCountText = 869 function(text) { 870 871 text = text || this._getItemCountText(); 872 873 var rpr = (this._getReadingPanePref() == ZmSetting.RP_RIGHT); 874 if (this._itemCountText[ZmSetting.RP_RIGHT]) { 875 this._itemCountText[ZmSetting.RP_RIGHT].setText(rpr ? text : ""); 876 } 877 if (this._itemCountText[ZmSetting.RP_BOTTOM]) { 878 this._itemCountText[ZmSetting.RP_BOTTOM].setText(rpr ? "" : text); 879 } 880 }; 881 882 ZmDoublePaneController.prototype._postShowCallback = 883 function() { 884 885 ZmMailListController.prototype._postShowCallback.apply(this, arguments); 886 var dpv = this._doublePaneView; 887 if (dpv && dpv.isStale && dpv._staleHandler) { 888 dpv._staleHandler(); 889 } 890 }; 891