/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998-1999 Netscape Communications Corporation. All * Rights Reserved. */ /* This is where functions related to displaying the headers for a selected message in the message pane live. */ //////////////////////////////////////////////////////////////////////////////////// // Warning: if you go to modify any of these JS routines please get a code review from either // hangas@netscape.com or mscott@netscape.com. It's critical that the code in here for displaying // the message headers for a selected message remain as fast as possible. In particular, // right now, we only introduce one reflow per message. i.e. if you click on a message in the thread // pane, we batch up all the changes for displaying the header pane (to, cc, attachements button, etc.) // and we make a single pass to display them. It's critical that we maintain this one reflow per message // view in the message header pane. // //////////////////////////////////////////////////////////////////////////////////// var msgHeaderParserProgID = "component://netscape/messenger/headerparser"; var abAddressCollectorProgID = "component://netscape/addressbook/services/addressCollecter"; var msgPaneData; var currentHeaderData = {}; // gGeneratedViewAllHeaderInfo --> we clear this every time we start to display a new message. // the view all header popup will set it when we first generate a view of all the headers. this is // just so it won't try to regenerate all the information every time the user clicks on the popup. var gGeneratedViewAllHeaderInfo = false; var gViewAllHeaders = false; var gNumAddressesToShow = 3; var gShowUserAgent = false; // attachments array var attachmentUrlArray = new Array(); var attachmentDisplayNameArray = new Array(); var attachmentMessageUriArray = new Array(); var msgHeaderParser = Components.classes[msgHeaderParserProgID].getService(Components.interfaces.nsIMsgHeaderParser); var abAddressCollector = Components.classes[abAddressCollectorProgID].getService(Components.interfaces.nsIAbAddressCollecter); function OnLoadMsgHeaderPane() { // build a document object for the header pane so we can cache fetches for elements // in a local variable // if you add more document elements with ids to msgHeaderPane.xul, you should add // to this object... msgPaneData = new Object; if (msgPaneData) { // First group of headers msgPaneData.SubjectBox = document.getElementById("SubjectBox"); msgPaneData.SubjectValue = document.getElementById("SubjectValue"); msgPaneData.FromBox = document.getElementById("FromBox"); msgPaneData.FromValue = document.getElementById("FromValue"); msgPaneData.DateBox = document.getElementById("DateBox"); msgPaneData.DateValue = document.getElementById("DateValue"); // Attachment related elements msgPaneData.AttachmentPopup = document.getElementById("attachmentPopup"); msgPaneData.AttachmentButton = document.getElementById("attachmentButton"); // Second toolbar msgPaneData.ToBox = document.getElementById("ToBox"); // ToValueShort is the div which shows a shortened number of addresses // on the to line...ToValueLong is a div which shows all of the // addresses on the to line. The same rule applies for ccValueShort/Long msgPaneData.ToValueShort = document.getElementById("ToValueShort"); msgPaneData.ToValueLong = document.getElementById("ToValueLong"); msgPaneData.ToValueToggleIcon = document.getElementById("ToValueToggleIcon"); msgPaneData.CcBox = document.getElementById("CcBox"); msgPaneData.CcValueShort = document.getElementById("CcValueShort"); msgPaneData.CcValueLong = document.getElementById("CcValueLong"); msgPaneData.CcValueToggleIcon = document.getElementById("CcValueToggleIcon"); msgPaneData.NewsgroupBox = document.getElementById("NewsgroupBox"); msgPaneData.NewsgroupValue = document.getElementById("NewsgroupValue"); msgPaneData.UserAgentBox = document.getElementById("UserAgentBox"); msgPaneData.UserAgentValue = document.getElementById("UserAgentValue"); msgPaneData.ViewAllHeadersToolbar = document.getElementById("viewAllHeadersToolbar"); msgPaneData.ViewAllHeadersBox = document.getElementById("viewAllHeadersBox"); } // load any preferences that at are global with regards to // displaying a message... gNumAddressesToShow = pref.GetIntPref("mailnews.max_header_display_length"); gShowUserAgent = pref.GetBoolPref("mailnews.headers.showUserAgent"); } // The messageHeaderSink is the class that gets notified of a message's headers as we display the message // through our mime converter. var messageHeaderSink = { onStartHeaders: function() { // every time we start to redisplay a message, check the view all headers pref.... var showAllHeadersPref = pref.GetIntPref("mail.show_headers"); if (showAllHeadersPref == 2) gViewAllHeaders = true; else gViewAllHeaders = false; ClearCurrentHeaders(); gGeneratedViewAllHeaderInfo = false; ClearAttachmentMenu(); }, onEndHeaders: function() { // WARNING: This is the ONLY routine inside of the message Header Sink that should // trigger a reflow! if (this.NotifyClearAddresses != undefined) NotifyClearAddresses(); // (1) clear out the email fields for to, from, cc.... ClearEmailField(msgPaneData.FromValue); ClearEmailFieldWithButton(msgPaneData.ToValueShort); ClearEmailFieldWithButton(msgPaneData.CcValueShort); ClearEmailFieldWithButton(msgPaneData.ToValueLong); ClearEmailFieldWithButton(msgPaneData.CcValueLong); // be sure to re-hide the toggle button, we'll re-enable it later if we need it... msgPaneData.ToValueToggleIcon.setAttribute('collapsed', 'true'); msgPaneData.CcValueToggleIcon.setAttribute('collapsed', 'true'); //hdrViewSetVisible(msgPaneData.ToValueToggleIcon, false); //hdrViewSetVisible(msgPaneData.CcValueToggleIcon, false); ShowMessageHeaderPane(); UpdateMessageHeaders(); }, handleHeader: function(headerName, headerValue) { // WARNING: if you are modifying this function, make sure you do not do *ANY* // dom manipulations which would trigger a reflow. This method gets called // for every rfc822 header in the message being displayed. If you introduce a reflow // you'll be introducing a reflow for every time we are told about a header. And msgs have // a lot of headers. Don't do it =)....Move your code into OnEndHeaders which is only called // once per message view. // for consistancy sake, let's force all header names to be lower case so // we don't have to worry about looking for: Cc and CC, etc. lowerCaseHeaderName = headerName.toLowerCase(); var foo = new Object; foo.headerValue = headerValue; foo.headerName = headerName; currentHeaderData[lowerCaseHeaderName] = foo; if (lowerCaseHeaderName == "from") { var collectIncoming = pref.GetBoolPref("mail.collect_email_address_incoming"); if (collectIncoming && headerValue && abAddressCollector) abAddressCollector.collectUnicodeAddress(headerValue); } }, handleAttachment: function(contentType, url, displayName, uri, notDownloaded) { // be sure to escape the display name before we insert it into the // method var commandString = "OpenAttachURL('" + contentType + "', '" + url + "', '" + escape(displayName) + "', '" + uri + "')"; if (notDownloaded) { displayName += " " + Bundle.GetStringFromName("notDownloaded"); } AddAttachmentToMenu(displayName, commandString); var count = attachmentUrlArray.length; // dump ("** attachment count**" + count + "\n"); if (count < 0) count = 0; attachmentUrlArray[count] = url; attachmentDisplayNameArray[count] = escape(displayName); attachmentMessageUriArray[count] = uri; }, onEndAllAttachments: function() { AddSaveAllAttachmentsMenu(); } }; function AddNodeToAddressBook (emailAddressNode) { if (emailAddressNode) { var primaryEmail = emailAddressNode.getAttribute("emailAddress"); var displayName = emailAddressNode.getAttribute("displayName"); window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul", "", "chrome,resizeable=no", {primaryEmail:primaryEmail, displayName:displayName }); } } // SendMailToNode takes the email address title button, extracts // the email address we stored in there and opens a compose window // with that address function SendMailToNode(emailAddressNode) { if (emailAddressNode) { var emailAddress = emailAddressNode.getAttribute("emailAddress"); if (emailAddress) messenger.OpenURL("mailto:" + emailAddress ); } } function OpenAttachURL(contentType, url, displayName, messageUri) { var args = {dspname: displayName, opval: 0}; window.openDialog("chrome://messenger/content/openSaveAttachment.xul", "openSaveAttachment", "chrome,modal", args); if (args.opval == 1) messenger.openAttachment(contentType, url, displayName, messageUri); else if (args.opval == 2) messenger.saveAttachment(url, displayName, messageUri); } function AddAttachmentToMenu(name, oncommand) { var popup = document.getElementById("attachmentPopup"); if (popup) { var item = document.createElement('menuitem'); if ( item ) { popup.appendChild(item); item.setAttribute('value', name); item.setAttribute('oncommand', oncommand); } var button = document.getElementById("attachmentButton"); if (button) button.setAttribute("value", popup.childNodes.length); } var attachBox = document.getElementById("attachmentBox"); if (attachBox) attachBox.removeAttribute("collapsed"); } function SaveAllAttachments() { try { messenger.saveAllAttachments(attachmentUrlArray.length, attachmentUrlArray, attachmentDisplayNameArray, attachmentMessageUriArray); } catch (ex) { dump ("** failed to save all attachments ** \n"); } } function AddSaveAllAttachmentsMenu() { var popup = document.getElementById("attachmentPopup"); if (popup && popup.childNodes.length > 1) { var separator = document.createElement('menuseparator'); var item = document.createElement('menuitem'); if (separator && item) { popup.appendChild(separator); popup.appendChild(item); item.setAttribute('value', "Save All..."); item.setAttribute('oncommand', "SaveAllAttachments()"); } } } function ClearAttachmentMenu() { var popup = document.getElementById("attachmentPopup"); if ( popup ) { while ( popup.childNodes.length ) popup.removeChild(popup.childNodes[0]); } var attachBox = document.getElementById("attachmentBox"); if (attachBox) attachBox.setAttribute("collapsed", "true"); // reset attachments name array attachmentUrlArray.length = 0; attachmentDisplayNameArray.length = 0; attachmentMessageUriArray.length = 0; } // Assumes that all the child nodes of the parent div need removed..leaving // an empty parent div...This is used to clear the To/From/cc lines where // we had to insert email addresses one at a time into their parent divs... function ClearEmailFieldWithButton(parentDiv) { if (parentDiv) { // the toggle button is the last child in the child nodes.. // we should never remove it... while (parentDiv.childNodes.length > 1) parentDiv.removeChild(parentDiv.childNodes[0]); } } // Clear Email Field takes the passed in div and removes all the child nodes! // if your div has a button in it (like To/cc use ClearEmailfieldWithButton function ClearEmailField(parentDiv) { if (parentDiv) { while (parentDiv.childNodes.length > 0) parentDiv.removeChild(parentDiv.childNodes[0]); } } // OutputEmailAddresses --> knows how to take a comma separated list of email addresses, // extracts them one by one, linkifying each email address into a mailto url. // Then we add the link'ified email address to the parentDiv passed in. // // defaultParentDiv --> the div to add the link-ified email addresses into. // emailAddresses --> comma separated list of the addresses for this header field // includeShortLongToggle --> true if you want to include the ability to toggle between short/long // address views for this header field. If true, then pass in a another div which is the div the long // view will be added too... function OutputEmailAddresses(parentBox, defaultParentDiv, emailAddresses, includeShortLongToggle, optionalLongDiv, optionalToggleButton) { // if we don't have any addresses for this field, hide the parent box! if ( !emailAddresses ) { hdrViewSetVisible(parentBox, false); return; } if (msgHeaderParser) { var enumerator = msgHeaderParser.ParseHeadersWithEnumerator(emailAddresses); enumerator = enumerator.QueryInterface(Components.interfaces.nsISimpleEnumerator); var numAddressesParsed = 0; if (enumerator) { var emailAddress = {}; var name = {}; while (enumerator.hasMoreElements()) { var headerResult = enumerator.getNext(); headerResult = enumerator.QueryInterface(Components.interfaces.nsIMsgHeaderParserResult); // get the email and name fields var addrValue = {}; var nameValue = {}; fullAddress = headerResult.getAddressAndName(addrValue, nameValue); emailAddress = addrValue.value; name = nameValue.value; // if we want to include short/long toggle views and we have a long view, always add it. // if we aren't including a short/long view OR if we are and we haven't parsed enough // addresses to reach the cutoff valve yet then add it to the default (short) div. if (includeShortLongToggle && optionalLongDiv) { InsertEmailAddressUnderEnclosingBox(parentBox, optionalLongDiv, emailAddress, fullAddress, name); } if (!includeShortLongToggle || (numAddressesParsed < gNumAddressesToShow)) { InsertEmailAddressUnderEnclosingBox(parentBox, defaultParentDiv, emailAddress, fullAddress, name); } numAddressesParsed++; } } // if enumerator if (includeShortLongToggle && (numAddressesParsed > gNumAddressesToShow) && optionalToggleButton) { optionalToggleButton.removeAttribute('collapsed'); } } // if msgheader parser } /* InsertEmailAddressUnderEnclosingBox --> right now all email addresses are borderless titled buttons with formatting to make them look like html anchors. When you click on the button, you are prompted with a popup asking you what you want to do with the email address parentBox --> the enclosing box for all the email addresses for this header. This is needed to control visibility of the header. parentDiv --> the DIV the email addresses need to be inserted into. */ function InsertEmailAddressUnderEnclosingBox(parentBox, parentDiv, emailAddress, fullAddress, displayName) { if ( parentBox ) { var item = document.createElement("mail-emailaddress"); var itemInDocument; if ( item && parentDiv) { if (parentDiv.childNodes.length) { var child = parentDiv.childNodes[parentDiv.childNodes.length - 1]; if (parentDiv.childNodes.length > 1) { var textNode = document.createElement("text"); textNode.setAttribute("value", ", "); textNode.setAttribute("class", "emailSeparator"); parentDiv.insertBefore(textNode, child); } itemInDocument = parentDiv.insertBefore(item, child); } else { itemInDocument = parentDiv.appendChild(item); } itemInDocument.setAttribute("value", fullAddress); itemInDocument.setTextAttribute("emailAddress", emailAddress); itemInDocument.setTextAttribute("fullAddress", fullAddress); itemInDocument.setTextAttribute("displayName", displayName); if (this.AddExtraAddressProcessing != undefined) AddExtraAddressProcessing(emailAddress, itemInDocument); hdrViewSetVisible(parentBox, true); } } } function UpdateMessageHeaders() { if (gViewAllHeaders) { fillBoxWithAllHeaders(msgPaneData.ViewAllHeadersBox, false); return; } if (currentHeaderData["subject"]) hdrViewSetNodeWithBox(msgPaneData.SubjectBox, msgPaneData.SubjectValue, currentHeaderData["subject"].headerValue); else hdrViewSetNodeWithBox(msgPaneData.SubjectBox, msgPaneData.SubjectValue, ""); if (currentHeaderData["from"]) OutputEmailAddresses(msgPaneData.FromBox, msgPaneData.FromValue, currentHeaderData["from"].headerValue, false, "", ""); else OutputEmailAddresses(msgPaneData.FromBox, msgPaneData.FromValue, "", false, "", ""); if (currentHeaderData["date"]) hdrViewSetNodeWithBox(msgPaneData.DateBox, msgPaneData.DateValue, currentHeaderData["date"].headerValue); else hdrViewSetNodeWithBox(msgPaneData.DateBox, msgPaneData.DateValue, ""); if (currentHeaderData["to"]) OutputEmailAddresses(msgPaneData.ToBox, msgPaneData.ToValueShort, currentHeaderData["to"].headerValue, true, msgPaneData.ToValueLong, msgPaneData.ToValueToggleIcon ); else OutputEmailAddresses(msgPaneData.ToBox, msgPaneData.ToValueShort, "", true, msgPaneData.ToValueLong, msgPaneData.ToValueToggleIcon ); if (currentHeaderData["cc"]) OutputEmailAddresses(msgPaneData.CcBox, msgPaneData.CcValueShort, currentHeaderData["cc"].headerValue, true, msgPaneData.CcValueLong, msgPaneData.CcValueToggleIcon ); else OutputEmailAddresses(msgPaneData.CcBox, msgPaneData.CcValueShort, "", true, msgPaneData.CcValueLong, msgPaneData.CcValueToggleIcon ); if (currentHeaderData["newsgroups"]) hdrViewSetNodeWithBox(msgPaneData.NewsgroupBox, msgPaneData.NewsgroupValue, currentHeaderData["newsgroups"].headerValue); else hdrViewSetNodeWithBox(msgPaneData.NewsgroupBox, msgPaneData.NewsgroupValue, ""); if (gShowUserAgent) { if (currentHeaderData["user-agent"]) hdrViewSetNodeWithBox(msgPaneData.UserAgentBox, msgPaneData.UserAgentValue, currentHeaderData["user-agent"].headerValue); else hdrViewSetNodeWithBox(msgPaneData.UserAgentBox, msgPaneData.UserAgentValue, ""); } if (this.FinishEmailProcessing != undefined) FinishEmailProcessing(); } function ClearCurrentHeaders() { currentHeaderData = {}; } function ShowMessageHeaderPane() { if (gViewAllHeaders) { HideMessageHeaderPane(); msgPaneData.ViewAllHeadersToolbar.removeAttribute("hide"); msgPaneData.ViewAllHeadersBox.removeAttribute("collapsed"); } else { msgPaneData.ViewAllHeadersToolbar.setAttribute("hide", "true"); msgPaneData.ViewAllHeadersBox.setAttribute("collapsed", "true"); var node = document.getElementById("headerPart1"); if (node) node.removeAttribute("hide"); node = document.getElementById("headerPart2"); if (node) node.removeAttribute("hide"); node = document.getElementById("headerPart3"); if (node) node.removeAttribute("hide"); } } function HideMessageHeaderPane() { msgPaneData.ViewAllHeadersToolbar.setAttribute("hide", "true"); msgPaneData.ViewAllHeadersBox.setAttribute("collapsed", "true"); var node = document.getElementById("headerPart1"); if (node) node.setAttribute("hide", "true"); node = document.getElementById("headerPart2"); if (node) node.setAttribute("hide", "true"); node = document.getElementById("headerPart3"); if (node) node.setAttribute("hide", "true"); } // ToggleLongShortAddresses is used to toggle between showing // all of the addresses on a given header line vs. only the first 'n' // where 'n' is a user controlled preference. By toggling on the more/less // images in the header window, we'll hide / show the appropriate div for that header. function ToggleLongShortAddresses(shortDivID, longDivID) { var shortNode = document.getElementById(shortDivID); var longNode = document.getElementById(longDivID); var nodeToReset; // test to see which if short is already hidden... if (shortNode.getAttribute("collapsed") == "true") { hdrViewSetVisible(longNode, false); hdrViewSetVisible(shortNode, true); nodeToReset = shortNode; } else { hdrViewSetVisible(shortNode, false); hdrViewSetVisible(longNode, true); nodeToReset = longNode; } // HACK ALERT: this is required because of a bug in boxes when you add content // to a box which is collapsed, when you then uncollapse that box, the content isn't // displayed. So I'm forcing us to reset the value for each node... var endPos = nodeToReset.childNodes.length; var index = 0; var tempValue; while (index < endPos - 1) { tempValue = nodeToReset.childNodes[index].getAttribute("value"); nodeToReset.childNodes[index].setAttribute("value", ""); nodeToReset.childNodes[index].setAttribute("value", tempValue); index++; } } // given a box, iterate through all of the headers and add them to // the enclosing box. function fillBoxWithAllHeaders(containerBox, boxPartOfPopup) { // clear out the old popup date if there is any... while ( containerBox.childNodes.length ) containerBox.removeChild(containerBox.childNodes[0]); for (header in currentHeaderData) { var innerBox = document.createElement('box'); innerBox.setAttribute("class", "headerBox"); innerBox.setAttribute("align", "horizontal"); if (boxPartOfPopup) innerBox.setAttribute("autostretch", "never"); // for each header, create a header value and header name then assign those values... var newHeaderTitle = document.createElement('text'); newHeaderTitle.setAttribute("class", "headerdisplayname"); newHeaderTitle.setAttribute("value", currentHeaderData[header].headerName + ':'); innerBox.appendChild(newHeaderTitle); var newHeaderValue = document.createElement('html'); // make sure we are properly resized... if (boxPartOfPopup) newHeaderValue.setAttribute("width", window.innerWidth*.65); newHeaderValue.setAttribute("class", "headerValue"); innerBox.appendChild(newHeaderValue); containerBox.appendChild(innerBox); ProcessHeaderValue(innerBox, newHeaderValue, currentHeaderData[header], boxPartOfPopup); } } // the on create handler for the view all headers popup... function fillAllHeadersPopup(node) { // don't bother re-filling the popup if we've already done it for the // currently displayed message.... if (gGeneratedViewAllHeaderInfo == true) return true; var containerBox = document.getElementById('allHeadersPopupContainer'); containerBox.setAttribute("class", "header-part1"); containerBox.setAttribute("align", "vertical"); containerBox.setAttribute("flex", "1"); fillBoxWithAllHeaders(containerBox, true); gGeneratedViewAllHeaderInfo = true; // don't try to regenerate this information for the currently displayed message return true; } // containingBox --> the box containing the header // containerNode --> the div or box that we want to insert this specific header into // header --> an entry from currentHeaderData contains .headerName and .headerValue // boxPartOfPopup --> true if the box is part of a popup (certain functions are disabled in this scenario) function ProcessHeaderValue(containingBox, containerNode, header, boxPartOfPopup) { // in the simplest case, we'll just create a text node for the header // and append it to the container Node. for certain headers, we might want to do // extra processing.... var headerName = header.headerName; headerName = headerName.toLowerCase(); if (!boxPartOfPopup && (headerName == "cc" || headerName == "from" || headerName == "to")) { OutputEmailAddresses(containingBox, containerNode, header.headerValue, "", "", "") return; } var textNode = document.createTextNode(header.headerValue); if (headerName == "subject") { containerNode.setAttribute("class", "subjectvalue headerValue"); } containerNode.appendChild(textNode); } /////////////////////////////////////////////////////////////// // The following are just small helper functions.. /////////////////////////////////////////////////////////////// function hdrViewSetNodeWithButton(boxNode, buttonNode, text) { if (text) { buttonNode.setAttribute("value", text); hdrViewSetVisible(boxNode, true); } else { hdrViewSetVisible(boxNode, false); return false; } } function hdrViewSetNodeWithBox(boxNode, textNode, text) { if ( text ) return hdrViewSetNode(boxNode, textNode, text ); else { hdrViewSetVisible(boxNode, false); return false; } } function hdrViewSetNode(boxNode, textNode, text) { textNode.childNodes[0].nodeValue = text; var visible; if ( text ) visible = true; else visible = false; hdrViewSetVisible(boxNode, visible); return visible; } function hdrViewSetVisible(boxNode, visible) { if ( visible ) boxNode.removeAttribute("collapsed"); else boxNode.setAttribute("collapsed", "true"); }