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 ZmMailMsgView = function(params) { 25 26 if (arguments.length == 0) { return; } 27 28 params.className = params.className || "ZmMailMsgView"; 29 ZmMailItemView.call(this, params); 30 31 this._mode = params.mode; 32 this._controller = params.controller; 33 this._viewId = this._getViewId(params.sessionId); 34 35 this._displayImagesId = ZmId.getViewId(this._viewId, ZmId.MV_DISPLAY_IMAGES, this._mode); 36 this._msgTruncatedId = ZmId.getViewId(this._viewId, ZmId.MV_MSG_TRUNC, this._mode); 37 this._infoBarId = ZmId.getViewId(this._viewId, ZmId.MV_INFO_BAR, this._mode); 38 this._tagRowId = ZmId.getViewId(this._viewId, ZmId.MV_TAG_ROW, this._mode); 39 this._tagCellId = ZmId.getViewId(this._viewId, ZmId.MV_TAG_CELL, this._mode); 40 this._attLinksId = ZmId.getViewId(this._viewId, ZmId.MV_ATT_LINKS, this._mode); 41 42 this._scrollWithIframe = params.scrollWithIframe; 43 this._limitAttachments = this._scrollWithIframe ? 3 : 0; //making it local 44 this._attcMaxSize = this._limitAttachments * 16 + 8; 45 this.setScrollStyle(this._scrollWithIframe ? DwtControl.CLIP : DwtControl.SCROLL); 46 47 ZmTagsHelper.setupListeners(this); //setup tags related listeners. 48 49 this._setMouseEventHdlrs(); // needed by object manager 50 this._objectManager = true; 51 52 this._changeListener = this._msgChangeListener.bind(this); 53 this.addListener(DwtEvent.ONSELECTSTART, this._selectStartListener.bind(this)); 54 this.addListener(DwtEvent.CONTROL, this._controlEventListener.bind(this)); 55 56 // bug fix #25724 - disable right click selection for offline 57 if (!appCtxt.isOffline) { 58 this._setAllowSelection(); 59 } 60 61 this.noTab = true; 62 this._attachmentLinkIdToFileNameMap = null; 63 this._bubbleParams = {}; 64 65 if (this._controller && this._controller._checkKeepReading) { 66 Dwt.setHandler(this.getHtmlElement(), DwtEvent.ONSCROLL, ZmDoublePaneController.handleScroll); 67 }; 68 69 this._tabGroupMember = new DwtTabGroup("ZmMailMsgView"); 70 this._headerTabGroup = new DwtTabGroup("ZmMailMsgView (header)"); 71 this._attachmentTabGroup = new DwtTabGroup("ZmMailMsgView (attachments)"); 72 this._bodyTabGroup = new DwtTabGroup("ZmMailMsgView (body)"); 73 this._footerTabGroup = new DwtTabGroup("ZmMailMsgView (footer)"); 74 75 this._tabGroupMember.addMember([ 76 this._headerTabGroup, this._bodyTabGroup, this._footerTabGroup 77 ]); 78 79 if (this._mode === ZmId.VIEW_TRAD) { 80 this.setAttribute('role', 'region'); 81 } 82 }; 83 84 ZmMailMsgView.prototype = new ZmMailItemView; 85 ZmMailMsgView.prototype.constructor = ZmMailMsgView; 86 87 ZmMailMsgView.prototype.isZmMailMsgView = true; 88 ZmMailMsgView.prototype.toString = function() { return "ZmMailMsgView"; }; 89 90 91 // displays any additional headers in messageView 92 //pass ZmMailMsgView.displayAdditionalHdrsInMsgView[<actualHeaderName>] = <DisplayName> 93 //pass ZmMailMsgView.displayAdditionalHdrsInMsgView["X-Mailer"] = "Sent Using:" 94 ZmMailMsgView.displayAdditionalHdrsInMsgView = {}; 95 96 97 // Consts 98 99 ZmMailMsgView.SCROLL_WITH_IFRAME = true; 100 ZmMailMsgView.LIMIT_ATTACHMENTS = ZmMailMsgView.SCROLL_WITH_IFRAME ? 3 : 0; 101 ZmMailMsgView.ATTC_COLUMNS = 2; 102 ZmMailMsgView.ATTC_MAX_SIZE = ZmMailMsgView.LIMIT_ATTACHMENTS * 16 + 8; 103 ZmMailMsgView.QUOTE_DEPTH_MOD = 3; 104 ZmMailMsgView.MAX_SIG_LINES = 8; 105 ZmMailMsgView.SIG_LINE = /^(- ?-+)|(__+)\r?$/; 106 ZmMailMsgView._inited = false; 107 ZmMailMsgView.SHARE_EVENT = "share"; 108 ZmMailMsgView.SUBSCRIBE_EVENT = "subscribe"; 109 ZmMailMsgView.IMG_FIX_RE = new RegExp("(<img\\s+.*dfsrc\\s*=\\s*)[\"']http[^'\"]+part=([\\d\\.]+)[\"']([^>]*>)", "gi"); 110 ZmMailMsgView.FILENAME_INV_CHARS_RE = /[\.\/?*:;{}'\\]/g; // Chars we do not allow in a filename 111 ZmMailMsgView.SETHEIGHT_MAX_TRIES = 3; 112 113 ZmMailMsgView._URL_RE = /^((https?|ftps?):\x2f\x2f.+)$/; 114 ZmMailMsgView._MAILTO_RE = /^mailto:[\x27\x22]?([^@?&\x22\x27]+@[^@?&]+\.[^@?&\x22\x27]+)[\x27\x22]?/; 115 116 ZmMailMsgView.MAX_ADDRESSES_IN_FIELD = 10; 117 118 // tags that are trusted in HTML content that is not displayed in an iframe 119 ZmMailMsgView.TRUSTED_TAGS = ["#text", "a", "abbr", "acronym", "address", "article", "b", "basefont", "bdo", "big", 120 "blockquote", "body", "br", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del", "dfn", "dir", 121 "div", "dl", "dt", "em", "font", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "i", "img", 122 "ins", "kbd", "li", "mark", "menu", "meter", "nav", "ol", "p", "pre", "q", "s", "samp", "section", "small", 123 "span", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "time", "tr", "tt", 124 "u", "ul", "var", "wbr"]; 125 126 // attributes that we don't want to appear in HTML displayed in a div 127 ZmMailMsgView.UNTRUSTED_ATTRS = ["id", "class", "name", "profile"]; 128 129 // Public methods 130 131 ZmMailMsgView.prototype.getController = 132 function() { 133 return this._controller; 134 }; 135 136 ZmMailMsgView.prototype.reset = 137 function() { 138 // Bug 23692: cancel any pending actions 139 if (this._resizeAction) { 140 AjxTimedAction.cancelAction(this._resizeAction); 141 this._resizeAction = null; 142 } 143 if (this._objectsAction) { 144 AjxTimedAction.cancelAction(this._objectsAction); 145 this._objectsAction = null; 146 } 147 this._msg = this._item = null; 148 this._htmlBody = null; 149 this._containerEl = null; 150 151 // TODO: reuse all these controls that are being disposed here..... 152 if (this._ifw) { 153 this._ifw.dispose(); 154 this._ifw = null; 155 } 156 if (this._inviteMsgView) { 157 this._inviteMsgView.reset(true); 158 } 159 160 var el = this.getHtmlElement(); 161 if (el) { 162 el.innerHTML = ""; 163 } 164 if (this._objectManager && this._objectManager.reset) { 165 this._objectManager.reset(); 166 } 167 this.setScrollWithIframe(this._scrollWithIframe); 168 }; 169 170 ZmMailMsgView.prototype.dispose = 171 function() { 172 ZmTagsHelper.disposeListeners(this); 173 ZmMailItemView.prototype.dispose.apply(this, arguments); 174 }; 175 176 ZmMailMsgView.prototype.preventSelection = 177 function() { 178 return false; 179 }; 180 181 ZmMailMsgView.prototype.set = 182 function(msg, force, dayViewCallback) { 183 184 if (!force && this._msg && msg && !msg.force && (this._msg == msg)) { return; } 185 186 var oldMsg = this._msg; 187 this.reset(); 188 var contentDiv = this._getContainer(); 189 this._msg = this._item = msg; 190 191 if (!msg) { 192 if (this._inviteMsgView) { 193 this._inviteMsgView.resize(true); //make sure the msg preview pane takes the entire area, in case we were viewing an invite. (since then it was resized to allow for day view) - bug 53098 194 } 195 contentDiv.innerHTML = (this._controller.getList().size()) ? AjxTemplate.expand("mail.Message#viewMessage") : ""; 196 return; 197 } 198 199 msg.force = false; 200 var respCallback = this._handleResponseSet.bind(this, msg, oldMsg, dayViewCallback); 201 this._renderMessage(msg, contentDiv, respCallback); 202 }; 203 204 ZmMailMsgView.prototype._getContainer = 205 function() { 206 return this.getHtmlElement(); 207 }; 208 209 ZmMailMsgView.prototype.__hasMountpoint = 210 function(share) { 211 var tree = appCtxt.getFolderTree(); 212 return tree 213 ? this.__hasMountpoint2(tree.root, share.grantor.id, share.link.id) 214 : false; 215 }; 216 217 ZmMailMsgView.prototype.__hasMountpoint2 = 218 function(organizer, zid, rid) { 219 if (organizer.zid == zid && organizer.rid == rid) 220 return true; 221 222 if (organizer.children) { 223 var children = organizer.children.getArray(); 224 for (var i = 0; i < children.length; i++) { 225 var found = this.__hasMountpoint2(children[i], zid, rid); 226 if (found) { 227 return true; 228 } 229 } 230 } 231 return false; 232 }; 233 234 ZmMailMsgView.prototype.highlightObjects = 235 function(origText) { 236 if (origText != null) { 237 // we get here only for text messages; it's a lot 238 // faster to call findObjects on the whole text rather 239 // than parsing the DOM. 240 DBG.timePt("START - highlight objects on-demand, text msg."); 241 this._lazyCreateObjectManager(); 242 var html = this._objectManager.findObjects(origText, true, null, true); 243 html = html.replace(/^ /mg, " ") 244 .replace(/\t/g, "<pre style='display:inline;'>\t</pre>") 245 .replace(/\n/g, "<br>"); 246 var container = this.getContentContainer(); 247 container.innerHTML = html; 248 DBG.timePt("END - highlight objects on-demand, text msg."); 249 } else { 250 this._processHtmlDoc(); 251 } 252 }; 253 254 ZmMailMsgView.prototype.resetMsg = 255 function(newMsg) { 256 // Remove listener for current msg if it exists 257 if (this._msg) { 258 this._msg.removeChangeListener(this._changeListener); 259 } 260 }; 261 262 ZmMailMsgView.prototype.getMsg = 263 function() { 264 return this._msg; 265 }; 266 267 ZmMailMsgView.prototype.getItem = ZmMailMsgView.prototype.getMsg; 268 269 // Following two overrides are a hack to allow this view to pretend it's a list view 270 ZmMailMsgView.prototype.getSelection = 271 function() { 272 return [this._msg]; 273 }; 274 275 ZmMailMsgView.prototype.getSelectionCount = 276 function() { 277 return 1; 278 }; 279 280 ZmMailMsgView.prototype.getMinHeight = 281 function() { 282 if (!this._headerHeight) { 283 var headerObj = document.getElementById(this._hdrTableId); 284 this._headerHeight = headerObj ? Dwt.getSize(headerObj).y : 0; 285 } 286 return this._headerHeight; 287 }; 288 289 // returns true if the current message was rendered in HTML 290 ZmMailMsgView.prototype.hasHtmlBody = 291 function() { 292 return this._htmlBody != null; 293 }; 294 295 // returns the IFRAME's document if we are using one, or the window document 296 ZmMailMsgView.prototype.getDocument = 297 function() { 298 return this._usingIframe ? Dwt.getIframeDoc(this.getIframe()) : document; 299 }; 300 301 // returns the IFRAME element if we are using one 302 ZmMailMsgView.prototype.getIframe = 303 function() { 304 305 if (!this._usingIframe) { return null; } 306 307 var iframe = this._iframeId && document.getElementById(this._iframeId); 308 iframe = iframe || (this._ifw && this._ifw.getIframe()); 309 return iframe; 310 }; 311 ZmMailMsgView.prototype.getIframeElement = ZmMailMsgView.prototype.getIframe; 312 313 // Returns a BODY element if we are using an IFRAME, the container DIV if we are not. 314 ZmMailMsgView.prototype.getContentContainer = 315 function() { 316 if (this._usingIframe) { 317 var idoc = this.getDocument(); 318 var body = idoc && idoc.body; 319 return body && body.childNodes.length === 1 ? body.firstChild : body; 320 } 321 else { 322 return this._containerEl; 323 } 324 }; 325 326 ZmMailMsgView.prototype.getContent = 327 function() { 328 var container = this.getContentContainer(); 329 return container ? container.innerHTML : ""; 330 }; 331 332 ZmMailMsgView.prototype.addInviteReplyListener = 333 function(listener) { 334 this.addListener(ZmInviteMsgView.REPLY_INVITE_EVENT, listener); 335 }; 336 337 ZmMailMsgView.prototype.addShareListener = 338 function(listener) { 339 this.addListener(ZmMailMsgView.SHARE_EVENT, listener); 340 }; 341 342 ZmMailMsgView.prototype.addSubscribeListener = 343 function(listener) { 344 this.addListener(ZmMailMsgView.SUBSCRIBE_EVENT, listener); 345 }; 346 347 ZmMailMsgView.prototype.getTabGroupMember = 348 function() { 349 return this._tabGroupMember; 350 }; 351 352 ZmMailMsgView.prototype._getMessageTabMember = 353 function() { 354 if (this._usingIframe) { 355 return this.getIframe().parentNode; 356 } else { 357 return Dwt.byId(this._msgBodyDivId); 358 } 359 }; 360 361 ZmMailMsgView.prototype.setVisible = 362 function(visible, readingPaneOnRight,msg) { 363 DwtComposite.prototype.setVisible.apply(this, arguments); 364 var inviteMsgView = this._inviteMsgView; 365 if (!inviteMsgView) { 366 return; 367 } 368 369 if (visible && this._msg) { 370 if (this._msg != msg) { 371 var dayView = inviteMsgView.getDayView(); 372 if (!dayView) { 373 return; 374 } 375 dayView.setIsRight(readingPaneOnRight); 376 377 inviteMsgView.set(this._msg); 378 inviteMsgView.repositionCounterToolbar(this._hdrTableId); 379 inviteMsgView.showMoreInfo(null, null, readingPaneOnRight); 380 } 381 } 382 else { 383 inviteMsgView.reset(); 384 } 385 }; 386 387 388 // Private / protected methods 389 390 ZmMailMsgView.prototype._getSubscribeToolbar = 391 function(req) { 392 if (this._subscribeToolbar) { 393 if (AjxEnv.isIE) { 394 //reparenting on IE does not work. So recreating in this case. (similar to bug 52412 for the invite toolbar) 395 this._subscribeToolbar.dispose(); 396 this._subscribeToolbar = null; 397 } 398 else { 399 return this._subscribeToolbar; 400 } 401 } 402 403 this._subscribeToolbar = this._getButtonToolbar([ZmOperation.SUBSCRIBE_APPROVE, ZmOperation.SUBSCRIBE_REJECT], 404 ZmId.TB_SUBSCRIBE, 405 this._subscribeToolBarListener.bind(this, req)); 406 407 return this._subscribeToolbar; 408 }; 409 410 411 412 ZmMailMsgView.prototype._getShareToolbar = 413 function() { 414 if (this._shareToolbar) { 415 if (AjxEnv.isIE) { 416 //reparenting on IE does not work. So recreating in this case. (similar to bug 52412 for the invite toolbar) 417 this._shareToolbar.dispose(); 418 this._shareToolbar = null; 419 } 420 else { 421 return this._shareToolbar; 422 } 423 } 424 425 this._shareToolbar = this._getButtonToolbar([ZmOperation.SHARE_ACCEPT, ZmOperation.SHARE_DECLINE], 426 ZmId.TB_SHARE, 427 this._shareToolBarListener.bind(this)); 428 429 return this._shareToolbar; 430 }; 431 432 ZmMailMsgView.prototype._getButtonToolbar = 433 function(buttonIds, toolbarType, listener) { 434 435 var params = { 436 parent: this, 437 buttons: buttonIds, 438 posStyle: DwtControl.STATIC_STYLE, 439 className: "ZmShareToolBar", 440 buttonClassName: "DwtToolbarButton", 441 context: this._mode, 442 toolbarType: toolbarType 443 }; 444 var toolbar = new ZmButtonToolBar(params); 445 446 for (var i = 0; i < buttonIds.length; i++) { 447 var id = buttonIds[i]; 448 449 // HACK: IE doesn't support multiple class names. 450 var b = toolbar.getButton(id); 451 b._hoverClassName = b._className + "-" + DwtCssStyle.HOVER; 452 b._activeClassName = b._className + "-" + DwtCssStyle.ACTIVE; 453 454 toolbar.addSelectionListener(id, listener); 455 } 456 457 return toolbar; 458 }; 459 460 ZmMailMsgView.prototype._handleResponseSet = 461 function(msg, oldMsg, dayViewCallback) { 462 463 var bubblesCreated = false; 464 if (this._inviteMsgView) { 465 // always show F/B view if in stand-alone message view otherwise, check if reading pane is on 466 if (this._inviteMsgView.isActive() && (this._controller.isReadingPaneOn() || (this._controller.isZmMsgController))) { 467 bubblesCreated = true; 468 appCtxt.notifyZimlets("onMsgView", [msg, oldMsg, this]); 469 this._inviteMsgView.showMoreInfo(this._createBubbles.bind(this), dayViewCallback); 470 } 471 else { 472 // resize the message view without F/B view 473 this._inviteMsgView.resize(true); 474 } 475 } 476 477 this._setTags(msg); 478 // Remove listener for current msg if it exists 479 if (oldMsg) { 480 oldMsg.removeChangeListener(this._changeListener); 481 } 482 msg.addChangeListener(this._changeListener); 483 484 if (msg.cloneOf) { 485 msg.cloneOf.addChangeListener(this._changeListener); 486 } 487 if (oldMsg && oldMsg.cloneOf) { 488 oldMsg.cloneOf.removeChangeListener(this._changeListener); 489 } 490 491 // reset scroll view to top most 492 var htmlElement = this.getHtmlElement(); 493 htmlElement.scrollTop = 0; 494 if (htmlElement.scrollTop != 0 && this._usingIframe) { 495 /* situation that happens only on Chrome, without repro steps - bug 55775/57090 */ 496 AjxDebug.println(AjxDebug.SCROLL, "scrollTop not set to 0. scrollTop=" + htmlElement.scrollTop + " offsetHeight=" + htmlElement.offsetHeight + " scrollHeight=" + htmlElement.scrollHeight + " browser=" + navigator.userAgent); 497 AjxDebug.dumpObj(AjxDebug.SCROLL, htmlElement.outerHTML); 498 /* 499 trying this hack for solution - 500 explanation: The scroll bar does not appear if the scrollHeight of the div is bigger than the total height of the iframe and header together (i.e. if htmlElement.scrollHeight >= htmlElement.offsetHeight) 501 If the scrollbar does not appear it's set to, and stays 0 when the scrollbar reappears due to resizing the iframe in _resetIframeHeight (which is later, I think always on timer). 502 So what I do here is set the height of the iframe to very small (since the default is 150px), so the scroll bar disappears. 503 it will reappear when we reset the size in _resetIframeHeight. I hope this will solve the issue. 504 */ 505 var iframe = this.getIframe(); 506 if (iframe) { 507 iframe.style.height = "1px"; 508 AjxDebug.println(AjxDebug.SCROLL, "scrollTop after resetting it with the hack =" + htmlElement.scrollTop); 509 } 510 511 } 512 513 if (!bubblesCreated) { 514 this._createBubbles(); 515 appCtxt.notifyZimlets("onMsgView", [msg, oldMsg, this]); 516 } 517 518 if (!msg.isDraft && msg.readReceiptRequested) { 519 this._controller.sendReadReceipt(msg); 520 } 521 }; 522 523 // This is needed for Gecko only: for some reason, clicking on a local link will 524 // open the full Zimbra chrome in the iframe :-( so we fake a scroll to the link 525 // target here. (bug 7927) 526 ZmMailMsgView.__localLinkClicked = 527 function(msgView, ev) { 528 // note that this function is called in the context of the link ('this' is an A element) 529 var id = this.getAttribute("href"); 530 var el = null; 531 var doc = this.ownerDocument; 532 533 if (id.substr(0, 1) == "#") { 534 id = id.substr(1); 535 el = doc.getElementById(id); 536 if (!el) { 537 try { 538 el = doc.getElementsByName(id)[0]; 539 } catch(ex) {} 540 } 541 if (!el) { 542 id = decodeURIComponent(id); 543 el = doc.getElementById(id); 544 if (!el) { 545 try { 546 el = doc.getElementsByName(id)[0]; 547 } catch(ex) {} 548 } 549 } 550 } 551 552 // attempt #1: doesn't work at all -- we're not scrolling with the IFRAME :-( 553 // if (el) { 554 // var pos = Dwt.getLocation(el); 555 // doc.contentWindow.scrollTo(pos.x, pos.y); 556 // } 557 558 // attempt #2: works pretty well, but the target node will showup at the bottom of the frame 559 // var foo = doc.createElement("a"); 560 // foo.href = "#"; 561 // foo.innerHTML = "foo"; 562 // el.parentNode.insertBefore(foo, el); 563 // foo.focus(); 564 565 // the final monstrosity: scroll the containing DIV 566 // (that is the whole msgView). Note we have to take 567 // into account the headers, "display images", etc -- 568 // so we add iframe.offsetTop/Left. 569 if (el) { 570 var div = msgView.getHtmlElement(); 571 var iframe = msgView.getIframe(); 572 var pos = Dwt.getLocation(el); 573 div.scrollTop = pos.y + iframe.offsetTop - 20; // fuzz factor necessary for unknown reason :-( 574 div.scrollLeft = pos.x + iframe.offsetLeft; 575 } 576 if (ev) { 577 ev.stopPropagation(); 578 ev.preventDefault(); 579 } 580 return false; 581 }; 582 583 ZmMailMsgView.prototype.hasValidHref = 584 function (node) { 585 // Bug 22958: IE can throw when you try and get the href if it doesn't like 586 // the value, so we wrap the test in a try/catch. 587 // hrefs formatted like http://www.name@domain.com can cause this to happen. 588 try { 589 var href = node.href; 590 return ZmMailMsgView._URL_RE.test(href) || ZmMailMsgView._MAILTO_RE.test(href); 591 } catch (e) { 592 return false; 593 } 594 }; 595 596 // Dives recursively into the given DOM node. Creates ObjectHandlers in text 597 // nodes and cleans the mess in element nodes. Discards by default "script", 598 // "link", "object", "style", "applet" and "iframe" (most of them shouldn't 599 // even be here since (1) they belong in the <head> and (2) are discarded on 600 // the server-side, but we check, just in case..). 601 ZmMailMsgView.prototype._processHtmlDoc = 602 function() { 603 604 var parent = this._usingIframe ? this.getDocument() : this._containerEl; 605 if (!parent) { return; } 606 607 DBG.timePt("Starting ZmMailMsgView.prototype._processHtmlDoc"); 608 // bug 8632 609 var images = parent.getElementsByTagName("img"); 610 if (images.length > 0) { 611 var length = images.length; 612 for (var i = 0; i < images.length; i++) { 613 this._checkImgInAttachments(images[i]); 614 } 615 } 616 617 //Find Zimlet Objects lazly 618 this.lazyFindMailMsgObjects(500); 619 620 DBG.timePt("-- END _processHtmlDoc"); 621 }; 622 623 ZmMailMsgView.prototype.lazyFindMailMsgObjects = function(interval) { 624 625 var isSpam = (this._msg && this._msg.folderId == ZmOrganizer.ID_SPAM); 626 if (this._objectManager && !this._disposed && !isSpam) { 627 this._lazyCreateObjectManager(); 628 this._objectsAction = new AjxTimedAction(this, this._findMailMsgObjects); 629 AjxTimedAction.scheduleAction(this._objectsAction, ( interval || 500 )); 630 } 631 }; 632 633 ZmMailMsgView.prototype._findMailMsgObjects = 634 function() { 635 var doc = this.getDocument(); 636 if (doc) { 637 var container = this.getContentContainer(); 638 this._objectManager.processObjectsInNode(doc, container); 639 } 640 }; 641 642 ZmMailMsgView.prototype._checkImgInAttachments = 643 function(img) { 644 if (!this._msg) { return; } 645 646 if (img.getAttribute("zmforced")){ 647 img.className = "InlineImage"; 648 return; 649 } 650 651 var attachments = this._msg.attachments; 652 var csfeMsgFetch = appCtxt.get(ZmSetting.CSFE_MSG_FETCHER_URI); 653 try { 654 var src = img.getAttribute("src") || img.getAttribute("dfsrc"); 655 } 656 catch(e) { 657 AjxDebug.println(AjxDebug.DATA_URI, "_checkImgInAttachments: couldn't access attribute src or dfsrc"); 658 } 659 var cid; 660 if (/^cid:(.*)/.test(src)) { 661 cid = "<" + RegExp.$1 + ">"; 662 } 663 664 for (var i = 0; i < attachments.length; i++) { 665 var att = attachments[i]; 666 667 if (att.foundInMsgBody) { continue; } 668 669 if (cid && att.contentId == cid) { 670 att.foundInMsgBody = true; 671 break; 672 } else if (src && src.indexOf(csfeMsgFetch) == 0) { 673 var mpId = src.substring(src.lastIndexOf("=") + 1); 674 if (mpId == att.part) { 675 att.foundInMsgBody = true; 676 break; 677 } 678 } else if (att.contentLocation && src) { 679 var filename = src.substring(src.lastIndexOf("/") + 1); 680 if (filename == att.fileName) { 681 att.foundInMsgBody = true; 682 break; 683 } 684 } 685 } 686 }; 687 688 ZmMailMsgView.prototype._fixMultipartRelatedImages = 689 function(msg, parent) { 690 // fix <img> tags 691 var images = parent.getElementsByTagName("img"); 692 var hasExternalImages = false; 693 if (this._usingIframe) { 694 var self = this; 695 var onload = function() { 696 //resize iframe onload of image 697 ZmMailMsgView._resetIframeHeight(self); 698 this.onload = null; // *this* is reference to <img> el. 699 }; 700 } 701 for (var i = 0; i < images.length; i++) { 702 var img = images[i]; 703 var external = ZmMailMsgView._isExternalImage(img); // has "dfsrc" attr 704 if (!external) { //Inline image 705 ZmMailMsgView.__unfangInternalImage(msg, img, "src", false); 706 if (onload) { 707 img.onload = onload; 708 } 709 } 710 else { 711 img.src = "/img/zimbra/1x1-trans.png"; 712 img.setAttribute('savedDisplayMode', img.style.display); 713 img.style.display = 'none'; 714 } 715 hasExternalImages = external || hasExternalImages; 716 } 717 // fix all elems with "background" attribute 718 hasExternalImages = this._fixMultipartRelatedImagesRecurse(msg, this._usingIframe ? parent.body : parent) || hasExternalImages; 719 720 // did we get them all? 721 return !hasExternalImages; 722 }; 723 724 ZmMailMsgView.prototype._fixMultipartRelatedImagesRecurse = 725 function(msg, node) { 726 727 var hasExternalImages = false; 728 729 function recurse(node){ 730 var child = node.firstChild; 731 while (child) { 732 if (child.nodeType == AjxUtil.ELEMENT_NODE) { 733 hasExternalImages = ZmMailMsgView.__unfangInternalImage(msg, child, "background", true) || hasExternalImages; 734 recurse(child); 735 } 736 child = child.nextSibling; 737 } 738 } 739 740 if (node.innerHTML.indexOf("dfbackground") != -1) { 741 recurse(node); 742 } 743 else if (node.attributes && node.getAttribute("dfbackground") != -1) { 744 hasExternalImages = ZmMailMsgView.__unfangInternalImage(msg, node, "background", true); 745 } 746 747 if (!hasExternalImages && $(node).find("table[dfbackground], td[dfbackground]").length) { 748 hasExternalImages = true; 749 } 750 751 return hasExternalImages; 752 }; 753 754 /** 755 * Determines if an img element references an external image 756 * @param elem {HTMLelement} 757 * @return {Boolean} true if image is external 758 */ 759 ZmMailMsgView._isExternalImage = 760 function(elem) { 761 if (!elem) { 762 return false; 763 } 764 return Boolean(elem.getAttribute("dfsrc")); 765 } 766 767 /** 768 * Reverses the work of the (server-side) defanger, so that images are displayed. 769 * 770 * @param {ZmMailMsg} msg mail message 771 * @param {Element} elem element to be checked (img) 772 * @param {string} aname attribute name 773 * @param {boolean} external if true, look only for external images 774 * 775 * @return true if the image is external 776 */ 777 ZmMailMsgView.__unfangInternalImage = 778 function(msg, elem, aname, external) { 779 780 var avalue, pnsrc; 781 try { 782 if (external) { 783 avalue = elem.getAttribute("df" + aname); 784 } 785 else { 786 pnsrc = avalue = elem.getAttribute("pn" + aname); 787 avalue = avalue || elem.getAttribute(aname); 788 } 789 } 790 catch(e) { 791 AjxDebug.println(AjxDebug.DATA_URI, "__unfangInternalImage: couldn't access attribute " + aname); 792 } 793 794 if (avalue) { 795 if (avalue.substr(0,4) == "cid:") { 796 var cid = "<" + AjxStringUtil.urlComponentDecode(avalue.substr(4)) + ">"; // Parse percent-escaping per bug #52085 (especially %40 to @) 797 avalue = msg.getContentPartAttachUrl(ZmMailMsg.CONTENT_PART_ID, cid); 798 if (avalue) { 799 elem.setAttribute(aname, avalue); 800 } 801 return false; 802 } else if (avalue.substring(0,4) == "doc:") { 803 avalue = [appCtxt.get(ZmSetting.REST_URL), ZmFolder.SEP, avalue.substring(4)].join(''); 804 if (avalue) { 805 elem.setAttribute(aname, avalue); 806 return false; 807 } 808 } else if (pnsrc) { // check for content-location verison 809 avalue = msg.getContentPartAttachUrl(ZmMailMsg.CONTENT_PART_LOCATION, avalue); 810 if (avalue) { 811 elem.setAttribute(aname, avalue); 812 return false; 813 } 814 } else if (avalue.substring(0,5) == "data:") { 815 return false; 816 } 817 return true; // not recognized as inline img 818 } 819 return false; 820 }; 821 822 ZmMailMsgView.prototype._createDisplayImageClickClosure = 823 function(msg, parent, id) { 824 var self = this; 825 return function(ev) { 826 var target = DwtUiEvent.getTarget(ev), 827 targetId = target ? target.id : null, 828 addrToAdd = ""; 829 var diEl = document.getElementById(id); 830 831 //This is required in case of the address is marked as trusted, the function is called without any target being set 832 var force = (msg && msg.showImages) || appCtxt.get(ZmSetting.DISPLAY_EXTERNAL_IMAGES); 833 834 if (!force) { 835 if (!targetId) { return; } 836 if (targetId.indexOf("domain") != -1) { 837 //clicked on domain 838 addrToAdd = msg.sentByDomain; 839 } 840 else if (targetId.indexOf("email") != -1) { 841 //clicked on email 842 addrToAdd = msg.sentByAddr; 843 } 844 else if (targetId.indexOf("dispImgs") != -1) { 845 //do nothing here - just load the images 846 } 847 else if (targetId.indexOf("close") != -1) { 848 Dwt.setVisible(diEl, false); 849 return; 850 } 851 else { 852 //clicked elsewhere in the info bar - DO NOTHING AND RETURN 853 return; 854 } 855 } 856 //Create a modifyprefs req and add the addr to modify 857 if (addrToAdd) { 858 var trustedList = self.getTrustedSendersList(); 859 trustedList.add(addrToAdd, null, true); 860 var callback = self._addTrustedAddrCallback.bind(self, addrToAdd); 861 var errorCallback = self._addTrustedAddrErrorCallback.bind(self, addrToAdd); 862 self._controller.addTrustedAddr(trustedList.getArray(), callback, errorCallback); 863 } 864 865 var images = parent.getElementsByTagName("img"); 866 var onload = null; 867 if (self._usingIframe) { 868 onload = function() { 869 ZmMailMsgView._resetIframeHeight(self); 870 this.onload = null; // *this* is reference to <img> el. 871 DBG.println(AjxDebug.DBG3, "external image onload called for " + this.src); 872 }; 873 } 874 for (var i = 0; i < images.length; i++) { 875 var dfsrc = images[i].getAttribute("dfsrc"); 876 if (dfsrc && dfsrc.match(/https?:\/\/([-\w\.]+)+(:\d+)?(\/([\w\_\.]*(\?\S+)?)?)?/)) { 877 images[i].onload = onload; 878 // Fix for IE: Over HTTPS, http src urls for images might cause an issue. 879 try { 880 DBG.println(AjxDebug.DBG3, "displaying external images. src = " + images[i].src); 881 images[i].src = ''; //unload it first 882 images[i].src = images[i].getAttribute("dfsrc"); 883 DBG.println(AjxDebug.DBG3, "displaying external images. src is now = " + images[i].src); 884 } catch (ex) { 885 // do nothing 886 } 887 images[i].style.display = images[i].getAttribute('savedDisplayMode'); 888 } 889 } 890 //determine if any tables or table cells have an external background image 891 var tableCells = $(parent).find("table[dfbackground], td[dfbackground]"); 892 for (var i=0; i<tableCells.length; i++) { 893 var dfbackground = $(tableCells[i]).attr("dfbackground"); 894 if (ZmMailMsgView._URL_RE.test(dfbackground)) { 895 $(tableCells[i]).attr("background", dfbackground); 896 } 897 } 898 899 Dwt.setVisible(diEl, false); 900 self._htmlBody = self.getContentContainer().innerHTML; 901 if (msg) { 902 msg.setHtmlContent(self._htmlBody); 903 msg.showImages = true; 904 } 905 //Make sure the link is not followed 906 return false; 907 }; 908 }; 909 910 ZmMailMsgView.prototype._resetIframeHeightOnTimer = 911 function(attempt) { 912 913 if (!this._usingIframe) { return; } 914 915 DBG.println(AjxDebug.DBG1, "_resetIframeHeightOnTimer attempt: " + (attempt != null ? attempt : "null")); 916 // Because sometimes our view contains images that are slow to download, wait a 917 // little while before resizing the iframe. 918 var act = this._resizeAction = new AjxTimedAction(this, ZmMailMsgView._resetIframeHeight, [this, attempt]); 919 AjxTimedAction.scheduleAction(act, 200); 920 }; 921 922 ZmMailMsgView.prototype._makeHighlightObjectsDiv = 923 function(origText) { 924 var self = this; 925 function func() { 926 var div = document.getElementById(self._highlightObjectsId); 927 div.innerHTML = ZmMsg.pleaseWaitHilitingObjects; 928 setTimeout(function() { 929 self.highlightObjects(origText); 930 div.parentNode.removeChild(div); 931 ZmMailMsgView._resetIframeHeight(self); 932 }, 3); 933 return false; 934 } 935 // avoid closure memory leaks 936 (function() { 937 var infoBarDiv = document.getElementById(self._infoBarId); 938 if (infoBarDiv) { 939 self._highlightObjectsId = ZmId.getViewId(self._viewId, ZmId.MV_HIGHLIGHT_OBJ, self._mode); 940 var subs = { 941 id: self._highlightObjectsId, 942 text: ZmMsg.objectsNotDisplayed, 943 link: ZmMsg.hiliteObjects 944 }; 945 var html = AjxTemplate.expand("mail.Message#InformationBar", subs); 946 infoBarDiv.appendChild(Dwt.parseHtmlFragment(html)); 947 948 var div = document.getElementById(subs.id+"_link"); 949 Dwt.setHandler(div, DwtEvent.ONCLICK, func); 950 } 951 })(); 952 }; 953 954 ZmMailMsgView.prototype._stripHtmlComments = 955 function(html) { 956 // bug: 38273 - Remove HTML Comments <!-- --> 957 // But make sure not to remove inside style|script tags. 958 var regex = /<(?:!(?:--[\s\S]*?--\s*)?(>)\s*|(?:script|style|SCRIPT|STYLE)[\s\S]*?<\/(?:script|style|SCRIPT|STYLE)>)/g; 959 html = html.replace(regex,function(m, $1) { 960 return $1 ? '':m; 961 }); 962 return html; 963 }; 964 965 // Returns true (the default) if we should display content in an IFRAME as opposed to a DIV. 966 ZmMailMsgView.prototype._useIframe = 967 function(isTextMsg, html, isTruncated) { 968 return true; 969 }; 970 971 // Displays the given content in an IFRAME or a DIV. 972 ZmMailMsgView.prototype._displayContent = 973 function(params) { 974 975 var html = params.html || ""; 976 977 if (!params.isTextMsg) { 978 //Microsoft silly smilies 979 html = html.replace(/<span style="font-family:Wingdings">J<\/span>/g, "\u263a"); // :) 980 html = html.replace(/<span style="font-family:Wingdings">L<\/span>/g, "\u2639"); // :( 981 } 982 983 // The info bar allows the user to load external images. We show it if: 984 // - msg is HTML 985 // - user pref says not to show images up front, or this is Spam folder 986 // - we're not already showing images 987 // - there are <img> tags OR tags with dfbackground set 988 var isSpam = (this._msg && this._msg.folderId == ZmOrganizer.ID_SPAM); 989 var imagesNotShown = (!this._msg || !this._msg.showImages); 990 this._needToShowInfoBar = (!params.isTextMsg && 991 (!appCtxt.get(ZmSetting.DISPLAY_EXTERNAL_IMAGES) || isSpam) && 992 imagesNotShown && 993 (/<img/i.test(html) || /<[^>]+dfbackground/.test(html))); 994 995 var displayImages; 996 if (this._needToShowInfoBar) { 997 displayImages = this._showInfoBar(this._infoBarId); 998 } 999 1000 var callback; 1001 var msgSize = (html.length / 1024); 1002 var maxHighlightSize = appCtxt.get(ZmSetting.HIGHLIGHT_OBJECTS); 1003 if (params.isTextMsg) { 1004 if (this._objectManager) { 1005 if (msgSize <= maxHighlightSize) { 1006 callback = this.lazyFindMailMsgObjects.bind(this, 500); 1007 } else { 1008 this._makeHighlightObjectsDiv(params.origText); 1009 } 1010 } 1011 if (AjxEnv.isSafari) { 1012 html = "<html><head></head><body>" + html + "</body></html>"; 1013 } 1014 } else { 1015 html = this._stripHtmlComments(html); 1016 if (this._objectManager) { 1017 var images = html.match(/<img[^>]+>/ig); 1018 msgSize = (images) ? msgSize - (images.join().length / 1024) : msgSize; // Excluding images in the message 1019 1020 if (msgSize <= maxHighlightSize) { 1021 callback = this._processHtmlDoc.bind(this); 1022 } else { 1023 this._makeHighlightObjectsDiv(); 1024 } 1025 } 1026 } 1027 1028 var msgTruncated; 1029 this._isMsgTruncated = false; 1030 if (params.isTruncated) { 1031 this._isMsgTruncated = true; 1032 var msgTruncatedDiv = document.getElementById(this._msgTruncatedId); 1033 if (!msgTruncatedDiv) { 1034 var infoBarDiv = document.getElementById(this._infoBarId); 1035 if (infoBarDiv) { 1036 var subs = { 1037 id: this._msgTruncatedId, 1038 text: ZmMsg.messageTooLarge, 1039 link: ZmMsg.viewEntireMessage 1040 }; 1041 var msgTruncatedHtml = AjxTemplate.expand("mail.Message#InformationBar", subs); 1042 msgTruncated = Dwt.parseHtmlFragment(msgTruncatedHtml); 1043 infoBarDiv.appendChild(msgTruncated); 1044 Dwt.setHandler(msgTruncated, DwtEvent.ONCLICK, this._handleMsgTruncated.bind(this)); 1045 } 1046 } 1047 } 1048 1049 this._msgBodyDivId = [this._htmlElId, ZmId.MV_MSG_BODY].join("_"); 1050 this._bodyTabGroup.removeAllMembers(); 1051 1052 this._usingIframe = this._useIframe(params.isTextMsg, html, params.isTruncated); 1053 DBG.println(AjxDebug.DBG1, "Use IFRAME: " + this._usingIframe); 1054 1055 if (this._usingIframe) { 1056 // bug fix #9475 - IE isnt resolving MsgBody class in iframe so set styles explicitly 1057 var inner_styles = AjxEnv.isIE ? ".MsgBody-text, .MsgBody-text * { font: 10pt monospace; }" : ""; 1058 var params1 = { 1059 parent: this, 1060 parentElement: params.container, 1061 index: params.index, 1062 className: this._getBodyClass(), 1063 id: this._msgBodyDivId, 1064 hidden: true, 1065 html: "<div>" + (this._cleanedHtml || html) + "</div>", 1066 styles: inner_styles, 1067 noscroll: !this._scrollWithIframe, 1068 posStyle: DwtControl.STATIC_STYLE, 1069 processHtmlCallback: callback, 1070 useKbMgmt: true, 1071 title: this._getIframeTitle() 1072 }; 1073 1074 // TODO: cache iframes 1075 var ifw = this._ifw = new DwtIframe(params1); 1076 if (ifw.initFailed) { 1077 AjxDebug.println(AjxDebug.MSG_DISPLAY, "Message display: IFRAME was not ready"); 1078 appCtxt.setStatusMsg(ZmMsg.messageDisplayProblem); 1079 return; 1080 } 1081 this._iframeId = ifw.getIframe().id; 1082 1083 var idoc = ifw.getDocument(); 1084 1085 if (AjxEnv.isGeckoBased) { 1086 // patch local links (pass null as object so it gets called in context of link) 1087 var geckoScrollCallback = ZmMailMsgView.__localLinkClicked.bind(null, this); 1088 var links = idoc.getElementsByTagName("a"); 1089 for (var i = links.length; --i >= 0;) { 1090 var link = links[i]; 1091 if (!link.target) { 1092 link.onclick = geckoScrollCallback; // has chances to be a local link 1093 } 1094 } 1095 } 1096 1097 //update root html elment class to reflect user selected font size - so that if we use our relative font size properties in CSS inside (stuff from msgview.css) it would be relative to this and not to the browser default. 1098 Dwt.addClass(idoc.documentElement, "user_font_size_" + appCtxt.get(ZmSetting.FONT_SIZE)); 1099 Dwt.addClass(idoc.documentElement, "user_font_" + appCtxt.get(ZmSetting.FONT_NAME)); 1100 1101 // assign the right class name to the iframe body 1102 idoc.body.className = this._getBodyClass() + (params.isTextMsg ? " MsgBody-text" : " MsgBody-html"); 1103 1104 idoc.body.style.height = "auto"; //see bug 56899 - if the body has height such as 100% or 94%, it causes a problem in FF in calcualting the iframe height. Make sure the height is clear. 1105 1106 ifw.getIframe().onload = this._onloadIframe.bind(this, ifw); 1107 1108 // import the object styles 1109 var head = idoc.getElementsByTagName("head")[0]; 1110 if (!head) { 1111 head = idoc.createElement("head"); 1112 idoc.body.parentNode.insertBefore(head, idoc.body); 1113 } 1114 1115 if (!ZmMailMsgView._CSS) { 1116 // Make a synchronous request for the CSS. Should we do this earlier? 1117 var cssUrl = [appContextPath, "/css/msgview.css?v=", cacheKillerVersion, "&locale=", window.appRequestLocaleId, "&skin=", window.appCurrentSkin].join(""); 1118 if (AjxEnv.supported.localstorage) { 1119 ZmMailMsgView._CSS = localStorage.getItem(cssUrl); 1120 } 1121 if (!ZmMailMsgView._CSS) { 1122 var result = AjxRpc.invoke(null, cssUrl, null, null, true); 1123 ZmMailMsgView._CSS = result && result.text; 1124 } 1125 } 1126 var style = document.createElement('style'); 1127 var rules = document.createTextNode(ZmMailMsgView._CSS); 1128 style.type = 'text/css'; 1129 if (style.styleSheet) { 1130 style.styleSheet.cssText = rules.nodeValue; 1131 } 1132 else { 1133 style.appendChild(rules); 1134 } 1135 head.appendChild(style); 1136 1137 ifw.getIframe().style.visibility = ""; 1138 1139 this._bodyTabGroup.addMember(ifw); 1140 1141 } 1142 else { 1143 var div = this._containerEl = document.createElement("div"); 1144 div.id = this._msgBodyDivId; 1145 div.className = "MsgBody MsgBody-" + (params.isTextMsg ? "text" : "html"); 1146 var parent = this.getHtmlElement(); 1147 if (!parent) { 1148 AjxDebug.println(AjxDebug.MSG_DISPLAY, "Message display: DIV was not ready"); 1149 appCtxt.setStatusMsg(ZmMsg.messageDisplayProblem); 1150 return; 1151 } 1152 if (params.index != null) { 1153 parent.insertBefore(div, parent.childNodes[params.index]) 1154 } 1155 else { 1156 parent.appendChild(div); 1157 } 1158 div.innerHTML = this._cleanedHtml || html; 1159 1160 this._makeFocusable(div); 1161 this._bodyTabGroup.addMember(div); 1162 } 1163 1164 if (!params.isTextMsg) { 1165 this._htmlBody = this.getContentContainer().innerHTML; 1166 1167 // TODO: only call this if top-level is multipart/related? 1168 // setup the click handler for the images 1169 var didAllImages = this._fixMultipartRelatedImages(this._msg, idoc || this._containerEl); 1170 if (didAllImages) { 1171 Dwt.setVisible(displayImages, false); 1172 this._needToShowInfoBar = false; 1173 } else { 1174 this._setupInfoBarClicks(displayImages); 1175 } 1176 } 1177 1178 this._resetIframeHeightOnTimer(); 1179 if (callback) { 1180 callback.run(); 1181 } 1182 }; 1183 ZmMailMsgView.prototype._makeIframeProxy = ZmMailMsgView.prototype._displayContent; 1184 1185 ZmMailMsgView.prototype._showInfoBar = 1186 function(parentEl, html, isTextMsg) { 1187 1188 parentEl = (typeof(parentEl) == "string") ? document.getElementById(parentEl) : parentEl; 1189 if (!parentEl) { return; } 1190 1191 // prevent appending the "Display Images" info bar more than once 1192 var displayImages; 1193 var dispImagesDiv = document.getElementById(this._displayImagesId); 1194 if (!dispImagesDiv) { 1195 if (parentEl) { 1196 var subs = { 1197 id: this._displayImagesId, 1198 text: ZmMsg.externalImages, 1199 link: ZmMsg.displayExternalImages, 1200 alwaysText: ZmMsg.alwaysDisplayExternalImages, 1201 domain: this._msg.sentByDomain, 1202 email: this._msg.sentByAddr, 1203 or: ZmMsg.or 1204 }; 1205 var extImagesHtml = AjxTemplate.expand("mail.Message#ExtImageInformationBar", subs); 1206 displayImages = Dwt.parseHtmlFragment(extImagesHtml); 1207 parentEl.appendChild(displayImages); 1208 } 1209 } 1210 return displayImages; 1211 }; 1212 1213 ZmMailMsgView.prototype._setupInfoBarClicks = 1214 function(displayImages) { 1215 1216 var parent = this._usingIframe ? this.getDocument() : this._containerEl; 1217 var func = this._createDisplayImageClickClosure(this._msg, parent, this._displayImagesId); 1218 if (displayImages) { 1219 Dwt.setHandler(displayImages, DwtEvent.ONCLICK, func); 1220 } 1221 else if (appCtxt.get(ZmSetting.DISPLAY_EXTERNAL_IMAGES) || 1222 (this._msg && this._msg.showImages)) 1223 { 1224 func.call(); 1225 } 1226 }; 1227 1228 ZmMailMsgView.prototype._getBodyClass = 1229 function() { 1230 return "MsgBody"; 1231 }; 1232 1233 ZmMailMsgView.prototype._addTrustedAddrCallback = 1234 function(addr) { 1235 this.getTrustedSendersList().add(addr, null, true); 1236 appCtxt.set(ZmSetting.TRUSTED_ADDR_LIST, this.getTrustedSendersList().getArray()); 1237 var prefApp = appCtxt.getApp(ZmApp.PREFERENCES); 1238 var func = prefApp && prefApp["refresh"]; 1239 if (func && (typeof(func) == "function")) { 1240 func.apply(prefApp, [null, addr]); 1241 } 1242 }; 1243 1244 ZmMailMsgView.prototype._addTrustedAddrErrorCallback = 1245 function(addr) { 1246 this.getTrustedSendersList().remove(addr); 1247 }; 1248 1249 ZmMailMsgView.prototype._isTrustedSender = 1250 function(msg) { 1251 var trustedList = this.getTrustedSendersList(); 1252 if (trustedList.contains(msg.sentByAddr.toLowerCase()) || trustedList.contains(msg.sentByDomain.toLowerCase())){ 1253 return true; 1254 } 1255 return false; 1256 }; 1257 1258 ZmMailMsgView.prototype.getTrustedSendersList = 1259 function() { 1260 return this._controller.getApp().getTrustedSendersList(); 1261 }; 1262 1263 ZmMailMsgView.showMore = 1264 function(elementId, type) { 1265 1266 var showMore = document.getElementById(this._getShowMoreId(elementId, type)); 1267 if (showMore) { 1268 Dwt.setVisible(showMore, false); 1269 } 1270 var more = document.getElementById(this._getMoreId(elementId, type)); 1271 if (more) { 1272 more.style.display = "inline"; 1273 } 1274 }; 1275 1276 ZmMailMsgView._getShowMoreId = 1277 function(elementId, type) { 1278 return elementId + 'showmore_' + type; 1279 }; 1280 1281 ZmMailMsgView._getMoreId = 1282 function(elementId, type) { 1283 return elementId + 'more_addrs_' + type; 1284 }; 1285 1286 /** 1287 * 1288 * formats the array of addresses as HTML with possible "show more" expand link if more than a certain number of addresses are in the field. 1289 * 1290 * @param addrs array of addresses 1291 * @param options 1292 * @param type some type identifier (one per page) 1293 * @param om {ZmObjectManager} 1294 * @param htmlElId - unique view id so it works with multiple views open. 1295 * 1296 * returns object with the html and ShowMore link id 1297 */ 1298 ZmMailMsgView.prototype.getAddressesFieldHtmlHelper = 1299 function(addrs, options, type) { 1300 1301 var addressInfo = {}; 1302 var idx = 0, parts = []; 1303 1304 for (var i = 0; i < addrs.length; i++) { 1305 if (i > 0) { 1306 // no need for separator since we're showing addr bubbles 1307 parts[idx++] = " "; 1308 } 1309 1310 if (i == ZmMailMsgView.MAX_ADDRESSES_IN_FIELD) { 1311 var showMoreId = ZmMailMsgView._getShowMoreId(this._htmlElId, type); 1312 addressInfo.showMoreLinkId = showMoreId + "_link"; 1313 var moreId = ZmMailMsgView._getMoreId(this._htmlElId, type); 1314 parts[idx++] = "<span id='" + showMoreId + "' style='white-space:nowrap'> "; 1315 parts[idx++] = "<a id='" + addressInfo.showMoreLinkId + "' href='' onclick='ZmMailMsgView.showMore(\"" + this._htmlElId + "\", \"" + type + "\"); return false;'>"; 1316 parts[idx++] = ZmMsg.showMore; 1317 parts[idx++] = "</a></span><span style='display:none;' id='" + moreId + "'>"; 1318 } 1319 var email = addrs[i]; 1320 if (email.address) { 1321 parts[idx++] = this._getBubbleHtml(email, options); 1322 } 1323 else { 1324 parts[idx++] = AjxStringUtil.htmlEncode(email.name); 1325 } 1326 } 1327 if (addressInfo.showMoreLinkId) { 1328 parts[idx++] = "</span>"; 1329 } 1330 addressInfo.html = parts.join(""); 1331 return addressInfo; 1332 }; 1333 1334 ZmMailMsgView.prototype._getBubbleHtml = function(addr, options) { 1335 if (!addr) { 1336 return ""; 1337 } 1338 1339 options = options || {}; 1340 1341 addr = addr.isAjxEmailAddress ? addr : new AjxEmailAddress(addr); 1342 1343 var canExpand = addr.isGroup && addr.canExpand && appCtxt.get("EXPAND_DL_ENABLED"), 1344 ctlr = this._controller; 1345 1346 if (canExpand && !this._aclv) { 1347 // create a hidden ZmAutocompleteListView to handle DL expansion 1348 var aclvParams = { 1349 dataClass: appCtxt.getAutocompleter(), 1350 matchValue: ZmAutocomplete.AC_VALUE_FULL, 1351 options: { massDLComplete:true }, 1352 selectionCallback: ctlr._dlAddrSelected.bind(ctlr), 1353 contextId: this.toString() 1354 }; 1355 this._aclv = new ZmAutocompleteListView(aclvParams); 1356 } 1357 1358 // We'll be creating controls (bubbles) later, so we provide the tooltip now and let the control manage 1359 // it instead of the zimlet framework. 1360 var id = ZmId.create({ 1361 app: ZmId.APP_MAIL, 1362 containingView: this._viewId, 1363 field: ZmId.FLD_PARTICIPANT 1364 }); 1365 1366 var bubbleParams = { 1367 parent: appCtxt.getShell(), 1368 parentId: this._htmlElId, 1369 addrObj: addr, 1370 id: id, 1371 canExpand: canExpand, 1372 email: addr.address 1373 }; 1374 ZmAddressInputField.BUBBLE_OBJ_ID[id] = this._htmlElId; // pretend to be a ZmAddressInputField for DL expansion 1375 this._bubbleParams[id] = bubbleParams; 1376 1377 return "<span id='" + id + "'></span>"; 1378 }; 1379 1380 ZmMailMsgView.prototype._clearBubbles = function() { 1381 1382 if (this._bubbleList) { 1383 this._bubbleList.clear(); 1384 } 1385 this._bubbleList = new ZmAddressBubbleList(); 1386 var ctlr = this._controller; 1387 this._bubbleList.addSelectionListener(ctlr._bubbleSelectionListener.bind(ctlr)); 1388 this._bubbleList.addActionListener(ctlr._bubbleActionListener.bind(ctlr)); 1389 this._bubbleParams = {}; 1390 }; 1391 1392 ZmMailMsgView.prototype._createBubbles = function() { 1393 1394 for (var id in this._bubbleParams) { 1395 // make sure SPAN was actually added to DOM (may have been ignored by template, for example) 1396 if (!document.getElementById(id)) { 1397 continue; 1398 } 1399 var bubbleParams = this._bubbleParams[id]; 1400 if (bubbleParams.created) { 1401 continue; 1402 } 1403 bubbleParams.created = true; 1404 var bubble = new ZmAddressBubble(bubbleParams); 1405 bubble.replaceElement(id); 1406 if (this._bubbleList) { 1407 this._bubbleList.add(bubble); 1408 this._headerTabGroup.addMember(bubble); 1409 } 1410 } 1411 }; 1412 1413 /** 1414 * 1415 * formats the array of addresses as HTML with possible "show more" expand link if more than a certain number of addresses are in the field. 1416 * 1417 * @param addrs array of addresses 1418 * @param options 1419 * @param type some type identifier (one per page) 1420 * 1421 * returns object with the html and ShowMore link id 1422 */ 1423 ZmMailMsgView.prototype.getAddressesFieldInfo = 1424 function(addrs, options, type, htmlElId) { 1425 return this.getAddressesFieldHtmlHelper(addrs, options, type, this._objectManager, htmlElId || this._htmlElId); 1426 }; 1427 1428 ZmMailMsgView.prototype._renderMessage = 1429 function(msg, container, callback) { 1430 1431 this._renderMessageHeader(msg, container); 1432 this._renderMessageBody(msg, container, callback); 1433 this._renderMessageFooter(msg, container); 1434 Dwt.setLoadedTime("ZmMailItem"); 1435 }; 1436 1437 ZmMailMsgView.prototype._renderMessageHeader = 1438 function(msg, container, doNotClearBubbles) { 1439 1440 if (!doNotClearBubbles) { 1441 this._clearBubbles(); 1442 } 1443 1444 this._renderInviteToolbar(msg, container); 1445 1446 var ai = this._getAddrInfo(msg); 1447 1448 var subject = AjxStringUtil.htmlEncode(msg.subject || ZmMsg.noSubject); 1449 var dateFormatter = AjxDateFormat.getDateTimeInstance(AjxDateFormat.LONG, AjxDateFormat.SHORT); 1450 // bug fix #31512 - if no sent date then display received date 1451 var date = new Date(msg.sentDate || msg.date); 1452 var dateString = dateFormatter.format(date); 1453 1454 var additionalHdrs = []; 1455 var invite = msg.invite; 1456 var autoSendTime = AjxUtil.isDate(msg.autoSendTime) ? AjxDateFormat.getDateTimeInstance(AjxDateFormat.FULL, AjxDateFormat.MEDIUM).format(msg.autoSendTime) : null; 1457 1458 if (msg.attrs) { 1459 for (var hdrName in ZmMailMsgView.displayAdditionalHdrsInMsgView) { 1460 if (msg.attrs[hdrName]) { 1461 additionalHdrs.push({hdrName:ZmMailMsgView.displayAdditionalHdrsInMsgView[hdrName], hdrVal: msg.attrs[hdrName]}); 1462 } 1463 } 1464 } 1465 1466 var options = {}; 1467 options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS); 1468 1469 var attachmentsCount = msg.getAttachmentCount(true); 1470 1471 // do we add a close button in the header section? 1472 1473 var folder = appCtxt.getById(msg.folderId); 1474 var isSyncFailureMsg = (folder && folder.nId == ZmOrganizer.ID_SYNC_FAILURES); 1475 if (!msg.showImages) { 1476 msg.showImages = folder && folder.isFeed(); 1477 } 1478 1479 this._hdrTableId = ZmId.getViewId(this._viewId, ZmId.MV_HDR_TABLE, this._mode); 1480 var reportBtnCellId = ZmId.getViewId(this._viewId, ZmId.MV_REPORT_BTN_CELL, this._mode); 1481 this._expandRowId = ZmId.getViewId(this._viewId, ZmId.MV_EXPAND_ROW, this._mode); 1482 1483 // the message view adapts to whatever height the image has, but 1484 // more than 96 pixels is a bit silly... 1485 var imageURL = ai.sentByContact && ai.sentByContact.getImageUrl(48, 96), 1486 imageAltText = imageURL && ai.sentByContact && ai.sentByContact.getFullName(); 1487 1488 var subs = { 1489 id: this._htmlElId, 1490 hdrTableId: this._hdrTableId, 1491 hdrTableTopRowId: ZmId.getViewId(this._viewId, ZmId.MV_HDR_TABLE_TOP_ROW, this._mode), 1492 expandRowId: this._expandRowId, 1493 attachId: this._attLinksId, 1494 infoBarId: this._infoBarId, 1495 subject: subject, 1496 imageURL: imageURL || ZmZimbraMail.DEFAULT_CONTACT_ICON, 1497 imageAltText: imageAltText || ZmMsg.noContactImage, 1498 dateString: dateString, 1499 hasAttachments: (attachmentsCount != 0), 1500 attachmentsCount: attachmentsCount, 1501 bwo: ai.bwo, 1502 bwoAddr: ai.bwoAddr, 1503 bwoId: ZmId.getViewId(this._viewId, ZmId.CMP_BWO_SPAN, this._mode) 1504 }; 1505 1506 if (msg.isHighPriority || msg.isLowPriority) { 1507 subs.priority = msg.isHighPriority ? "high" : "low"; 1508 subs.priorityImg = msg.isHighPriority ? "ImgPriorityHigh_list" : "ImgPriorityLow_list"; 1509 subs.priorityDivId = ZmId.getViewId(this._view, ZmId.MV_PRIORITY); 1510 } 1511 1512 if (invite && !invite.isEmpty() && this._inviteMsgView) { 1513 this._getInviteSubs(subs, ai.sentBy, ai.sentByAddr, ai.sender ? ai.fromAddr : null); 1514 } 1515 else { 1516 subs.sentBy = ai.sentBy; 1517 subs.sentByNormal = ai.sentByAddr; 1518 subs.sentByAddr = ai.sentByAddr; 1519 subs.obo = ai.obo; 1520 subs.oboAddr = ai.oboAddr; 1521 subs.oboId = ZmId.getViewId(this._viewId, ZmId.CMP_OBO_SPAN, this._mode) 1522 subs.addressTypes = ai.addressTypes; 1523 subs.participants = ai.participants; 1524 subs.reportBtnCellId = reportBtnCellId; 1525 subs.isSyncFailureMsg = isSyncFailureMsg; 1526 subs.autoSendTime = autoSendTime; 1527 subs.additionalHdrs = additionalHdrs; 1528 subs.isOutDated = invite && invite.isEmpty(); 1529 } 1530 1531 var template = (invite && !invite.isEmpty() && this._inviteMsgView) 1532 ? "mail.Message#InviteHeader" : "mail.Message#MessageHeader"; 1533 var html = AjxTemplate.expand(template, subs); 1534 1535 var el = container || this.getHtmlElement(); 1536 el.setAttribute('aria-label', subject); 1537 el.appendChild(Dwt.parseHtmlFragment(html)); 1538 this._headerElement = Dwt.byId(this._htmlElId + "_headerElement"); 1539 this._makeFocusable(this._headerElement); 1540 1541 this._headerTabGroup.removeAllMembers(); 1542 this._headerTabGroup.addMember(this._headerElement); 1543 1544 if (this._inviteMsgView) { 1545 if (this._inviteToolbarCellId && this._inviteToolbarCellId && this._inviteMsgView._inviteToolbar) { 1546 this._inviteMsgView._inviteToolbar.reparentHtmlElement(this._inviteToolbarCellId, 0); 1547 } 1548 if (this._calendarSelectCellId && this._inviteMsgView._inviteMoveSelect) { 1549 this._inviteMsgView._inviteMoveSelect.reparentHtmlElement(this._calendarSelectCellId, 0); 1550 } 1551 this._inviteMsgView.repositionCounterToolbar(this._hdrTableId); 1552 this._headerTabGroup.addMember(this._inviteMsgView._inviteToolbar); 1553 } 1554 1555 1556 /**************************************************************************/ 1557 /* Add to DOM based on Id's used to generate HTML via templates */ 1558 /**************************************************************************/ 1559 // add the report button if applicable 1560 var reportBtnCell = document.getElementById(reportBtnCellId); 1561 if (reportBtnCell) { 1562 var id = ZmId.getButtonId(this._mode, ZmId.REPORT, ZmId.MSG_VIEW); 1563 var reportBtn = new DwtButton({parent:this, id:id, parentElement:reportBtnCell}); 1564 reportBtn.setText(ZmMsg.reportSyncFailure); 1565 reportBtn.addSelectionListener(this._reportButtonListener.bind(this, msg)); 1566 } 1567 1568 if (this._hasShareToolbar) { 1569 var topToolbar = this._getShareToolbar(); 1570 topToolbar.reparentHtmlElement(container); 1571 topToolbar.setVisible(Dwt.DISPLAY_BLOCK); 1572 this._headerTabGroup.addMember(topToolbar); 1573 } 1574 }; 1575 1576 // Returns a hash with what we need to show the message's address headers 1577 ZmMailMsgView.prototype._getAddrInfo = 1578 function(msg) { 1579 1580 var acctId = appCtxt.getActiveAccount().id; 1581 var cl; 1582 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && appCtxt.getApp(ZmApp.CONTACTS).contactsLoaded[acctId]) { 1583 cl = AjxDispatcher.run("GetContacts"); 1584 } 1585 var fromAddr = msg.getAddress(AjxEmailAddress.FROM); 1586 // if we have no FROM address and msg is in an outbound folder, assume current user is the sender 1587 if (!fromAddr) { 1588 var folder = msg.folderId && appCtxt.getById(msg.folderId); 1589 if (folder && folder.isOutbound()) { 1590 var identity = appCtxt.getIdentityCollection().defaultIdentity; 1591 if (identity) { 1592 fromAddr = new AjxEmailAddress(identity.sendFromAddress, AjxEmailAddress.FROM, identity.sendFromDisplay); 1593 } 1594 } 1595 } 1596 var sender = msg.getAddress(AjxEmailAddress.SENDER); // bug fix #10652 - Sender: header means on-behalf-of 1597 var sentBy = (sender && sender.address) ? sender : fromAddr; 1598 var from = AjxStringUtil.htmlEncode(fromAddr ? fromAddr.toString(true) : ZmMsg.unknown); 1599 var sentByAddr = sentBy && sentBy.getAddress(); 1600 if (sentByAddr) { 1601 msg.sentByAddr = sentByAddr; 1602 msg.sentByDomain = sentByAddr.substr(sentByAddr.indexOf("@") + 1); 1603 msg.showImages = this._isTrustedSender(msg); 1604 } 1605 var sentByContact = cl && cl.getContactByEmail(sentBy && sentBy.getAddress()); //bug 78163 originally 1606 var obo = sender ? fromAddr : null; 1607 var oboAddr = obo && obo.getAddress(); 1608 1609 var bwo = msg.getAddress(AjxEmailAddress.RESENT_FROM); 1610 var bwoAddr = bwo ? bwo.getAddress() : null; 1611 1612 // find addresses we may need to search for contacts for, so that we can 1613 // aggregate them into a single search 1614 var contactsApp = appCtxt.getApp(ZmApp.CONTACTS); 1615 if (contactsApp) { 1616 var lookupAddrs = []; 1617 if (sentBy) { lookupAddrs.push(sentBy); } 1618 if (obo) { lookupAddrs.push(obo); } 1619 for (var i = 1; i < ZmMailMsg.ADDRS.length; i++) { 1620 var type = ZmMailMsg.ADDRS[i]; 1621 if ((type == AjxEmailAddress.SENDER) || (type == AjxEmailAddress.RESENT_FROM)) { continue; } 1622 var addrs = msg.getAddresses(type).getArray(); 1623 for (var j = 0; j < addrs.length; j++) { 1624 if (addrs[j]) { 1625 lookupAddrs.push(addrs[j].address); 1626 } 1627 } 1628 } 1629 if (lookupAddrs.length > 1) { 1630 contactsApp.setAddrLookupGroup(lookupAddrs); 1631 } 1632 } 1633 1634 var options = {}; 1635 options.shortAddress = appCtxt.get(ZmSetting.SHORT_ADDRESS); 1636 1637 if (this._objectManager) { 1638 this._lazyCreateObjectManager(); 1639 appCtxt.notifyZimlets("onFindMsgObjects", [msg, this._objectManager, this]); 1640 } 1641 1642 sentBy = this._getBubbleHtml(sentBy); 1643 obo = obo && this._getBubbleHtml(fromAddr); 1644 bwo = bwo && this._getBubbleHtml(bwo); 1645 1646 var showMoreIds = {}; 1647 var addressTypes = [], participants = {}; 1648 for (var i = 0; i < ZmMailMsg.ADDRS.length; i++) { 1649 var type = ZmMailMsg.ADDRS[i]; 1650 if ((type == AjxEmailAddress.FROM) || (type == AjxEmailAddress.SENDER) || (type == AjxEmailAddress.RESENT_FROM)) { continue; } 1651 1652 var addrs = AjxEmailAddress.dedup(msg.getAddresses(type).getArray()); 1653 1654 if (type == AjxEmailAddress.REPLY_TO){ // bug: 79175 - Reply To shouldn't be shown when it matches From 1655 var k = addrs.length; 1656 for (var j = 0; j < k;){ 1657 if (addrs[j].address === fromAddr.address){ 1658 addrs.splice(j,1); 1659 k--; 1660 } 1661 else { 1662 j++; 1663 } 1664 } 1665 } 1666 1667 if (addrs.length > 0) { 1668 var prefix = AjxStringUtil.htmlEncode(ZmMsg[AjxEmailAddress.TYPE_STRING[type]]); 1669 var addressInfo = this.getAddressesFieldInfo(addrs, options, type); 1670 addressTypes.push(type); 1671 participants[type] = { prefix: prefix, partStr: addressInfo.html }; 1672 if (addressInfo.showMoreLinkId) { 1673 showMoreIds[addressInfo.showMoreLinkId] = true; 1674 } 1675 } 1676 } 1677 1678 return { 1679 fromAddr: fromAddr, 1680 from: from, 1681 sender: sender, 1682 sentBy: sentBy, 1683 sentByAddr: sentByAddr, 1684 sentByContact: sentByContact, 1685 obo: obo, 1686 oboAddr: oboAddr, 1687 bwo: bwo, 1688 bwoAddr: bwoAddr, 1689 addressTypes: addressTypes, 1690 participants: participants, 1691 showMoreIds: showMoreIds 1692 }; 1693 }; 1694 1695 ZmMailMsgView.prototype._getInviteSubs = 1696 function(subs, sentBy, sentByAddr, sender, addr) { 1697 this._inviteMsgView.addSubs(subs, sentBy, sentByAddr, sender ? addr : null); 1698 var imv = this._inviteMsgView; 1699 if (imv._inviteToolbar && imv._inviteToolbar.getVisible()) { 1700 subs.toolbarCellId = this._inviteToolbarCellId = 1701 [this._viewId, "inviteToolbarCell"].join("_"); 1702 } 1703 if (imv._inviteMoveSelect && imv._inviteMoveSelect.getVisible()) { 1704 subs.calendarSelectCellId = this._calendarSelectCellId = 1705 [this._viewId, "calendarSelectToolbarCell"].join("_"); 1706 } 1707 }; 1708 1709 ZmMailMsgView.prototype._renderInviteToolbar = 1710 function(msg, container) { 1711 1712 this._dateObjectHandlerDate = new Date(msg.sentDate || msg.date); 1713 this._hasShareToolbar = this._hasSubToolbar = false; 1714 1715 var invite = msg.invite; 1716 var ac = window.parentAppCtxt || window.appCtxt; 1717 1718 if ((ac.get(ZmSetting.CALENDAR_ENABLED) || ac.multiAccounts) && 1719 (invite && !invite.isEmpty() && invite.type != "task")) 1720 { 1721 if (!this._inviteMsgView) { 1722 this._inviteMsgView = new ZmInviteMsgView({parent:this, mode:this._mode}); 1723 } 1724 this._inviteMsgView.set(msg); 1725 } 1726 else if (appCtxt.get(ZmSetting.SHARING_ENABLED) && msg.share && 1727 ZmOrganizer.normalizeId(msg.folderId) != ZmFolder.ID_TRASH && 1728 ZmOrganizer.normalizeId(msg.folderId) != ZmFolder.ID_SENT && 1729 appCtxt.getActiveAccount().id != msg.share.grantor.id) 1730 { 1731 AjxDispatcher.require("Share"); 1732 var action = msg.share.action; 1733 var isNew = action == ZmShare.NEW; 1734 var isEdit = action == ZmShare.EDIT; 1735 var folder = appCtxt.getById(msg.folderId); 1736 var isDataSource = (folder && folder.isDataSource(null, true) && (msg.folderId != ZmFolder.ID_INBOX)); 1737 1738 if (!isDataSource && 1739 (isNew || (isEdit && !this.__hasMountpoint(msg.share))) && 1740 msg.share.link.perm) 1741 { 1742 this._hasShareToolbar = true; 1743 } 1744 } 1745 else if (msg.subscribeReq && msg.folderId != ZmFolder.ID_TRASH) { 1746 var topToolbar = this._getSubscribeToolbar(msg.subscribeReq); 1747 topToolbar.reparentHtmlElement(container); 1748 topToolbar.setVisible(Dwt.DISPLAY_BLOCK); 1749 this._hasSubToolbar = true; 1750 } 1751 }; 1752 1753 /** 1754 * Renders the message body. There is a chance a server call will be made to fetch an alternative part. 1755 * 1756 * @param {ZmMailMsg} msg 1757 * @param {Element} container 1758 * @param {callback} callback 1759 */ 1760 ZmMailMsgView.prototype._renderMessageBody = 1761 function(msg, container, callback, index) { 1762 1763 var htmlMode = appCtxt.get(ZmSetting.VIEW_AS_HTML); 1764 var contentType = htmlMode ? ZmMimeTable.TEXT_HTML : ZmMimeTable.TEXT_PLAIN; 1765 msg.getBodyPart(contentType, this._renderMessageBody1.bind(this, { 1766 msg: msg, 1767 container: container, 1768 callback: callback, 1769 index: index 1770 })); 1771 }; 1772 1773 // The second argument 'part' is added to the callback by getBodyPart() above. We ignore it 1774 // and just get the body parts from the loaded msg. 1775 ZmMailMsgView.prototype._renderMessageBody1 = function(params, part) { 1776 1777 var msg = params.msg, 1778 htmlMode = appCtxt.get(ZmSetting.VIEW_AS_HTML), 1779 preferredContentType = params.forceType || (htmlMode ? ZmMimeTable.TEXT_HTML : ZmMimeTable.TEXT_PLAIN), 1780 hasHtmlPart = (preferredContentType === ZmMimeTable.TEXT_HTML && msg.hasContentType(ZmMimeTable.TEXT_HTML)) || msg.hasInlineImage(), 1781 hasMultipleBodyParts = msg.hasMultipleBodyParts(), 1782 bodyParts = hasMultipleBodyParts ? msg.getBodyParts(preferredContentType) : [ msg.getBodyPart(preferredContentType) || msg.getBodyPart() ], 1783 invite = msg.invite, 1784 hasInviteContent = invite && !invite.isEmpty(), 1785 origText, 1786 isTextMsg = !hasHtmlPart, 1787 isTruncated = false, 1788 hasViewableTextContent = false, 1789 html = []; 1790 1791 bodyParts = AjxUtil.collapseList(bodyParts); 1792 1793 // The server tells us which parts are worth displaying by marking them as body parts. In general, 1794 // we just append them in order to the output, with some special handling for each based on its content type. 1795 1796 for (var i = 0; i < bodyParts.length; i++) { 1797 1798 var bp = bodyParts[i], 1799 ct = bp.contentType, 1800 content = this._getBodyContent(bp), 1801 isImage = ZmMimeTable.isRenderableImage(ct), 1802 isHtml = (ct === ZmMimeTable.TEXT_HTML), 1803 isPlain = (ct === ZmMimeTable.TEXT_PLAIN); 1804 1805 isTruncated = isTruncated || this.isTruncated(bp); 1806 1807 // first let's check for invite notes and use those as content if present 1808 if (hasInviteContent && !hasMultipleBodyParts) { 1809 if (!msg.getMimeHeader(ZmMailMsg.HDR_INREPLYTO)) { 1810 // Hack - bug 70603 - Do not truncate the message for forwarded invites 1811 // The InReplyTo rfc822 header would be present in most of the forwarded invites 1812 content = ZmInviteMsgView.truncateBodyContent(content, isHtml); 1813 } 1814 // if the notes are empty, don't bother rendering them 1815 var tmp = AjxStringUtil.stripTags(content); 1816 if (!AjxStringUtil._NON_WHITESPACE.test(tmp)) { 1817 content = ""; 1818 } 1819 } 1820 1821 // Handle the part based on its Content-Type 1822 1823 // images 1824 if (isImage) { 1825 var src = (hasMultipleBodyParts && content.length > 0) ? content : msg.getUrlForPart(bp), 1826 classAttr = hasMultipleBodyParts ? "class='InlineImage' " : " "; 1827 1828 content = "<img " + [ "zmforced='1' " + classAttr + "src='" + src + "'>"].join(""); 1829 } 1830 1831 // calendar part in ICS format 1832 else if (ct === ZmMimeTable.TEXT_CAL) { 1833 content = ZmMailMsg.getTextFromCalendarPart(bp); 1834 } 1835 1836 // HTML 1837 else if (isHtml) { 1838 if (htmlMode) { 1839 // fix broken inline images - take one like this: <img dfsrc="http:...part=1.2.2"> 1840 // and make it look like this: <img dfsrc="cid:DWT123"> by looking up the cid for that part 1841 if (msg._attachments && ZmMailMsgView.IMG_FIX_RE.test(content)) { 1842 var partToCid = {}; 1843 for (var j = 0; j < msg._attachments.length; j++) { 1844 var att = msg._attachments[j]; 1845 if (att.contentId) { 1846 partToCid[att.part] = att.contentId.substring(1, att.contentId.length - 1); 1847 } 1848 } 1849 content = content.replace(ZmMailMsgView.IMG_FIX_RE, function(s, p1, p2, p3) { 1850 return partToCid[p2] ? [ p1, '"cid:', partToCid[p2], '"', p3 ].join("") : s; 1851 }); 1852 } 1853 } 1854 else { 1855 // this can happen if a message only has an HTML part and the user wants to view mail as text 1856 content = "<div style='white-space:pre-wrap;'>" + AjxStringUtil.convertHtml2Text(content) + "</div>" 1857 } 1858 } 1859 1860 // plain text 1861 else if (isPlain) { 1862 origText = content; 1863 if (bp.format === ZmMimeTable.FORMAT_FLOWED) { 1864 var wrapParams = { 1865 text: content, 1866 isFlowed: true 1867 } 1868 content = AjxStringUtil.wordWrap(wrapParams); 1869 } 1870 content = AjxStringUtil.convertToHtml(content); 1871 if (content && hasMultipleBodyParts && hasHtmlPart) { 1872 content = "<pre>" + content + "</pre>"; 1873 } 1874 } 1875 1876 // something else 1877 else { 1878 content = AjxStringUtil.convertToHtml(content); 1879 } 1880 1881 // wrap it in a DIV to be safe 1882 if (content && content.length) { 1883 if (!isImage && AjxStringUtil.trimHtml(content).length > 0) { 1884 content = "<div>" + content + "</div>"; 1885 hasViewableTextContent = true; 1886 } 1887 html.push(content); 1888 } 1889 } 1890 1891 // Handle empty messages 1892 if (!hasMultipleBodyParts && !hasViewableTextContent && msg.hasNoViewableContent()) { 1893 // if we got nothing for one alternative type, try the other 1894 if (msg.hasContentType(ZmMimeTable.MULTI_ALT) && !params.forceType) { 1895 var otherType = (preferredContentType === ZmMimeTable.TEXT_HTML) ? ZmMimeTable.TEXT_PLAIN : ZmMimeTable.TEXT_HTML; 1896 params.forceType = otherType; 1897 msg.getBodyPart(otherType, this._renderMessageBody1.bind(this, params)); 1898 return; 1899 } 1900 var empty = AjxTemplate.expand("mail.Message#EmptyMessage"); 1901 html.push(content ? [empty, content].join("<br><br>") : empty); 1902 } 1903 1904 if (html.length > 0) { 1905 this._displayContent({ 1906 container: params.container || this.getHtmlElement(), 1907 html: html.join(""), 1908 isTextMsg: isTextMsg, 1909 isTruncated: isTruncated, 1910 index: params.index, 1911 origText: origText 1912 }); 1913 } 1914 1915 this._completeMessageBody(params.callback, isTextMsg); 1916 }; 1917 1918 ZmMailMsgView.prototype.isTruncated = 1919 function(part) { 1920 return part.isTruncated; 1921 }; 1922 1923 ZmMailMsgView.prototype._completeMessageBody = function(callback, isTextMsg) { 1924 1925 // Used in ZmConvView2._setExpansion : if false, create the message body (the 1926 // first time a message is expanded). 1927 this._msgBodyCreated = true; 1928 this._setAttachmentLinks(AjxUtil.isBoolean(isTextMsg) ? isTextMsg : appCtxt.get(ZmSetting.VIEW_AS_HTML)); 1929 1930 if (callback) { 1931 callback.run(); 1932 } 1933 }; 1934 1935 ZmMailMsgView.prototype._getBodyContent = 1936 function(bodyPart) { 1937 return bodyPart ? bodyPart.getContent() : ""; 1938 }; 1939 1940 ZmMailMsgView.prototype._renderMessageFooter = function(msg, container) {}; 1941 1942 ZmMailMsgView.prototype._setTags = 1943 function(msg) { 1944 if (!msg) { 1945 msg = this._item; 1946 } 1947 if (msg && msg.cloneOf) { 1948 msg = msg.cloneOf; 1949 } 1950 //use the helper to get the tags. 1951 var tagsHtml = ZmTagsHelper.getTagsHtml(msg, this); 1952 1953 var table = document.getElementById(this._hdrTableId); 1954 if (!table) { return; } 1955 var tagRow = $(table).find(document.getElementById(this._tagRowId)); 1956 1957 if (tagRow.length) { 1958 tagRow.remove(); 1959 } 1960 if (tagsHtml.length > 0) { 1961 var cell = this._insertTagRow(table, this._tagCellId); 1962 cell.innerHTML = tagsHtml; 1963 } 1964 }; 1965 1966 ZmMailMsgView.prototype._insertTagRow = 1967 function(table, tagCellId) { 1968 1969 if (!table) { return; } 1970 1971 var tagRow = table.insertRow(-1); 1972 tagRow.id = this._tagRowId; 1973 var tagLabelCell = tagRow.insertCell(-1); 1974 tagLabelCell.className = "LabelColName"; 1975 tagLabelCell.innerHTML = ZmMsg.tags + ":"; 1976 tagLabelCell.style.verticalAlign = "middle"; 1977 var tagCell = tagRow.insertCell(-1); 1978 tagCell.id = tagCellId; 1979 return tagCell; 1980 }; 1981 1982 1983 // Types of links for each attachment 1984 ZmMailMsgView.ATT_LINK_MAIN = "main"; 1985 ZmMailMsgView.ATT_LINK_CALENDAR = "calendar"; 1986 ZmMailMsgView.ATT_LINK_DOWNLOAD = "download"; 1987 ZmMailMsgView.ATT_LINK_BRIEFCASE = "briefcase"; 1988 ZmMailMsgView.ATT_LINK_VCARD = "vcard"; 1989 ZmMailMsgView.ATT_LINK_HTML = "html"; 1990 ZmMailMsgView.ATT_LINK_REMOVE = "remove"; 1991 1992 ZmMailMsgView.prototype._setAttachmentLinks = function(isTextMsg) { 1993 1994 this._attachmentLinkIdToFileNameMap = null; 1995 var attInfo = this._msg.getAttachmentInfo(true, false, isTextMsg); 1996 var el = document.getElementById(this._attLinksId + "_container"); 1997 if (el) { 1998 el.style.display = (attInfo.length == 0) ? "none" : ""; 1999 } 2000 if (attInfo.length == 0) { return; } 2001 2002 // prevent appending attachment links more than once 2003 var attLinksTable = document.getElementById(this._attLinksId + "_table"); 2004 if (attLinksTable) { return; } 2005 2006 var htmlArr = []; 2007 var idx = 0; 2008 var imageAttsFound = 0; 2009 2010 var attColumns = (this._controller.isReadingPaneOn() && this._controller.isReadingPaneOnRight()) ? 1 : ZmMailMsgView.ATTC_COLUMNS; 2011 var dividx = idx; // we might get back here 2012 htmlArr[idx++] = "<table id='" + this._attLinksId + "_table' border=0 cellpadding=0 cellspacing=0>"; 2013 2014 var attLinkIds = []; 2015 var rows = 0; 2016 for (var i = 0; i < attInfo.length; i++) { 2017 var att = attInfo[i]; 2018 2019 if ((i % attColumns) == 0) { 2020 if (i != 0) { 2021 htmlArr[idx++] = "</tr>"; 2022 } 2023 htmlArr[idx++] = "<tr>"; 2024 ++rows; 2025 } 2026 2027 htmlArr[idx++] = "<td>"; 2028 htmlArr[idx++] = "<table border=0 cellpadding=0 cellspacing=0 style='margin-right:1em; margin-bottom:1px'><tr>"; 2029 htmlArr[idx++] = "<td style='width:18px'>"; 2030 htmlArr[idx++] = AjxImg.getImageHtml({ 2031 imageName: att.linkIcon, 2032 styles: "position:relative;", 2033 altText: ZmMsg.attachment 2034 }); 2035 htmlArr[idx++] = "</td><td style='white-space:nowrap'>"; 2036 2037 if (appCtxt.get(ZmSetting.ATTACHMENTS_BLOCKED)) { 2038 // if attachments are blocked, just show the label 2039 htmlArr[idx++] = att.label; 2040 } else { 2041 // main link for the att name 2042 var linkArr = []; 2043 var j = 0; 2044 var displayFileName = AjxStringUtil.clipFile(att.label, 30); 2045 // if name got clipped, set up to show full name in tooltip 2046 if (displayFileName != att.label) { 2047 if (!this._attachmentLinkIdToFileNameMap) { 2048 this._attachmentLinkIdToFileNameMap = {}; 2049 } 2050 this._attachmentLinkIdToFileNameMap[att.attachmentLinkId] = att.label; 2051 } 2052 var params = { 2053 att: att, 2054 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_MAIN), 2055 text: displayFileName, 2056 mid: att.mid, 2057 rfc822Part: att.rfc822Part 2058 }; 2059 var link = ZmMailMsgView.getMainAttachmentLinkHtml(params); 2060 link = att.isHit ? "<span class='AttName-matched'>" + link + "</span>" : link; 2061 // objectify if this attachment is an image 2062 if (att.objectify && this._objectManager) { 2063 this._lazyCreateObjectManager(); 2064 var imgHandler = this._objectManager.getImageAttachmentHandler(); 2065 idx = this._objectManager.generateSpan(imgHandler, htmlArr, idx, link, {url:att.url}); 2066 } else { 2067 htmlArr[idx++] = link; 2068 } 2069 } 2070 2071 // add any discretionary links depending on the attachment and what's enabled 2072 var linkCount = 0; 2073 var vCardLink = (att.links.vcard && !appCtxt.isWebClientOffline()); 2074 if (!appCtxt.isExternalAccount() && (att.size || att.links.html || vCardLink || att.links.download || att.links.briefcase || att.links.importICS)) { 2075 // size 2076 htmlArr[idx++] = " ("; 2077 if (att.size) { 2078 htmlArr[idx++] = att.size; 2079 htmlArr[idx++] = ") "; 2080 } 2081 // convert to HTML 2082 if (att.links.html && !appCtxt.get(ZmSetting.ATTACHMENTS_BLOCKED)) { 2083 var params = { 2084 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_HTML), 2085 blankTarget: true, 2086 href: att.url + "&view=html", 2087 text: ZmMsg.preview 2088 }; 2089 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2090 linkCount++; 2091 attLinkIds.push(params.id); 2092 } 2093 // save as vCard 2094 else if (vCardLink) { 2095 var params = { 2096 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_VCARD), 2097 jsHref: true, 2098 text: ZmMsg.addressBook 2099 }; 2100 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2101 linkCount++; 2102 attLinkIds.push(params.id); 2103 } 2104 // save locally 2105 if (att.links.download && !appCtxt.get(ZmSetting.ATTACHMENTS_BLOCKED) && !appCtxt.get(ZmSetting.ATTACHMENTS_VIEW_IN_HTML_ONLY)) { 2106 htmlArr[idx++] = linkCount ? " | " : ""; 2107 var params = { 2108 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_DOWNLOAD), 2109 text: ZmMsg.download 2110 }; 2111 if (att.url.indexOf("data:") === -1) { 2112 params.href = att.url + "&disp=a"; 2113 } else { 2114 params.href = att.url; 2115 params.download = true; 2116 params.downloadLabel = att.label; 2117 } 2118 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2119 linkCount++; 2120 attLinkIds.push(params.id); 2121 } 2122 // add as Briefcase file 2123 if (att.links.briefcase && !appCtxt.get(ZmSetting.ATTACHMENTS_BLOCKED) && !appCtxt.isWebClientOffline()) { 2124 htmlArr[idx++] = linkCount ? " | " : ""; 2125 var params = { 2126 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_BRIEFCASE), 2127 jsHref: true, 2128 text: ZmMsg.addToBriefcase 2129 }; 2130 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2131 linkCount++; 2132 attLinkIds.push(params.id); 2133 } 2134 // add ICS as calendar event 2135 if (att.links.importICS) { 2136 htmlArr[idx++] = linkCount ? " | " : ""; 2137 var params = { 2138 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_CALENDAR), 2139 jsHref: true, 2140 text: ZmMsg.addToCalendar 2141 }; 2142 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2143 linkCount++; 2144 attLinkIds.push(params.id); 2145 } 2146 // remove attachment from msg 2147 if (att.links.remove && !appCtxt.isWebClientOffline()) { 2148 htmlArr[idx++] = linkCount ? " | " : ""; 2149 var params = { 2150 id: this._getAttachmentLinkId(att.part, ZmMailMsgView.ATT_LINK_REMOVE), 2151 jsHref: true, 2152 text: ZmMsg.remove 2153 }; 2154 htmlArr[idx++] = ZmMailMsgView.getAttachmentLinkHtml(params); 2155 linkCount++; 2156 attLinkIds.push(params.id); 2157 } 2158 2159 // Attachment Link Handlers (optional) 2160 if (ZmMailMsgView._attachmentHandlers) { 2161 var contentHandlers = ZmMailMsgView._attachmentHandlers[att.ct]; 2162 var handlerFunc; 2163 if (contentHandlers) { 2164 for (var handlerId in contentHandlers) { 2165 handlerFunc = contentHandlers[handlerId]; 2166 if (handlerFunc) { 2167 var customHandlerLinkHTML = handlerFunc.call(this, att); 2168 if (customHandlerLinkHTML) { 2169 htmlArr[idx++] = " | " + customHandlerLinkHTML; 2170 } 2171 } 2172 } 2173 } 2174 } 2175 } 2176 2177 htmlArr[idx++] = "</td></tr></table>"; 2178 htmlArr[idx++] = "</td>"; 2179 2180 if (att.ct.indexOf("image") != -1) { 2181 ++imageAttsFound; 2182 } 2183 } 2184 2185 // limit display size. seems like an attc. row has exactly 16px; we set it 2186 // to 56px so that it becomes obvious that there are more attachments. 2187 if (this._limitAttachments != 0 && rows > ZmMailMsgView._limitAttachments) { 2188 htmlArr[dividx] = "<div style='height:"; 2189 htmlArr[dividx] = this._attcMaxSize; 2190 htmlArr[dividx] = "px; overflow:auto;' />"; 2191 } 2192 htmlArr[idx++] = "</tr></table>"; 2193 2194 var allAttParams; 2195 var hasGeneratedAttachments = false; 2196 2197 for (var i = 0; i < attInfo.length; i++) { 2198 hasGeneratedAttachments = hasGeneratedAttachments || att.generated; 2199 } 2200 2201 if (!hasGeneratedAttachments && attInfo.length > 1 && !appCtxt.isWebClientOffline()) { 2202 allAttParams = this._addAllAttachmentsLinks(attInfo, (imageAttsFound > 1), this._msg.subject); 2203 htmlArr[idx++] = allAttParams.html; 2204 } 2205 2206 // Push all that HTML to the DOM 2207 var attLinksDiv = document.getElementById(this._attLinksId); 2208 if (attLinksDiv) { 2209 attLinksDiv.innerHTML = htmlArr.join(""); 2210 } 2211 2212 2213 // add handlers for individual attachment links 2214 for (var i = 0; i < attInfo.length; i++) { 2215 var att = attInfo[i]; 2216 if (att.ct == ZmMimeTable.MSG_RFC822) { 2217 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_MAIN, ZmMailMsgView.rfc822Callback, null, this._msg.id, att.part); 2218 } 2219 if (att.links.importICS) { 2220 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_CALENDAR, ZmMailMsgView.addToCalendarCallback, null, this._msg.id, att.part); 2221 } 2222 if (att.links.briefcase) { 2223 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_BRIEFCASE, ZmMailMsgView.briefcaseCallback, null, this._msg.id, att.part, att.label.replace(/\x27/g, "'")); 2224 } 2225 if (att.links.download) { 2226 if (att.url.indexOf("data:") === -1) { 2227 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_DOWNLOAD, ZmMailMsgView.downloadCallback, null, att.url + "&disp=a"); 2228 } 2229 } 2230 if (att.links.vcard) { 2231 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_VCARD, ZmMailMsgView.vcardCallback, null, this._msg.id, att.part); 2232 } 2233 if (att.links.remove) { 2234 this._addClickHandler(att.part, ZmMailMsgView.ATT_LINK_REMOVE, this.removeAttachmentCallback, this, att.part); 2235 } 2236 } 2237 2238 var offlineHandler = appCtxt.webClientOfflineHandler; 2239 if (offlineHandler) { 2240 var getLinkIdCallback = this._getAttachmentLinkId.bind(this); 2241 var linkIds = [ZmMailMsgView.ATT_LINK_MAIN, ZmMailMsgView.ATT_LINK_DOWNLOAD]; 2242 offlineHandler._handleAttachmentsForOfflineMode(attInfo, getLinkIdCallback, linkIds); 2243 } 2244 2245 // add handlers for "all attachments" links 2246 if (allAttParams) { 2247 var downloadAllLink = document.getElementById(allAttParams.downloadAllLinkId); 2248 if (downloadAllLink) { 2249 downloadAllLink.onclick = allAttParams.downloadAllCallback; 2250 } 2251 var removeAllLink = document.getElementById(allAttParams.removeAllLinkId); 2252 if (removeAllLink) { 2253 removeAllLink.onclick = allAttParams.removeAllCallback; 2254 } 2255 } 2256 2257 // add all links to the header tab order 2258 var attLinks = attLinksDiv.querySelectorAll('A.AttLink'); 2259 for (var i = 0; i < attLinks.length; i++) { 2260 this._headerTabGroup.addMember(attLinks[i]); 2261 } 2262 }; 2263 2264 /** 2265 * Returns the HTML for an attachment-related link (an <a> tag). The link will have an HREF 2266 * or an ID (so an onclick handler can be added after the element has been created). 2267 * 2268 * @param {hash} params a hash of params: 2269 * @param {string} id ID for the link 2270 * @param {string} href link target 2271 * @param {boolean} noUnderline if true, do not include an underline style 2272 * @param {boolean} blankTarget if true, set target to _blank 2273 * @param {boolean} jsHref empty link target so browser styles it as a link 2274 * @param {string} text visible link text 2275 * 2276 * @private 2277 */ 2278 ZmMailMsgView.getAttachmentLinkHtml = 2279 function(params) { 2280 var html = [], i = 0; 2281 html[i++] = "<a class='AttLink' "; 2282 html[i++] = params.id ? "id='" + params.id + "' " : ""; 2283 html[i++] = !params.noUnderline ? "style='text-decoration:underline' " : ""; 2284 html[i++] = params.blankTarget ? "target='_blank' " : ""; 2285 var href = params.href || (params.jsHref && "javascript:;"); 2286 html[i++] = href ? "href='" + href + "' " : ""; 2287 html[i++] = params.download ? (" download='"+(params.downloadLabel||"") + "'") : ""; 2288 if (params.isRfc822) { 2289 html[i++] = " onclick='ZmMailMsgView.rfc822Callback(\""; 2290 html[i++] = params.mid; 2291 html[i++] = "\",\""; 2292 html[i++] = params.rfc822Part; 2293 html[i++] = "\"); return false;'"; 2294 } 2295 html[i++] = "title='" + AjxStringUtil.encodeQuotes(AjxStringUtil.htmlEncode(params.label || params.text)); 2296 html[i++] = "'>" + AjxStringUtil.htmlEncode(params.text) + "</a>"; 2297 2298 return html.join(""); 2299 }; 2300 2301 /** 2302 * Returns the HTML for the link for the attachment name (which usually opens the 2303 * content in a new browser tab). 2304 * 2305 * @param id 2306 */ 2307 ZmMailMsgView.getMainAttachmentLinkHtml = 2308 function(params) { 2309 var params1 = { 2310 id: params.id, 2311 noUnderline: true, 2312 text: params.text, 2313 label: params.att.label 2314 }; 2315 // handle rfc/822 attachments differently 2316 if (params.att.ct == ZmMimeTable.MSG_RFC822) { 2317 params1.jsHref = true; 2318 params1.isRfc822 = true; 2319 params1.mid = params.mid; 2320 params1.rfc822Part = params.rfc822Part; 2321 } 2322 else { 2323 // open non-JavaScript URLs in a blank target 2324 if (params.att.url && params.att.url.indexOf('javascript:') !== 0) { 2325 params1.blankTarget = true; 2326 } 2327 params1.href = params.att.url; 2328 } 2329 return ZmMailMsgView.getAttachmentLinkHtml(params1); 2330 }; 2331 2332 ZmMailMsgView.prototype._getAttachmentLinkId = 2333 function(part, type) { 2334 if (!part) 2335 return; 2336 return [this._attLinksId, part, type].join("_"); 2337 }; 2338 2339 // Adds an onclick handler to the link with the given part and type. I couldn't find an easy 2340 // way to pass and bind a variable number of arguments, so went with three, which is the most 2341 // any of the handlers takes. 2342 ZmMailMsgView.prototype._addClickHandler = 2343 function (part, type, func, obj, arg1, arg2, arg3) { 2344 var id = this._getAttachmentLinkId(part, type); 2345 var link = document.getElementById(id); 2346 if (link) { 2347 link.onclick = func.bind(obj, arg1, arg2, arg3); 2348 } 2349 }; 2350 2351 ZmMailMsgView.prototype._addAllAttachmentsLinks = 2352 function(attachments, viewAllImages, filename) { 2353 2354 var itemId = this._msg.id; 2355 if (AjxUtil.isString(filename)) { 2356 filename = filename.replace(ZmMailMsgView.FILENAME_INV_CHARS_RE, ""); 2357 } else { 2358 filename = null; 2359 } 2360 filename = AjxStringUtil.urlComponentEncode(filename || ZmMsg.downloadAllDefaultFileName); 2361 var url = [appCtxt.get(ZmSetting.CSFE_MSG_FETCHER_URI), "&id=", itemId, "&filename=", filename,"&charset=", appCtxt.getCharset(), "&part="].join(""); 2362 var parts = []; 2363 for (var j = 0; j < attachments.length; j++) { 2364 parts.push(attachments[j].part); 2365 } 2366 var partsStr = parts.join(","); 2367 var params = { 2368 url: (url + partsStr), 2369 downloadAllLinkId: this._viewId + "_downloadAll", 2370 removeAllLinkId: this._viewId + "_removeAll" 2371 } 2372 if (viewAllImages) { 2373 params.viewAllUrl = "/h/viewimages?id=" + itemId; 2374 } 2375 params.html = AjxTemplate.expand("mail.Message#AllAttachments", params); 2376 2377 params.downloadAllCallback = ZmZimbraMail.unloadHackCallback.bind(null); 2378 params.removeAllCallback = this.removeAttachmentCallback.bind(this, partsStr); 2379 return params; 2380 }; 2381 2382 ZmMailMsgView.prototype.getToolTipContent = 2383 function(evt) { 2384 2385 var tgt = DwtUiEvent.getTarget(evt, false); 2386 2387 //see if this is the priority icon. If so, it has a "priority" attribute high/low. 2388 if (tgt.id == ZmId.getViewId(this._view, ZmId.MV_PRIORITY)) { 2389 return tgt.getAttribute('priority') =='high' ? ZmMsg.highPriorityTooltip : ZmMsg.lowPriorityTooltip; 2390 } 2391 2392 if (!this._attachmentLinkIdToFileNameMap) {return null}; 2393 2394 if (tgt && tgt.nodeName.toLowerCase() == "a") { 2395 var id = tgt.getAttribute("id"); 2396 if (id) { 2397 var fileName = this._attachmentLinkIdToFileNameMap[id]; 2398 if (fileName) { 2399 return AjxStringUtil.htmlEncode(fileName); 2400 } 2401 } 2402 } 2403 return null; 2404 }; 2405 2406 // AttachmentLink Handlers 2407 ZmMailMsgView.prototype.addAttachmentLinkHandler = 2408 function(contentType,handlerId,handlerFunc){ 2409 if (!ZmMailMsgView._attachmentHandlers) { 2410 ZmMailMsgView._attachmentHandlers = {}; 2411 } 2412 2413 if (!ZmMailMsgView._attachmentHandlers[contentType]) { 2414 ZmMailMsgView._attachmentHandlers[contentType] = {}; 2415 } 2416 2417 ZmMailMsgView._attachmentHandlers[contentType][handlerId] = handlerFunc; 2418 }; 2419 2420 // Listeners 2421 2422 ZmMailMsgView.prototype._controlEventListener = 2423 function(ev) { 2424 // note - we may get here before we have a chance to initialize the IFRAME 2425 this._resetIframeHeightOnTimer(); 2426 if (this._inviteMsgView && this._inviteMsgView.isActive()) { 2427 this._inviteMsgView.resize(); 2428 } 2429 }; 2430 2431 ZmMailMsgView.prototype._shareToolBarListener = 2432 function(ev) { 2433 ev._buttonId = ev.item.getData(ZmOperation.KEY_ID); 2434 ev._share = this._msg.share; 2435 this.notifyListeners(ZmMailMsgView.SHARE_EVENT, ev); 2436 }; 2437 2438 ZmMailMsgView.prototype._subscribeToolBarListener = 2439 function(req, ev) { 2440 ev._buttonId = ev.item.getData(ZmOperation.KEY_ID); 2441 ev._subscribeReq = req; 2442 this.notifyListeners(ZmMailMsgView.SUBSCRIBE_EVENT, ev); 2443 }; 2444 2445 2446 ZmMailMsgView.prototype._msgChangeListener = 2447 function(ev) { 2448 if (ev.type != ZmEvent.S_MSG) { return; } 2449 if (ev.event == ZmEvent.E_DELETE || ev.event == ZmEvent.E_MOVE) { 2450 if (ev.source == this._msg && (appCtxt.getCurrentViewId() == this._viewId)) { 2451 this._controller._app.popView(); 2452 } 2453 } else if (ev.event == ZmEvent.E_TAGS || ev.event == ZmEvent.E_REMOVE_ALL) { 2454 this._setTags(this._msg); 2455 } else if (ev.event == ZmEvent.E_MODIFY) { 2456 if (ev.source == this._msg) { 2457 this.set(ev.source, true); 2458 } 2459 } 2460 }; 2461 2462 ZmMailMsgView.prototype._selectStartListener = 2463 function(ev) { 2464 // reset mouse event to propagate event to browser (allows text selection) 2465 ev._stopPropagation = false; 2466 ev._returnValue = true; 2467 }; 2468 2469 2470 ZmMailMsgView.prototype._reportButtonListener = 2471 function(msg, ev) { 2472 var proxy = AjxUtil.createProxy(msg); 2473 2474 proxy.clearAddresses(); 2475 var toAddress = new AjxEmailAddress(appCtxt.get(ZmSetting.OFFLINE_REPORT_EMAIL)); 2476 proxy._addrs[AjxEmailAddress.TO] = AjxVector.fromArray([toAddress]); 2477 2478 var bp = msg.getBodyPart(); 2479 if (bp) { 2480 var top = new ZmMimePart(); 2481 top.setContentType(bp.ct); 2482 top.setContent(msg.getBodyPart().getContent()); 2483 proxy.setTopPart(top); 2484 } 2485 2486 var respCallback = this._sendReportCallback.bind(this, msg); 2487 var errorCallback = this._sendReportError.bind(this); 2488 proxy.send(false, respCallback, errorCallback, null, true); 2489 }; 2490 2491 ZmMailMsgView.prototype._sendReportCallback = 2492 function(msg) { 2493 this._controller._doDelete([msg], true); 2494 }; 2495 2496 ZmMailMsgView.prototype._sendReportError = 2497 function() { 2498 appCtxt.setStatusMsg(ZmMsg.reportSyncError, ZmStatusView.LEVEL_WARNING); 2499 }; 2500 2501 2502 // Callbacks 2503 2504 2505 ZmMailMsgView.prototype._handleMsgTruncated = 2506 function() { 2507 2508 // redo selection to trigger loading and display of entire msg 2509 this._msg.viewEntireMessage = true; // remember so we reply to entire msg 2510 this._msg.force = true; // make sure view re-renders msg 2511 if (this._controller._setSelectedItem) { 2512 // list controller 2513 this._controller._setSelectedItem({noTruncate: true, forceLoad: true, markRead: false}); 2514 } 2515 else if (this._controller.show) { 2516 // msg controller 2517 this._controller.show(this._msg, this._controller, null, false, false, true, true); 2518 } 2519 2520 Dwt.setVisible(this._msgTruncatedId, false); 2521 }; 2522 2523 // Static methods 2524 2525 ZmMailMsgView._swapIdAndSrc = 2526 function (image, i, len, msg, parent, view) { 2527 // Fix for IE: Over HTTPS, http src urls for images might cause an issue. 2528 try { 2529 image.src = image.getAttribute("dfsrc"); 2530 } 2531 catch (ex) { 2532 // do nothing 2533 } 2534 2535 if (i == len - 1) { 2536 if (msg) { 2537 msg.setHtmlContent(parent.innerHTML); 2538 } 2539 view._resetIframeHeightOnTimer(); 2540 } 2541 }; 2542 2543 ZmMailMsgView.prototype._onloadIframe = 2544 function(dwtIframe) { 2545 var iframe = dwtIframe.getIframe(); 2546 try { iframe.onload = null; } catch(ex) {} 2547 ZmMailMsgView._resetIframeHeight(this); 2548 }; 2549 2550 ZmMailMsgView._resetIframeHeight = 2551 function(self, attempt) { 2552 2553 var iframe = self.getIframe(); 2554 if (!iframe) { return; } 2555 2556 DBG.println("cv2", "ZmMailMsgView::_resetIframeHeight " + (attempt || "0")); 2557 var h; 2558 if (self._scrollWithIframe) { 2559 h = self.getH(); 2560 function subtract(el) { 2561 if (el) { 2562 if (typeof el == "string") { 2563 el = document.getElementById(el); 2564 } 2565 if (el) { 2566 h -= Dwt.getSize(el).y; 2567 } 2568 } 2569 } 2570 subtract(self._headerElement); 2571 subtract(self._displayImagesId); 2572 subtract(self._highlightObjectsId); 2573 if (self._isMsgTruncated) { 2574 subtract(self._msgTruncatedId); 2575 } 2576 if (self._inviteMsgView && self._inviteMsgView.isActive()) { 2577 if (self._inviteMsgView._inviteToolbar) {//if toolbar not created there's nothing to subtract (e.g. sent folder) 2578 subtract(self._inviteMsgView.getInviteToolbar().getHtmlElement()); 2579 } 2580 if (self._inviteMsgView._dayView) { 2581 subtract(self._inviteMsgView._dayView.getHtmlElement()); 2582 } 2583 } 2584 if (self._hasShareToolbar && self._shareToolbar) { 2585 subtract(self._shareToolbar.getHtmlElement()); 2586 } 2587 iframe.style.height = h + "px"; 2588 } else { 2589 if (attempt == null) { attempt = 0; } 2590 try { 2591 if (!iframe.contentWindow || !iframe.contentWindow.document) { 2592 if (attempt < ZmMailMsgView.SETHEIGHT_MAX_TRIES) { 2593 attempt++; 2594 self._resetIframeHeightOnTimer(attempt); 2595 } 2596 return; // give up 2597 } 2598 } catch(ex) { 2599 if (attempt < ZmMailMsgView.SETHEIGHT_MAX_TRIES) { 2600 attempt++; 2601 self._resetIframeHeightOnTimer(attempt++); // for IE 2602 } 2603 return; // give up 2604 } 2605 2606 var doc = iframe.contentWindow.document; 2607 var origHeight = doc && doc.body && doc.body.scrollHeight || 0; 2608 2609 // first off, make it wide enough to fill ZmMailMsgView. 2610 iframe.style.width = "100%"; // *** changes height! 2611 2612 // remember the current width 2613 var view_width = iframe.offsetWidth; 2614 2615 // if there's a long unbreakable string, the scrollWidth of the body 2616 // element will be bigger--we must make the iframe that wide, or there 2617 // won't be any scrollbars. 2618 var w = doc.body.scrollWidth; 2619 if (w > view_width) { 2620 iframe.style.width = w + "px"; // *** changes height! 2621 2622 // Now (bug 20743), by forcing the body a determined width (that of 2623 // the view) we are making the browser wrap those paragraphs that 2624 // can be wrapped, even if there's a long unbreakable string in the message. 2625 doc.body.style.overflow = "visible"; 2626 if (view_width > 20) { 2627 doc.body.style.width = view_width - 20 + "px"; // *** changes height! 2628 } 2629 } 2630 2631 // we are finally in the right position to determine height. 2632 h = Math.max(doc.documentElement.scrollHeight, origHeight); 2633 2634 iframe.style.height = h + "px"; 2635 2636 if (AjxEnv.isWebKitBased) { 2637 // bug: 39434, WebKit specific 2638 // After the iframe ht is set there is change is body.scrollHeight, weird. 2639 // So reset ht to make the entire body visible. 2640 var newHt = Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight); 2641 if (newHt > h) { 2642 iframe.style.height = newHt + "px"; 2643 } 2644 } 2645 } 2646 }; 2647 2648 // note that IE doesn't seem to be able to reset the "scrolling" attribute. 2649 // this function isn't safe to call for IE! 2650 ZmMailMsgView.prototype.setScrollWithIframe = 2651 function(val) { 2652 2653 if (!this._usingIframe) { return; } 2654 2655 this._scrollWithIframe = val; 2656 this._limitAttachments = this._scrollWithIframe ? 3 : 0; //making it local 2657 this._attcMaxSize = this._limitAttachments * 16 + 8; 2658 2659 this.setScrollStyle(val ? DwtControl.CLIP : DwtControl.SCROLL); 2660 var iframe = this.getIframe(); 2661 if (iframe) { 2662 iframe.style.width = "100%"; 2663 iframe.scrolling = val; 2664 ZmMailMsgView._resetIframeHeight(this); 2665 } 2666 }; 2667 2668 2669 2670 2671 ZmMailMsgView._detachCallback = 2672 function(isRfc822, parentController, result) { 2673 var msgNode = result.getResponse().GetMsgResponse.m[0]; 2674 var ac = window.parentAppCtxt || window.appCtxt; 2675 var ctlr = ac.getApp(ZmApp.MAIL).getMailListController(); 2676 var msg = ZmMailMsg.createFromDom(msgNode, {list: ctlr.getList()}, true); 2677 msg._loaded = true; // bug fix #8868 - force load for rfc822 msgs since they may not return any content 2678 msg.readReceiptRequested = false; // bug #36247 - never allow read receipt for rfc/822 message 2679 ZmMailMsgView.detachMsgInNewWindow(msg, isRfc822, parentController); 2680 }; 2681 2682 ZmMailMsgView.detachMsgInNewWindow = 2683 function(msg, isRfc822, parentController) { 2684 var appCtxt = window.parentAppCtxt || window.appCtxt; 2685 var newWinObj = appCtxt.getNewWindow(true); 2686 if(newWinObj) {// null check for popup blocker 2687 newWinObj.command = "msgViewDetach"; 2688 newWinObj.params = { msg:msg, isRfc822:isRfc822, parentController:parentController }; 2689 } 2690 }; 2691 2692 // loads a msg and displays it in a new window 2693 ZmMailMsgView.rfc822Callback = 2694 function(msgId, msgPartId, parentController) { 2695 var isRfc822 = Boolean((msgPartId != null)); 2696 var appCtxt = window.parentAppCtxt || window.appCtxt; 2697 var params = { 2698 sender: appCtxt.getAppController(), 2699 msgId: msgId, 2700 partId: msgPartId, 2701 getHtml: appCtxt.get(ZmSetting.VIEW_AS_HTML), 2702 markRead: appCtxt.isExternalAccount() ? false : true, 2703 callback: ZmMailMsgView._detachCallback.bind(null, isRfc822, parentController) 2704 }; 2705 ZmMailMsg.fetchMsg(params); 2706 }; 2707 2708 ZmMailMsgView.vcardCallback = 2709 function(msgId, partId) { 2710 ZmZimbraMail.unloadHackCallback(); 2711 2712 var ac = window.parentAppCtxt || window.appCtxt; 2713 ac.getApp(ZmApp.CONTACTS).createFromVCard(msgId, partId); 2714 }; 2715 2716 ZmMailMsgView.downloadCallback = 2717 function(downloadUrl) { 2718 ZmZimbraMail.unloadHackCallback(); 2719 location.href = downloadUrl; 2720 }; 2721 2722 ZmMailMsgView.prototype.removeAttachmentCallback = 2723 function(partIds) { 2724 ZmZimbraMail.unloadHackCallback(); 2725 2726 if (!(partIds instanceof Array)) { partIds = partIds.split(","); } 2727 2728 var msg = (partIds.length > 1) 2729 ? ZmMsg.attachmentConfirmRemoveAll 2730 : ZmMsg.attachmentConfirmRemove; 2731 2732 var dlg = appCtxt.getYesNoMsgDialog(); 2733 dlg.registerCallback(DwtDialog.YES_BUTTON, this._removeAttachmentCallback, this, [partIds]); 2734 dlg.setMessage(msg, DwtMessageDialog.WARNING_STYLE); 2735 dlg.popup(); 2736 }; 2737 2738 ZmMailMsgView.prototype._removeAttachmentCallback = 2739 function(partIds) { 2740 appCtxt.getYesNoMsgDialog().popdown(); 2741 this._msg.removeAttachments(partIds, this._handleRemoveAttachment.bind(this)); 2742 }; 2743 2744 ZmMailMsgView.prototype._handleRemoveAttachment = 2745 function(result) { 2746 var msgNode = result.getResponse().RemoveAttachmentsResponse.m[0]; 2747 var ac = window.parentAppCtxt || window.appCtxt; 2748 var listCtlr = ac.getApp(ZmApp.MAIL).getMailListController(); //todo - getting a list controller from appCtxt always seems suspicious to me (should we get the controller for the current view?) 2749 var msg = ZmMailMsg.createFromDom(msgNode, {list: listCtlr.getList()}, true); 2750 this._msg = this._item = null; 2751 // cache this actioned ID so we can reset selection to it once the CREATE 2752 // notifications have been processed. 2753 listCtlr.actionedMsgId = msgNode.id; 2754 if (this._controller.setMsg) { 2755 //for the ZmMsgController case. (standalone). 2756 this._controller.setMsg(msg); 2757 } 2758 this.set(msg); 2759 }; 2760 2761 ZmMailMsgView.briefcaseCallback = 2762 function(msgId, partId, name) { 2763 ZmZimbraMail.unloadHackCallback(); 2764 2765 // force create deferred folders if not created 2766 AjxDispatcher.require("BriefcaseCore"); 2767 var aCtxt = appCtxt.isChildWindow ? parentAppCtxt : appCtxt; 2768 var briefcaseApp = aCtxt.getApp(ZmApp.BRIEFCASE); 2769 briefcaseApp._createDeferredFolders(); 2770 2771 appCtxt.getApp(ZmApp.BRIEFCASE).createFromAttachment(msgId, partId, name); 2772 }; 2773 2774 ZmMailMsgView.prototype.deactivate = 2775 function() { 2776 this._controller.inactive = true; 2777 }; 2778 2779 ZmMailMsgView.addToCalendarCallback = 2780 function(msgId, partId, name) { 2781 ZmZimbraMail.unloadHackCallback(); 2782 2783 // force create deferred folders if not created 2784 AjxDispatcher.require(["MailCore", "CalendarCore"]); 2785 var aCtxt = appCtxt.isChildWindow ? parentAppCtxt : appCtxt; 2786 var calApp = aCtxt.getApp(ZmApp.CALENDAR); 2787 calApp._createDeferredFolders(); 2788 2789 appCtxt.getApp(ZmApp.CALENDAR).importAppointment(msgId, partId, name); 2790 }; 2791 2792 ZmMailMsgView.prototype.getMsgBodyElement = 2793 function(){ 2794 return document.getElementById(this._msgBodyDivId); 2795 }; 2796 2797 ZmMailMsgView.prototype._getViewId = 2798 function() { 2799 var ctlrViewId = this._controller.getCurrentViewId(); 2800 return this._controller.isZmMsgController ? ctlrViewId : [ctlrViewId, ZmId.VIEW_MSG].join("_"); 2801 }; 2802 2803 ZmMailMsgView.prototype._keepReading = 2804 function(check) { 2805 var cont = this.getHtmlElement(); 2806 var contHeight = Dwt.getSize(cont).y; 2807 var canScroll = (cont.scrollHeight > contHeight && (cont.scrollTop + contHeight < cont.scrollHeight)); 2808 if (canScroll) { 2809 if (!check) { 2810 cont.scrollTop = cont.scrollTop + contHeight; 2811 } 2812 return true; 2813 } 2814 return false; 2815 }; 2816 2817 ZmMailMsgView.prototype._getIframeTitle = function() { 2818 return AjxMessageFormat.format(ZmMsg.messageTitle, this._msg.subject); 2819 }; 2820