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 an empty message controller. 26 * @constructor 27 * @class 28 * This class controls the display and management of a single message in the content area. Since it 29 * needs to handle pretty much the same operations as a list, it extends ZmMailListController. 30 * 31 * @author Parag Shah 32 * @author Conrad Damon 33 * 34 * @param {DwtControl} container the containing shell 35 * @param {constant} type type of controller 36 * @param {ZmApp} mailApp the containing application 37 * @param {string} sessionId the session id 38 * 39 * @extends ZmMailListController 40 */ 41 ZmMsgController = function(container, mailApp, type, sessionId) { 42 43 if (arguments.length == 0) { return; } 44 ZmMailListController.apply(this, arguments); 45 this._elementsToHide = ZmAppViewMgr.LEFT_NAV; 46 }; 47 48 ZmMsgController.prototype = new ZmMailListController; 49 ZmMsgController.prototype.constructor = ZmMsgController; 50 51 ZmMsgController.MODE_TO_CONTROLLER = {}; 52 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_TRAD] = "GetTradController"; 53 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_CONV] = "GetConvController"; 54 ZmMsgController.MODE_TO_CONTROLLER[ZmId.VIEW_CONVLIST] = "GetConvListController"; 55 56 ZmMsgController.DEFAULT_TAB_TEXT = ZmMsg.message; 57 58 ZmMsgController.viewToTab = {}; 59 60 ZmMsgController.prototype.isZmMsgController = true; 61 ZmMsgController.prototype.toString = function() { return "ZmMsgController"; }; 62 63 // Public methods 64 65 ZmMsgController.getDefaultViewType = 66 function() { 67 return ZmId.VIEW_MSG; 68 }; 69 ZmMsgController.prototype.getDefaultViewType = ZmMsgController.getDefaultViewType; 70 71 /** 72 * Displays a message in the single-pane view. 73 * 74 * @param {ZmMailMsg} msg the message to display 75 * @param {ZmListController} parentController the controller that called this method 76 * @param {AjxCallback} callback the client callback 77 * @param {Boolean} markRead if <code>true</code>, mark msg read 78 * @param {Boolean} hidePagination if <code>true</code>, hide the pagination buttons 79 */ 80 ZmMsgController.prototype.show = 81 function(msg, parentController, callback, markRead, hidePagination, forceLoad, noTruncate) { 82 this.setMsg(msg); 83 this._parentController = parentController; 84 //if(msg.list) { 85 this.setList(msg.list); 86 //} 87 if (!msg._loaded || forceLoad) { 88 var respCallback = new AjxCallback(this, this._handleResponseShow, [callback, hidePagination]); 89 if (msg._loadPending) { 90 // override any local callback if we're being launched by double-pane view, 91 // so that multiple GetMsgRequest's aren't made 92 msg._loadCallback = respCallback; 93 } else { 94 markRead = markRead || (appCtxt.get(ZmSetting.MARK_MSG_READ) == ZmSetting.MARK_READ_NOW); 95 msg.load({callback:respCallback, markRead:markRead, forceLoad:forceLoad, noTruncate:noTruncate}); 96 } 97 } else { 98 // May have been explicitly marked as unread 99 var marked = false; 100 if (!msg.isReadOnly() && msg.isUnread && (appCtxt.get(ZmSetting.MARK_MSG_READ) != ZmSetting.MARK_READ_NONE)) { 101 if (msg.list) { 102 // Need to mark it on the server 103 marked = true; 104 var markCallback = this._handleResponseShow.bind(this, callback, hidePagination); 105 msg.list.markRead({items: msg, value: true, callback: markCallback, noBusyOverlay: true}); 106 } else { 107 msg.markRead(); 108 } 109 } 110 if (!marked) { 111 this._handleResponseShow(callback, hidePagination); 112 } 113 } 114 }; 115 116 ZmMsgController.prototype._handleResponseShow = 117 function(callback, hidePagination, result) { 118 this._showMsg(); 119 this._showNavToolBarButtons(this._currentViewId, !hidePagination); 120 if (callback && callback.run) { 121 callback.run(this, this._view[this._currentViewId]); 122 } 123 }; 124 125 126 /** 127 * can't repro bug 77538 - but since the exception happens in ZmListController.prototype._setupContinuation if lastItem is not set, let's set it here to be on the safe side. 128 */ 129 ZmMsgController.prototype._setupContinuation = 130 function() { 131 this._continuation.lastItem = true; //just a dummy value. I could use this._msg but afraid that in the case of the bug (77538) - that I can't repro - this._msg might be empty. 132 this._continuation.totalItems = 1; 133 ZmListController.prototype._setupContinuation.apply(this, arguments); 134 }; 135 136 137 /** 138 * Called by ZmNewWindow.unload to remove tag list listener (which resides in 139 * the parent window). Otherwise, after the child window is closed, the parent 140 * window is still referencing the child window's msg controller, which has 141 * been unloaded!! 142 * 143 * @private 144 */ 145 ZmMsgController.prototype.dispose = 146 function() { 147 this._tagList.removeChangeListener(this._tagChangeListener); 148 }; 149 150 ZmMsgController.prototype._showMsg = 151 function() { 152 this._showMailItem(); 153 }; 154 155 ZmMsgController.prototype._getTabParams = 156 function(tabId, tabCallback) { 157 return { 158 id: tabId, 159 textPrecedence: 85, 160 image: "CloseGray", 161 hoverImage: "Close", 162 style: DwtLabel.IMAGE_RIGHT, 163 tooltip: ZmMsgController.DEFAULT_TAB_TEXT, 164 tabCallback: tabCallback 165 }; 166 }; 167 168 ZmMsgController.prototype.getKeyMapName = 169 function() { 170 return ZmKeyMap.MAP_MESSAGE; 171 }; 172 173 ZmMsgController.prototype.handleKeyAction = 174 function(actionCode) { 175 DBG.println(AjxDebug.DBG3, "ZmMsgController.handleKeyAction"); 176 177 switch (actionCode) { 178 case ZmKeyMap.CANCEL: 179 this._backListener(); 180 break; 181 182 case ZmKeyMap.NEXT_PAGE: 183 this._goToMsg(this._currentViewId, true); 184 break; 185 186 case ZmKeyMap.PREV_PAGE: 187 this._goToMsg(this._currentViewId, false); 188 break; 189 190 // switching view not supported here 191 case ZmKeyMap.VIEW_BY_CONV: 192 case ZmKeyMap.VIEW_BY_MSG: 193 break; 194 195 default: 196 if (ZmMsgController.ALLOWED_SHORTCUT[actionCode]) { 197 return ZmMailListController.prototype.handleKeyAction.call(this, actionCode); 198 } 199 } 200 return true; 201 }; 202 203 ZmMsgController.prototype.mapSupported = 204 function(map) { 205 return false; 206 }; 207 208 // Private methods (mostly overrides of ZmListController protected methods) 209 210 ZmMsgController.prototype._getToolBarOps = 211 function() { 212 var list = [ZmOperation.CLOSE, ZmOperation.SEP]; 213 list = list.concat(ZmMailListController.prototype._getToolBarOps.call(this)); 214 return list; 215 }; 216 217 ZmMsgController.prototype._getRightSideToolBarOps = 218 function() { 219 if (appCtxt.isChildWindow || !appCtxt.get(ZmSetting.DETACH_MAILVIEW_ENABLED) || appCtxt.isExternalAccount()) { 220 return []; 221 } 222 return [ZmOperation.DETACH]; 223 }; 224 225 226 ZmMsgController.prototype._showDetachInSecondary = 227 function() { 228 return false; 229 }; 230 231 ZmMsgController.prototype._initializeToolBar = 232 function(view) { 233 var className = appCtxt.isChildWindow ? "ZmMsgViewToolBar_cw" : null; 234 235 ZmMailListController.prototype._initializeToolBar.call(this, view, className); 236 }; 237 238 ZmMsgController.prototype._navBarListener = 239 function(ev) { 240 var op = ev.item.getData(ZmOperation.KEY_ID); 241 if (op == ZmOperation.PAGE_BACK || op == ZmOperation.PAGE_FORWARD) { 242 this._goToMsg(this._currentViewId, (op == ZmOperation.PAGE_FORWARD)); 243 } 244 }; 245 246 // message view has no view menu button 247 ZmMsgController.prototype._setupViewMenu = function(view, firstTime) {}; 248 249 ZmMsgController.prototype._getActionMenuOps = 250 function() { 251 return null; 252 }; 253 254 ZmMsgController.prototype._initializeView = 255 function(view) { 256 if (!this._view[view]) { 257 var params = { 258 parent: this._container, 259 id: ZmId.getViewId(ZmId.VIEW_MSG, null, view), 260 posStyle: Dwt.ABSOLUTE_STYLE, 261 mode: ZmId.VIEW_MSG, 262 controller: this 263 }; 264 this._view[view] = new ZmMailMsgView(params); 265 this._view[view].addInviteReplyListener(this._inviteReplyListener); 266 this._view[view].addShareListener(this._shareListener); 267 this._view[view].addSubscribeListener(this._subscribeListener); 268 } 269 }; 270 271 ZmMsgController.prototype._initializeTabGroup = 272 function(view) { 273 if (this._tabGroups[view]) { return; } 274 275 ZmMailListController.prototype._initializeTabGroup.apply(this, arguments); 276 277 this._tabGroups[view].removeMember(this._view[view]); 278 }; 279 280 ZmMsgController.prototype._getSearchFolderId = 281 function() { 282 return this._msg.folderId ? this._msg.folderId : (this._msg.list && this._msg.list.search) ? 283 this._msg.list.search.folderId : null; 284 }; 285 286 ZmMsgController.prototype._getTagMenuMsg = 287 function() { 288 return ZmMsg.tagMessage; 289 }; 290 291 ZmMsgController.prototype._getMoveDialogTitle = 292 function() { 293 return ZmMsg.moveMessage; 294 }; 295 296 ZmMsgController.prototype._setViewContents = 297 function(view) { 298 this._view[view].set(this._msg); 299 }; 300 301 ZmMsgController.prototype._resetNavToolBarButtons = 302 function(view) { 303 view = view || this.getCurrentViewId(); 304 if (!this._navToolBar[view]) { return; } 305 // NOTE: we purposely do not call base class here! 306 if (!appCtxt.isChildWindow) { 307 var list = this._msg.list && this._msg.list.getVector(); 308 309 this._navToolBar[view].enable(ZmOperation.PAGE_BACK, (list && (list.get(0) != this._msg))); 310 311 var bEnableForw = list && (this._msg.list.hasMore() || (list.getLast() != this._msg)); 312 this._navToolBar[view].enable(ZmOperation.PAGE_FORWARD, bEnableForw); 313 314 this._navToolBar[view].setToolTip(ZmOperation.PAGE_BACK, ZmMsg.previousMessage); 315 this._navToolBar[view].setToolTip(ZmOperation.PAGE_FORWARD, ZmMsg.nextMessage); 316 } 317 }; 318 319 ZmMsgController.prototype._showNavToolBarButtons = 320 function(view, show) { 321 var toolbar = this._navToolBar[view]; 322 if (!toolbar) { return; } 323 if (!appCtxt.isChildWindow) { 324 toolbar.getButton(ZmOperation.PAGE_BACK).setVisible(show); 325 toolbar.getButton(ZmOperation.PAGE_FORWARD).setVisible(show); 326 } 327 }; 328 329 ZmMsgController.prototype._goToMsg = 330 function(view, next) { 331 var controller = this._parentController; 332 if (controller && controller.pageItemSilently) { 333 controller.pageItemSilently(this._msg, next, this); 334 } 335 }; 336 337 ZmMsgController.prototype._selectNextItemInParentListView = 338 function() { 339 var controller = this._parentController; 340 if (controller && controller._getNextItemToSelect) { 341 controller._view[controller._currentViewId]._itemToSelect = controller._getNextItemToSelect(); 342 } 343 }; 344 345 ZmMsgController.prototype._doDelete = 346 function() { 347 this._selectNextItemInParentListView(); 348 ZmMailListController.prototype._doDelete.apply(this, arguments); 349 }; 350 351 ZmMsgController.prototype._doMove = 352 function() { 353 this._selectNextItemInParentListView(); 354 ZmMailListController.prototype._doMove.apply(this, arguments); 355 }; 356 357 ZmMsgController.prototype._doSpam = 358 function() { 359 this._selectNextItemInParentListView(); 360 ZmMailListController.prototype._doSpam.apply(this, arguments); 361 }; 362 363 ZmMsgController.prototype._menuPopdownActionListener = 364 function(ev) { 365 // dont do anything since msg view has no action menus 366 }; 367 368 // Miscellaneous 369 370 ZmMsgController.prototype.getMsg = 371 function(params) { 372 return this._msg; 373 }; 374 375 ZmMsgController.prototype.getItems = 376 function() { 377 return [this._msg]; 378 }; 379 380 ZmMsgController.prototype._getLoadedMsg = 381 function(params, callback) { 382 callback.run(this._msg); 383 }; 384 385 ZmMsgController.prototype._getSelectedMsg = 386 function() { 387 return this._msg; 388 }; 389 390 ZmMsgController.prototype.setMsg = function (msg) { 391 this._msg = msg; 392 msg.refCount++ 393 }; 394 395 ZmMsgController.prototype.getItemView = function() { 396 return this._view[this._currentViewId]; 397 }; 398 399 // No-op replenishment 400 ZmMsgController.prototype._checkReplenish = 401 function(params) { 402 // XXX: remove this when replenishment is fixed for msg controller! 403 DBG.println(AjxDebug.DBG1, "SORRY. NO REPLENISHMENT FOR YOU."); 404 }; 405 406 ZmMsgController.prototype._checkItemCount = 407 function() { 408 if (!appCtxt.isChildWindow) { 409 this._backListener(); 410 } 411 }; 412 413 ZmMsgController.prototype._getDefaultFocusItem = 414 function() { 415 return this._toolbar[this._currentViewId]; 416 }; 417 418 ZmMsgController.prototype._backListener = 419 function(ev) { 420 // bug fix #30835 - prism triggers this listener twice for some reason :/ 421 if (appCtxt.isOffline && (this._currentViewId != appCtxt.getCurrentViewId())) { 422 return; 423 } 424 var isChildWindow = appCtxt.isChildWindow; 425 if (!this._app.popView() && !isChildWindow) { 426 this._app.mailSearch(); 427 } 428 }; 429 430 ZmMsgController.prototype.isTransient = 431 function(oldView, newView) { 432 return (appCtxt.getViewTypeFromId(newView) != ZmId.VIEW_COMPOSE); 433 }; 434 435 ZmMsgController.prototype._tabCallback = 436 function(oldView, newView) { 437 return (appCtxt.getViewTypeFromId(oldView) == ZmId.VIEW_MSG); 438 }; 439 440 ZmMsgController.prototype._printListener = 441 function(ev) { 442 var ids = []; 443 var item = this._msg; 444 var id; 445 var showImages; 446 // always extract out the msg ids from the conv 447 if (item.toString() == "ZmConv") { 448 // get msg ID in case of virtual conv. 449 // item.msgIds.length is inconsistent, so checking if conv id is negative. 450 if (appCtxt.isOffline && item.id.split(":")[1]<0) { 451 id = item.msgIds[0]; 452 } else { 453 id = "C:" + item.id; 454 } 455 var msgList = item.getMsgList(); 456 for(var j=0; j<msgList.length; j++) { 457 if(msgList[j].showImages) { 458 showImages = true; 459 break; 460 } 461 } 462 } else { 463 id = item.id; 464 // Fix for bug: 84261, bug: 85363. partId is present if original message is present as an attachment. 465 var part = item.partId; 466 if (part) { 467 id += "&part=" + part; 468 } 469 470 if (item.showImages) { 471 showImages = true; 472 } 473 } 474 var url = "/h/printmessage?id=" + id + "&tz=" + AjxTimezone.getServerId(AjxTimezone.DEFAULT); 475 if (appCtxt.get(ZmSetting.DISPLAY_EXTERNAL_IMAGES) || showImages) { 476 url += "&xim=1"; 477 } 478 if (appCtxt.isOffline) { 479 var acctName = item.getAccount().name; 480 url+="&acct=" + acctName ; 481 } 482 window.open(appContextPath+url, "_blank"); 483 }; 484 485 ZmMsgController.prototype._subscribeResponseHandler = 486 function(statusMsg, ev) { 487 ZmMailListController.prototype._subscribeResponseHandler.call(this, statusMsg, ev); 488 //Close View 489 appCtxt.getAppViewMgr().popView(); 490 }; 491 492 ZmMsgController.prototype._acceptShareHandler = 493 function(ev) { 494 ZmMailListController.prototype._acceptShareHandler.call(this, ev); 495 //Close View 496 appCtxt.getAppViewMgr().popView(); 497 }; 498 499 ZmMsgController.prototype._setStatics = function() { 500 501 if (!ZmMsgController.ALLOWED_SHORTCUT) { 502 ZmMsgController.ALLOWED_SHORTCUT = AjxUtil.arrayAsHash([ 503 ZmKeyMap.FORWARD, 504 ZmKeyMap.MOVE, 505 ZmKeyMap.PRINT, 506 ZmKeyMap.TAG, 507 ZmKeyMap.UNTAG, 508 ZmKeyMap.REPLY, 509 ZmKeyMap.REPLY_ALL, 510 ZmKeyMap.SPAM, 511 ZmKeyMap.MARK_READ, 512 ZmKeyMap.MARK_UNREAD, 513 ZmKeyMap.FLAG 514 ]); 515 } 516 517 ZmMailListController.prototype._setStatics(); 518 }; 519 520 ZmMsgController.prototype._postRemoveCallback = function() { 521 this._msg.refCount--; 522 }; 523