# -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla 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/MPL/ # # 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 the Initial Developer are Copyright (C) 1998-1999 # the Initial Developer. All Rights Reserved. # # Contributor(s): # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** /* 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 // scott@scott-macgregor.org. 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. //////////////////////////////////////////////////////////////////////////////////// const msgHeaderParserContractID = "@mozilla.org/messenger/headerparser;1"; const abAddressCollectorContractID = "@mozilla.org/addressbook/services/addressCollecter;1"; const kPersonalAddressbookUri = "moz-abmdbdirectory://abook.mab"; const kRDFServiceContractID = "@mozilla.org/rdf/rdf-service;1"; const kLargeIcon = 32; const kSmallIcon = 16; var gViewAllHeaders = false; var gNumAddressesToShow = 3; var gShowOrganization = false; var gShowLargeAttachmentView = false; var gShowUserAgent = false; var gMinNumberOfHeaders = 0; var gDummyHeaderIdIndex = 0; var gCollectIncoming = false; var gCollectOutgoing = false; var gCollectNewsgroup = false; var gCollapsedHeaderViewMode = false; var gSlimAttachmentView = false; var gCollectAddressTimer = null; var gCollectAddress = null; var gBuildAttachmentsForCurrentMsg = false; var gBuildAttachmentPopupForCurrentMsg = true; var gBuiltExpandedView = false; var gBuiltCollapsedView = false; var gOpenLabel; var gOpenLabelAccesskey; var gSaveLabel; var gSaveLabelAccesskey; var gDetachLabel; var gDeleteLabel; var gDetachLabelAccesskey; var gDeleteLabelAccesskey; var gMessengerBundle; var gProfileDirURL; var gIOService; var gFileHandler; var gShowCondensedEmailAddresses = true; // show the friendly display names for people I know instead of the name + email address var gPersonalAddressBookDirectory; // used for determining if we want to show just the display name in email address nodes var msgHeaderParser = Components.classes[msgHeaderParserContractID].getService(Components.interfaces.nsIMsgHeaderParser); var abAddressCollector = null; // other components may listen to on start header & on end header notifications for each message we display // to do that you need to add yourself to our gMessageListeners array with object that has two properties: // onStartHeaders and onEndHeaders. var gMessageListeners = new Array; // For every possible "view" in the message pane, you need to define the header names you want to // see in that view. In addition, include information describing how you want that header field to be // presented. i.e. if it's an email address field, if you want a toggle inserted on the node in case // of multiple email addresses, etc. We'll then use this static table to dynamically generate header view entries // which manipulate the UI. // When you add a header to one of these view lists you can specify the following properties: // name: the name of the header. i.e. "to", "subject". This must be in lower case and the name of the // header is used to help dynamically generate ids for objects in the document. (REQUIRED) // useToggle: true if the values for this header are multiple email addresses and you want a // a toggle icon to show a short vs. long list (DEFAULT: false) // useShortView: (only works on some fields like From). If the field has a long presentation and a // short presentation we'll use the short one. i.e. if you are showing the From field and you // set this to true, we can show just "John Doe" instead of "John Doe ". // (DEFAULT: false) // // outputFunction: this is a method which takes a headerEntry (see the definition below) and a header value // This allows you to provide your own methods for actually determining how the header value // is displayed. (DEFAULT: updateHeaderValue which just sets the header value on the text node) // Our first view is the collapsed view. This is very light weight view of the data. We only show a couple // fields. var gCollapsedHeaderList = [ {name:"subject", outputFunction:updateHeaderValueInTextNode}, {name:"from", useShortView:true, outputFunction:OutputEmailAddresses}, {name:"date", outputFunction:updateHeaderValueInTextNode}]; // We also have an expanded header view. This shows many of your more common (and useful) headers. var gExpandedHeaderList = [ {name:"subject"}, {name:"from", outputFunction:OutputEmailAddresses}, {name:"sender", outputFunction:OutputEmailAddresses}, {name:"reply-to", outputFunction:OutputEmailAddresses}, {name:"date"}, {name:"to", useToggle:true, outputFunction:OutputEmailAddresses}, {name:"cc", useToggle:true, outputFunction:OutputEmailAddresses}, {name:"bcc", useToggle:true, outputFunction:OutputEmailAddresses}, {name:"newsgroups", outputFunction:OutputNewsgroups}, {name:"followup-to", outputFunction:OutputNewsgroups}, {name:"content-base"} ]; // Now, for each view the message pane can generate, we need a global table of headerEntries. These // header entry objects are generated dynamically based on the static date in the header lists (see above) // and elements we find in the DOM based on properties in the header lists. var gCollapsedHeaderView = {}; var gExpandedHeaderView = {}; // currentHeaderData --> this is an array of header name and value pairs for the currently displayed message. // it's purely a data object and has no view information. View information is contained in the view objects. // for a given entry in this array you can ask for: // .headerName ---> name of the header (i.e. 'to'). Always stored in lower case // .headerValue --> value of the header "johndoe@netscape.net" var currentHeaderData = {}; // For the currently displayed message, we store all the attachment data. When displaying a particular // view, it's up to the view layer to extract this attachment data and turn it into something useful. // For a given entry in the attachments list, you can ask for the following properties: // .contentType --> the content type of the attachment // url --> an imap, or mailbox url which can be used to fetch the message // uri --> an RDF URI which refers to the message containig the attachment // isExternalAttachment --> boolean flag stating whether the attachment is an attachment which is a URL that refers to the attachment location var currentAttachments = new Array(); // createHeaderEntry --> our constructor method which creates a header Entry // based on an entry in one of the header lists. A header entry is different from a header list. // a header list just describes how you want a particular header to be presented. The header entry // actually has knowledge about the DOM and the actual DOM elements associated with the header. // prefix --> the name of the view (i.e. "collapsed", "expanded") // headerListInfo --> entry from a header list. function createHeaderEntry(prefix, headerListInfo) { var partialIDName = prefix + headerListInfo.name; this.enclosingBox = document.getElementById(partialIDName + 'Box'); this.textNode = document.getElementById(partialIDName + 'Value'); this.isValid = false; if ("useToggle" in headerListInfo) { this.useToggle = headerListInfo.useToggle; if (this.useToggle) // find the toggle icon in the document { this.toggleIcon = this.enclosingBox.toggleIcon; this.longTextNode = this.enclosingBox.longEmailAddresses; this.textNode = this.enclosingBox.emailAddresses; } } else this.useToggle = false; if ("useShortView" in headerListInfo) { this.useShortView = headerListInfo.useShortView; this.enclosingBox.emailAddressNode = this.textNode; } else this.useShortView = false; if ("outputFunction" in headerListInfo) this.outputFunction = headerListInfo.outputFunction; else this.outputFunction = updateHeaderValue; } function initializeHeaderViewTables() { // iterate over each header in our header list arrays and create header entries // for each one. These header entries are then stored in the appropriate header table var index; for (index = 0; index < gCollapsedHeaderList.length; index++) { gCollapsedHeaderView[gCollapsedHeaderList[index].name] = new createHeaderEntry('collapsed', gCollapsedHeaderList[index]); } for (index = 0; index < gExpandedHeaderList.length; index++) { var headerName = gExpandedHeaderList[index].name; gExpandedHeaderView[headerName] = new createHeaderEntry('expanded', gExpandedHeaderList[index]); } if (gShowOrganization) { var organizationEntry = {name:"organization", outputFunction:updateHeaderValue}; gExpandedHeaderView[organizationEntry.name] = new createHeaderEntry('expanded', organizationEntry); } if (gShowUserAgent) { var userAgentEntry = {name:"user-agent", outputFunction:updateHeaderValue}; gExpandedHeaderView[userAgentEntry.name] = new createHeaderEntry('expanded', userAgentEntry); } } function OnLoadMsgHeaderPane() { // HACK...force our XBL bindings file to be load before we try to create our first xbl widget.... // otherwise we have problems. document.loadBindingDocument('chrome://messenger/content/mailWidgets.xml'); // load any preferences that at are global with regards to // displaying a message... gNumAddressesToShow = pref.getIntPref("mailnews.max_header_display_length"); gCollectIncoming = pref.getBoolPref("mail.collect_email_address_incoming"); gCollectNewsgroup = pref.getBoolPref("mail.collect_email_address_newsgroup"); gCollectOutgoing = pref.getBoolPref("mail.collect_email_address_outgoing"); gShowUserAgent = pref.getBoolPref("mailnews.headers.showUserAgent"); gMinNumberOfHeaders = pref.getIntPref("mailnews.headers.minNumHeaders"); gShowOrganization = pref.getBoolPref("mailnews.headers.showOrganization"); gShowLargeAttachmentView = pref.getBoolPref("mailnews.attachments.display.largeView"); gShowCondensedEmailAddresses = pref.getBoolPref("mail.showCondensedAddresses"); // listen to the pref.addObserver("mail.showCondensedAddresses", MsgHdrViewObserver, false); initializeHeaderViewTables(); var toggleHeaderView = document.getElementById("msgHeaderView"); var initialCollapsedSetting = toggleHeaderView.getAttribute("state"); if (initialCollapsedSetting == "true") gCollapsedHeaderViewMode = true; // dispatch an event letting any listeners know that we have loaded the message pane var event = document.createEvent('Events'); event.initEvent('messagepane-loaded', false, true); var headerViewElement = document.getElementById("msgHeaderView"); headerViewElement.dispatchEvent(event); } function OnUnloadMsgHeaderPane() { pref.removeObserver("mail.showCondensedAddresses", MsgHdrViewObserver); // dispatch an event letting any listeners know that we have unloaded the message pane var event = document.createEvent('Events'); event.initEvent('messagepane-unloaded', false, true); var headerViewElement = document.getElementById("msgHeaderView"); headerViewElement.dispatchEvent(event); } const MsgHdrViewObserver = { observe: function(subject, topic, prefName) { // verify that we're changing the mail pane config pref if (topic == "nsPref:changed") { if (prefName == "mail.showCondensedAddresses") { gShowCondensedEmailAddresses = pref.getBoolPref("mail.showCondensedAddresses"); MsgReload(); } } } }; // 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() { this.mSaveHdr = null; // clear out any pending collected address timers... if (gCollectAddressTimer) { gCollectAddress = ""; clearTimeout(gCollectAddressTimer); gCollectAddressTimer = null; } // 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 { if (gViewAllHeaders) // if we currently are in view all header mode, rebuild our header view so we remove most of the header data { hideHeaderView(gExpandedHeaderView); gExpandedHeaderView = {}; initializeHeaderViewTables(); } gViewAllHeaders = false; } ClearCurrentHeaders(); gBuiltExpandedView = false; gBuiltCollapsedView = false; gBuildAttachmentsForCurrentMsg = false; gBuildAttachmentPopupForCurrentMsg = true; ClearAttachmentList(); ClearEditMessageButton(); gMessageNotificationBar.clearMsgNotifications(); for (index in gMessageListeners) gMessageListeners[index].onStartHeaders(); }, onEndHeaders: function() { // WARNING: This is the ONLY routine inside of the message Header Sink that should // trigger a reflow! CheckNotify(); ClearHeaderView(gCollapsedHeaderView); ClearHeaderView(gExpandedHeaderView); EnsureSubjectValue(); // make sure there is a subject even if it's empty so we'll show the subject and the twisty ShowMessageHeaderPane(); UpdateMessageHeaders(); if (gIsEditableMsgFolder) ShowEditMessageButton(); for (index in gMessageListeners) gMessageListeners[index].onEndHeaders(); }, processHeaders: function(headerNameEnumerator, headerValueEnumerator, dontCollectAddress) { this.onStartHeaders(); var fromMailbox; var index = 0; while (headerNameEnumerator.hasMore()) { var header = new Object; header.headerValue = headerValueEnumerator.getNext(); header.headerName = headerNameEnumerator.getNext(); // 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. var lowerCaseHeaderName = header.headerName.toLowerCase(); // if we have an x-mailer or x-mimeole string, put it in the user-agent slot which we know how to handle // already. if (lowerCaseHeaderName == "x-mailer" || lowerCaseHeaderName == "x-mimeole") lowerCaseHeaderName = "user-agent"; else if (lowerCaseHeaderName == "sender" && msgHeaderParser && header.headerValue && fromMailbox) { if (msgHeaderParser.extractHeaderAddressMailboxes(null, header.headerValue) == fromMailbox) continue; } if (this.mDummyMsgHeader) { if (lowerCaseHeaderName == "from") this.mDummyMsgHeader.author = header.headerValue; else if (lowerCaseHeaderName == "to") this.mDummyMsgHeader.recipients = header.headerValue; else if (lowerCaseHeaderName == "cc") this.mDummyMsgHeader.ccList = header.headerValue; else if (lowerCaseHeaderName == "subject") this.mDummyMsgHeader.subject = header.headerValue; else if (lowerCaseHeaderName == "reply-to") this.mDummyMsgHeader.replyTo = header.headerValue; else if (lowerCaseHeaderName == "message-id") this.mDummyMsgHeader.messageId = header.headerValue; } // according to RFC 2822, certain headers // can occur "unlimited" times if (lowerCaseHeaderName in currentHeaderData) { // sometimes, you can have multiple To or Cc lines.... // in this case, we want to append these headers into one. if (lowerCaseHeaderName == 'to' || lowerCaseHeaderName == 'cc') currentHeaderData[lowerCaseHeaderName].headerValue = currentHeaderData[lowerCaseHeaderName].headerValue + ',' + header.headerValue; else { // use the index to create a unique header name like: // received5, received6, etc currentHeaderData[lowerCaseHeaderName + index++] = header; } } else currentHeaderData[lowerCaseHeaderName] = header; if (lowerCaseHeaderName == "from") { if (msgHeaderParser && header.headerValue) fromMailbox = msgHeaderParser.extractHeaderAddressMailboxes(null, header.headerValue); if (header.headerValue) { try { if (!dontCollectAddress && (gCollectIncoming || gCollectNewsgroup || gCollectOutgoing)) { if (!abAddressCollector) abAddressCollector = Components.classes[abAddressCollectorContractID] .getService(Components.interfaces.nsIAbAddressCollecter); gCollectAddress = header.headerValue; if (gCollectOutgoing) // collect, but only update existing cards, unknown preferred send format gCollectAddressTimer = setTimeout('abAddressCollector.collectUnicodeAddress(gCollectAddress, false, Components.interfaces.nsIAbPreferMailFormat.unknown);', 2000); else // collect, and add card if doesn't exist, unknown preferred send format gCollectAddressTimer = setTimeout('abAddressCollector.collectUnicodeAddress(gCollectAddress, true, Components.interfaces.nsIAbPreferMailFormat.unknown);', 2000); } } catch(ex) {} } } // if lowerCaseHeaderName == "from" } // while we have more headers to parse this.onEndHeaders(); }, handleAttachment: function(contentType, url, displayName, uri, isExternalAttachment) { // presentation level change....don't show vcards as external attachments in the UI. // libmime already renders them inline. try { if (!this.mSaveHdr) this.mSaveHdr = messenger.messageServiceFromURI(uri).messageURIToMsgHdr(uri); } catch (ex) {} if (contentType == "text/x-vcard") { var inlineAttachments = pref.getBoolPref("mail.inline_attachments"); var displayHtmlAs = pref.getIntPref("mailnews.display.html_as"); if (inlineAttachments && !displayHtmlAs) { return; } } // display name optimization. Eliminate any large quantities of white space from the display name. // such that Hello World.txt becomes Hello World.txt. var displayName = displayName.replace(/ +/g, " "); currentAttachments.push (new createNewAttachmentInfo(contentType, url, displayName, uri, isExternalAttachment)); // if we have an attachment, set the MSG_FLAG_ATTACH flag on the hdr // this will cause the "message with attachment" icon to show up // in the thread pane // we only need to do this on the first attachment var numAttachments = currentAttachments.length; if (numAttachments == 1) { // we also have to enable the File/Attachments menuitem var node = document.getElementById("fileAttachmentMenu"); if (node) node.removeAttribute("disabled"); try { // convert the uri into a hdr this.mSaveHdr.markHasAttachments(true); } catch (ex) { dump("ex = " + ex + "\n"); } } }, onEndAllAttachments: function() { displayAttachmentsForExpandedView(); }, onEndMsgDownload: function(url) { // if we don't have any attachments, turn off the attachments flag if (!this.mSaveHdr) { var messageUrl = url.QueryInterface(Components.interfaces.nsIMsgMessageUrl); try { this.mSaveHdr = messenger.msgHdrFromURI(messageUrl.uri); } catch (ex) {} } if (!currentAttachments.length && this.mSaveHdr) this.mSaveHdr.markHasAttachments(false); OnMsgParsed(url); }, onEndMsgHeaders: function(url) { OnMsgLoaded(url); }, onMsgHasRemoteContent: function(aMsgHdr) { gMessageNotificationBar.setRemoteContentMsg(aMsgHdr); }, mSecurityInfo : null, mSaveHdr: null, getSecurityInfo: function() { return this.mSecurityInfo; }, setSecurityInfo: function(aSecurityInfo) { this.mSecurityInfo = aSecurityInfo; }, mDummyMsgHeader: null, getDummyMsgHeader: function() { if (!this.mDummyMsgHeader) this.mDummyMsgHeader = new nsDummyMsgHeader(); return this.mDummyMsgHeader; } }; function EnsureSubjectValue() { if (!('subject' in currentHeaderData)) { var foo = new Object; foo.headerValue = ""; foo.headerName = 'subject'; currentHeaderData[foo.headerName] = foo; } } function CheckNotify() { if ("NotifyClearAddresses" in this) NotifyClearAddresses(); } // flush out any local state being held by a header entry for a given // table function ClearHeaderView(headerTable) { for (index in headerTable) { var headerEntry = headerTable[index]; if (headerEntry.useToggle) { headerEntry.enclosingBox.clearEmailAddresses(); } headerEntry.valid = false; } } // make sure that any valid header entry in the table is collapsed function hideHeaderView(headerTable) { for (index in headerTable) { headerTable[index].enclosingBox.collapsed = true; } } // make sure that any valid header entry in the table specified is // visible function showHeaderView(headerTable) { var headerEntry; for (index in headerTable) { headerEntry = headerTable[index]; if (headerEntry.valid) { headerEntry.enclosingBox.collapsed = false; } else // if the entry is invalid, always make sure it's collapsed headerEntry.enclosingBox.collapsed = true; } } // enumerate through the list of headers and find the number that are visible // add empty entries if we don't have the minimum number of rows function EnsureMinimumNumberOfHeaders (headerTable) { if (!gMinNumberOfHeaders) // 0 means we don't have a minimum..do nothing special return; var numVisibleHeaders = 0; for (index in headerTable) { if (headerTable[index].valid) numVisibleHeaders ++; } if (numVisibleHeaders < gMinNumberOfHeaders) { // how many empty headers do we need to add? var numEmptyHeaders = gMinNumberOfHeaders - numVisibleHeaders; // we may have already dynamically created our empty rows and we just need to make them visible for (index in headerTable) { if (index.indexOf("Dummy-Header") == 0 && numEmptyHeaders) { headerTable[index].valid = true; numEmptyHeaders--; } } // ok, now if we have any extra dummy headers we need to add, create a new header widget for them while (numEmptyHeaders) { var dummyHeaderId = "Dummy-Header" + gDummyHeaderIdIndex; gExpandedHeaderView[dummyHeaderId] = new createNewHeaderView(dummyHeaderId); gExpandedHeaderView[dummyHeaderId].valid = true; gDummyHeaderIdIndex++; numEmptyHeaders--; } } } // make sure the appropriate fields within the currently displayed view header mode // are collapsed or visible... function updateHeaderViews() { if (gCollapsedHeaderViewMode) showHeaderView(gCollapsedHeaderView); else { if (gMinNumberOfHeaders) EnsureMinimumNumberOfHeaders(gExpandedHeaderView); showHeaderView(gExpandedHeaderView); } displayAttachmentsForExpandedView(); } function ToggleHeaderView () { var expandedNode = document.getElementById("expandedHeaderView"); var collapsedNode = document.getElementById("collapsedHeaderView"); var toggleHeaderView = document.getElementById("msgHeaderView"); if (gCollapsedHeaderViewMode) { gCollapsedHeaderViewMode = false; // hide the current view hideHeaderView(gCollapsedHeaderView); // update the current view UpdateMessageHeaders(); // now uncollapse / collapse the right views expandedNode.collapsed = false; collapsedNode.collapsed = true; } else { gCollapsedHeaderViewMode = true; // hide the current view hideHeaderView(gExpandedHeaderView); // update the current view UpdateMessageHeaders(); // now uncollapse / collapse the right views collapsedNode.collapsed = false; expandedNode.collapsed = true; } if (gCollapsedHeaderViewMode) toggleHeaderView.setAttribute("state", "true"); else toggleHeaderView.setAttribute("state", "false"); } // default method for updating a header value into a header entry function updateHeaderValue(headerEntry, headerValue) { headerEntry.enclosingBox.headerValue = headerValue; } function updateHeaderValueInTextNode(headerEntry, headerValue) { headerEntry.textNode.value = headerValue; } function createNewHeaderView(headerName) { var idName = 'expanded' + headerName + 'Box'; var newHeader = document.createElement("mail-headerfield"); newHeader.setAttribute('id', idName); if (headerName.indexOf("Dummy-Header") == 0) // -1 means not found, 0 means starts at the beginning { newHeader.setAttribute('label', ""); } else { newHeader.setAttribute('label', currentHeaderData[headerName].headerName + ':'); // all mail-headerfield elements are keyword related newHeader.setAttribute('keywordrelated','true'); } newHeader.collapsed = true; // this new element needs to be inserted into the view... var topViewNode = document.getElementById('expandedHeaders'); topViewNode.appendChild(newHeader); this.enclosingBox = newHeader; this.isValid = false; this.useToggle = false; this.useShortView = false; this.outputFunction = updateHeaderValue; } // UpdateMessageHeaders: Iterate through all the current header data we received from mime for this message // for each header entry table, see if we have a corresponding entry for that header. i.e. does the particular // view care about this header value. if it does then call updateHeaderEntry function UpdateMessageHeaders() { // iterate over each header we received and see if we have a matching entry in each // header view table... for (headerName in currentHeaderData) { var headerField = currentHeaderData[headerName]; var headerEntry = null; if (headerName == "subject") { try { if (gDBView.keyForFirstSelectedMessage == nsMsgKey_None) { var folder = null; if (gCurrentFolderUri) folder = RDF.GetResource(gCurrentFolderUri).QueryInterface(Components.interfaces.nsIMsgFolder); setTitleFromFolder(folder, headerField.headerValue); } } catch (ex) {} } if (gCollapsedHeaderViewMode && !gBuiltCollapsedView) { if (headerName in gCollapsedHeaderView) headerEntry = gCollapsedHeaderView[headerName]; } else if (!gCollapsedHeaderViewMode && !gBuiltExpandedView) { if (headerName in gExpandedHeaderView) headerEntry = gExpandedHeaderView[headerName]; if (!headerEntry && gViewAllHeaders) { // for view all headers, if we don't have a header field for this value....cheat and create one....then // fill in a headerEntry gExpandedHeaderView[headerName] = new createNewHeaderView(headerName); headerEntry = gExpandedHeaderView[headerName]; } } // if we are in expanded view.... if (headerEntry) { headerEntry.outputFunction(headerEntry, headerField.headerValue); headerEntry.valid = true; } } if (gCollapsedHeaderViewMode) gBuiltCollapsedView = true; else gBuiltExpandedView = true; // now update the view to make sure the right elements are visible updateHeaderViews(); if ("FinishEmailProcessing" in this) FinishEmailProcessing(); } function ClearCurrentHeaders() { currentHeaderData = {}; currentAttachments = new Array(); } function ShowMessageHeaderPane() { var node; if (gCollapsedHeaderViewMode) { node = document.getElementById("collapsedHeaderView"); if (node) node.collapsed = false; } else { node = document.getElementById("expandedHeaderView"); if (node) node.collapsed = false; } /* workaround for 39655 */ if (gFolderJustSwitched) { var el = document.getElementById("msgHeaderView"); el.setAttribute("style", el.getAttribute("style")); gFolderJustSwitched = false; } } function HideMessageHeaderPane() { var node = document.getElementById("collapsedHeaderView"); if (node) node.collapsed = true; node = document.getElementById("expandedHeaderView"); if (node) node.collapsed = true; // we also have to disable the File/Attachments menuitem node = document.getElementById("fileAttachmentMenu"); if (node) node.setAttribute("disabled", "true"); node = document.getElementById("attachmentView"); if (node) node.collapsed = true; } function OutputNewsgroups(headerEntry, headerValue) { headerValue = headerValue.replace(/,/g,", "); updateHeaderValue(headerEntry, headerValue); } // 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... // useShortView --> if true, we'll only generate the Name of the email address field instead of // showing the name + the email address. function OutputEmailAddresses(headerEntry, emailAddresses) { if ( !emailAddresses ) return; if (msgHeaderParser) { var addresses = {}; var fullNames = {}; var names = {}; var numAddresses = 0; numAddresses = msgHeaderParser.parseHeadersWithArray(emailAddresses, addresses, names, fullNames); var index = 0; while (index < numAddresses) { // 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 (headerEntry.useToggle) { var address = {}; address.emailAddress = addresses.value[index]; address.fullAddress = fullNames.value[index]; address.displayName = names.value[index]; headerEntry.enclosingBox.addAddressView(address); } else { updateEmailAddressNode(headerEntry.enclosingBox.emailAddressNode, addresses.value[index], fullNames.value[index], names.value[index], headerEntry.useShortView); } if (headerEntry.enclosingBox.getAttribute("id") == "expandedfromBox") { setFromBuddyIcon(addresses.value[index]); } index++; } if (headerEntry.useToggle) headerEntry.enclosingBox.buildViews(gNumAddressesToShow); } // if msgheader parser } function setFromBuddyIcon(email) { var fromBuddyIcon = document.getElementById("fromBuddyIcon"); try { // better to cache this? var myScreenName = pref.getCharPref("aim.session.screenname"); if (!abAddressCollector) abAddressCollector = Components.classes[abAddressCollectorContractID].getService(Components.interfaces.nsIAbAddressCollecter); var card = abAddressCollector.getCardFromAttribute("PrimaryEmail", email); if (myScreenName && card && card.aimScreenName) { if (!gIOService) { // lazily create these globals gIOService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); gFileHandler = gIOService.getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler); var dirService = Components.classes["@mozilla.org/file/directory_service;1"] .getService(Components.interfaces.nsIProperties); var profileDir = dirService.get("ProfD", Components.interfaces.nsIFile); gProfileDirURL = gIOService.newFileURI(profileDir); } // if we did have a buddy icon on disk for this screenname, this would be the file url spec for it var iconURLStr = gProfileDirURL.spec + "/NIM/" + myScreenName + "/picture/" + card.aimScreenName + ".gif"; // check if the file exists // is this a perf hit? (how expensive is stat()?) var iconFile = gFileHandler.getFileFromURLSpec(iconURLStr); if (iconFile.exists()) { fromBuddyIcon.setAttribute("src", iconURLStr); return; } } } catch (ex) { // can get here if no screenname //dump("ex = " + ex + "\n"); } fromBuddyIcon.setAttribute("src", ""); } function updateEmailAddressNode(emailAddressNode, emailAddress, fullAddress, displayName, useShortView) { if (useShortView && displayName) { emailAddressNode.setAttribute("label", displayName); emailAddressNode.setAttribute("tooltiptext", emailAddress); } else { emailAddressNode.setAttribute("label", fullAddress); emailAddressNode.removeAttribute("tooltiptext"); } emailAddressNode.setTextAttribute("emailAddress", emailAddress); emailAddressNode.setTextAttribute("fullAddress", fullAddress); emailAddressNode.setTextAttribute("displayName", displayName); if ("AddExtraAddressProcessing" in this) AddExtraAddressProcessing(emailAddress, emailAddressNode); } // thunderbird has smart logic for determining if we should show just the display name. // mozilla already has a generic method that gets called on each email address node called // AddExtraAddressProcessing which is undefined in seamonkey. Let's hijack this convient method to do what // we want. function AddExtraAddressProcessing(emailAddress, addressNode) { var displayName = addressNode.getTextAttribute("displayName"); var emailAddress = addressNode.getTextAttribute("emailAddress"); // always show the address for the from and reply-to fields var parentElementId = addressNode.parentNode.id; var condenseName = true; if (parentElementId == "expandedfromBox" || parentElementId == "expandedreply-toBox") condenseName = false; if (condenseName && gShowCondensedEmailAddresses && displayName && useDisplayNameForAddress(emailAddress)) { addressNode.setAttribute('label', displayName); addressNode.setAttribute("tooltiptext", emailAddress); addressNode.setAttribute("tooltip", "emailAddressTooltip"); } else addressNode.removeAttribute("tooltiptext"); } function fillEmailAddressPopup(emailAddressNode) { var emailAddressPlaceHolder = document.getElementById('emailAddressPlaceHolder'); var emailAddress = emailAddressNode.getTextAttribute('emailAddress'); emailAddressPlaceHolder.setAttribute('label', emailAddress); } // Public method called to generate a tooltip over a condensed display name function fillInEmailAddressTooltip(addressNode) { var emailAddress = addressNode.getTextAttribute('emailAddress'); var tooltipNode = document.getElementById("attachmentListTooltip"); tooltipNode.setAttribute("label", attachmentName); return true; } // returns true if we should use the display name for this address // otherwise returns false function useDisplayNameForAddress(emailAddress) { // For now, if the email address is in the personal address book, then consider this user a 'known' user // and use the display name. I could eventually see our rules enlarged to include other local ABs, replicated // LDAP directories, and maybe even domain matches (i.e. any email from someone in my company // should use the friendly display name) if (!gPersonalAddressBookDirectory) { var RDFService = Components.classes[kRDFServiceContractID].getService(Components.interfaces.nsIRDFService); gPersonalAddressBookDirectory = RDFService.GetResource(kPersonalAddressbookUri).QueryInterface(Components.interfaces.nsIAbMDBDirectory); if (!gPersonalAddressBookDirectory) return false; } // look up the email address in the database return gPersonalAddressBookDirectory.hasCardForEmailAddress(emailAddress); } function AddNodeToAddressBook (emailAddressNode) { if (emailAddressNode) { var primaryEmail = emailAddressNode.getAttribute("emailAddress"); var displayName = emailAddressNode.getAttribute("displayName"); window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul", "", "chrome,resizable=no,titlebar,modal,centerscreen", {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) { var fields = Components.classes["@mozilla.org/messengercompose/composefields;1"].createInstance(Components.interfaces.nsIMsgCompFields); var params = Components.classes["@mozilla.org/messengercompose/composeparams;1"].createInstance(Components.interfaces.nsIMsgComposeParams); if (emailAddressNode && fields && params) { fields.to = emailAddressNode.getAttribute("fullAddress"); params.type = Components.interfaces.nsIMsgCompType.New; params.format = Components.interfaces.nsIMsgCompFormat.Default; params.identity = accountManager.getFirstIdentityForServer(GetLoadedMsgFolder().server); params.composeFields = fields; msgComposeService.OpenComposeWindowWithParams(null, params); } } // CopyEmailAddress takes the email address title button, extracts // the email address we stored in there and copies it to the clipboard function CopyEmailAddress(emailAddressNode) { if (emailAddressNode) { var emailAddress = emailAddressNode.getAttribute("emailAddress"); var contractid = "@mozilla.org/widget/clipboardhelper;1"; var iid = Components.interfaces.nsIClipboardHelper; var clipboard = Components.classes[contractid].getService(iid); clipboard.copyString(emailAddress); } } // CreateFilter opens the Message Filters and Filter Rules dialogs. //The Filter Rules dialog has focus. The window is prefilled with filtername //Sender condition is selected and the value is prefilled function CreateFilter(emailAddressNode) { if (emailAddressNode) { var emailAddress = emailAddressNode.getAttribute("emailAddress"); if (emailAddress){ top.MsgFilters(emailAddress, GetFirstSelectedMsgFolder()); } } } // createnewAttachmentInfo --> constructor method for creating new attachment object which goes into the // data attachment array. function createNewAttachmentInfo(contentType, url, displayName, uri, isExternalAttachment) { this.contentType = contentType; this.url = url; this.displayName = displayName; this.uri = uri; this.isExternalAttachment = isExternalAttachment; } function dofunc(aFunctionName, aFunctionArg) { if (aFunctionName == "saveAttachment") saveAttachment(aFunctionArg); else if (aFunctionName == "openAttachment") openAttachment(aFunctionArg); else if (aFunctionName == "printAttachment") printAttachment(aFunctionArg); else if (aFunctionName == "deleteAttachment") detachAttachment(aFunctionArg, false); else if (aFunctionName == "detachAttachment") detachAttachment(aFunctionArg, true); } function saveAttachment(aAttachment) { messenger.saveAttachment(aAttachment.contentType, aAttachment.url, encodeURIComponent(aAttachment.displayName), aAttachment.messageUri, aAttachment.isExternalAttachment); } function openAttachment(aAttachment) { messenger.openAttachment(aAttachment.contentType, aAttachment.url, encodeURIComponent(aAttachment.displayName), aAttachment.messageUri, aAttachment.isExternalAttachment); } function printAttachment(aAttachment) { /* we haven't implemented the ability to print attachments yet... messenger.printAttachment(aAttachment.contentType, aAttachment.url, encodeURIComponent(aAttachment.displayName), aAttachment.messageUri); */ } function detachAttachment(aAttachment, aSaveFirst) { messenger.detachAttachment(aAttachment.contentType, aAttachment.url, encodeURIComponent(aAttachment.displayName), aAttachment.messageUri, aSaveFirst); } function CanDetachAttachments() { var uri = GetLoadedMessage(); var canDetach = !IsNewsMessage(uri) && (!IsImapMessage(uri) || CheckOnline()); if (canDetach && ("content-type" in currentHeaderData)) { var contentType = currentHeaderData["content-type"].headerValue; canDetach = contentType.indexOf("application/x-pkcs7-mime") < 0 && contentType.indexOf("application/x-pkcs7-signature") < 0; } return canDetach; } function onShowAttachmentContextMenu() { // if no attachments are selected, disable the Open and Save... var attachmentList = document.getElementById('attachmentList'); var selectedAttachments = attachmentList.selectedItems; var openMenu = document.getElementById('context-openAttachment'); var saveMenu = document.getElementById('context-saveAttachment'); var detachMenu = document.getElementById('context-detachAttachment'); var deleteMenu = document.getElementById('context-deleteAttachment'); var detachAllMenu = document.getElementById('context-detachAllAttachments'); var deleteAllMenu = document.getElementById('context-deleteAllAttachments'); var canDetach = CanDetachAttachments(); var canOpen = false; for (var i = 0; i < selectedAttachments.length && !canOpen; i++) canOpen = selectedAttachments[i].attachment.contentType != 'text/x-moz-deleted'; if (canOpen && selectedAttachments.length == 1) { openMenu.removeAttribute('disabled'); } else { openMenu.setAttribute('disabled', true); } if (canOpen) { saveMenu.removeAttribute('disabled'); } else { saveMenu.setAttribute('disabled', true); } if (canDetach && canOpen) { detachMenu.removeAttribute('disabled'); deleteMenu.removeAttribute('disabled'); } else { detachMenu.setAttribute('disabled', 'true'); deleteMenu.setAttribute('disabled', 'true'); } if (canDetach) { detachAllMenu.removeAttribute('disabled'); deleteAllMenu.removeAttribute('disabled'); } else { detachAllMenu.setAttribute('disabled', 'true'); deleteAllMenu.setAttribute('disabled', 'true'); } } // this is our onclick handler for the attachment list. // A double click in a listitem simulates "opening" the attachment.... function attachmentListClick(event) { // we only care about button 0 (left click) events if (event.button != 0) return; if (event.detail == 2) // double click { var target = event.target; if (target.localName == "descriptionitem") { dofunc("openAttachment", target.attachment); } } } // on command handlers for the attachment list context menu... // commandPrefix matches one of our existing functions // (openAttachment, saveAttachment, etc.) function handleAttachmentSelection(commandPrefix) { var attachmentList = document.getElementById('attachmentList'); var selectedAttachments = attachmentList.selectedItems; // loop over all of the selected attachments... // XXX hack alert. If we sit in tight loop and call doFunc for multiple attachments, // we get chrome errors in layout as we start loading the first helper app dialog // then before it loads, we kick off the next one and the next one. Subsequent helper app dialogs // were failing because we were still loading the chrome files for the first attempt (error about the xul cache // being empty). For now, work around this by doing the first helper app dialog right away, then waiting a bit // before we launch the rest. for (var i = 0; i < selectedAttachments.length; i++) if (!i) dofunc(commandPrefix, selectedAttachments[i].attachment); else setTimeout(dofunc, 100, commandPrefix, selectedAttachments[i].attachment); } function cloneAttachment(aAttachment) { var obj = new Object(); obj.contentType = aAttachment.contentType; obj.url = aAttachment.url; obj.displayName = aAttachment.displayName; obj.messageUri = aAttachment.uri; obj.isExternalAttachment = aAttachment.isExternalAttachment; return obj; } function displayAttachmentsForExpandedView() { var numAttachments = currentAttachments.length; // IMPORTANT: make sure we uncollapse the attachment box BEFORE we start adding // our attachments to the view. Otherwise, layout doesn't calculate the correct height for // the attachment view and we end up with a box that is too tall. var expandedAttachmentBox = document.getElementById('attachmentView'); expandedAttachmentBox.collapsed = numAttachments <= 0; if (numAttachments > 0 && !gBuildAttachmentsForCurrentMsg) { var attachmentList = document.getElementById('attachmentList'); for (index in currentAttachments) { var attachment = currentAttachments[index]; // create a new attachment widget. set the label, set the // moz-icon img src var attachmentView = attachmentList.appendItem(attachment.displayName); attachmentView.setAttribute("class", "descriptionitem-iconic"); if (gShowLargeAttachmentView) { attachmentView.setAttribute("largeView", "true"); attachmentView.setAttribute("orient", "vertical"); } setApplicationIconForAttachment(attachment, attachmentView, gShowLargeAttachmentView); attachmentView.setAttribute("tooltip", "attachmentListTooltip"); attachmentView.setAttribute("context", "attachmentListContext"); attachmentView.attachment = cloneAttachment(attachment); attachmentView.setAttribute("attachmentUrl", attachment.url); attachmentView.setAttribute("attachmentContentType", attachment.contentType); attachmentView.setAttribute("attachmentUri", attachment.uri); var item = attachmentList.appendChild(attachmentView); } // for each attachment gBuildAttachmentsForCurrentMsg = true; } } // attachment --> the attachment struct containing all the information on the attachment // listitem --> the listitem currently showing the attachment. function setApplicationIconForAttachment(attachment, listitem, largeView) { var iconSize = largeView ? kLargeIcon : kSmallIcon; // generate a moz-icon url for the attachment so we'll show a nice icon next to it. if ( attachment.contentType == 'text/x-moz-deleted' ) listitem.setAttribute('image', 'chrome://messenger/skin/icons/message-mail-attach-del.gif'); else listitem.setAttribute('image', "moz-icon:" + "//" + attachment.displayName + "?size=" + iconSize + "&contentType=" + attachment.contentType); } // Public method called to generate a tooltip over an attachment function FillInAttachmentTooltip(cellNode) { var attachmentName = cellNode.getAttribute("label"); var tooltipNode = document.getElementById("attachmentListTooltip"); tooltipNode.setAttribute("label", attachmentName); return true; } // Public method called when we create the attachments file menu function FillAttachmentListPopup(popup) { // the FE sometimes call this routine TWICE...I haven't been able to figure out why yet... // protect against it... if (!gBuildAttachmentPopupForCurrentMsg) return; var attachmentIndex = 0; // otherwise we need to build the attachment view... // First clear out the old view... ClearAttachmentMenu(popup); for (index in currentAttachments) { ++attachmentIndex; addAttachmentToPopup(popup, currentAttachments[index], attachmentIndex); } gBuildAttachmentPopupForCurrentMsg = false; var detachAllMenu = document.getElementById('file-detachAllAttachments'); var deleteAllMenu = document.getElementById('file-deleteAllAttachments'); if (CanDetachAttachments()) { detachAllMenu.removeAttribute('disabled'); deleteAllMenu.removeAttribute('disabled'); } else { detachAllMenu.setAttribute('disabled', 'true'); deleteAllMenu.setAttribute('disabled', 'true'); } } // Public method used to clear the file attachment menu function ClearAttachmentMenu(popup) { if ( popup ) { while ( popup.childNodes[0].localName == 'menu' ) popup.removeChild(popup.childNodes[0]); } } // Public method used to determine the number of attachments for the currently displayed message... function GetNumberOfAttachmentsForDisplayedMessage() { return currentAttachments.length; } // private method used to build up a menu list of attachments function addAttachmentToPopup(popup, attachment, attachmentIndex) { if (popup) { var item = document.createElement('menu'); if ( item ) { if (!gMessengerBundle) gMessengerBundle = document.getElementById("bundle_messenger"); // insert the item just before the separator...the separator is the 2nd to last element in the popup. item.setAttribute('class', 'menu-iconic'); setApplicationIconForAttachment(attachment,item, false); var numItemsInPopup = popup.childNodes.length; // find the separator var indexOfSeparator = 0; while (popup.childNodes[indexOfSeparator].localName != 'menuseparator') indexOfSeparator++; var formattedDisplayNameString = gMessengerBundle.getFormattedString("attachmentDisplayNameFormat", [attachmentIndex, attachment.displayName]); item.setAttribute('label', formattedDisplayNameString); item.setAttribute('accesskey', attachmentIndex); // each attachment in the list gets its own menupopup with options for saving, deleting, detaching, etc. var openpopup = document.createElement('menupopup'); openpopup = item.appendChild(openpopup); // Due to Bug #314228, we must append our menupopup to the new attachment menu item // before we inserting the attachment menu into the popup. If we don't, our attachment // menu items will not show up. item = popup.insertBefore(item, popup.childNodes[indexOfSeparator]); var menuitementry = document.createElement('menuitem'); menuitementry.attachment = cloneAttachment(attachment); menuitementry.setAttribute('oncommand', 'openAttachment(this.attachment)'); if (!gSaveLabel) gSaveLabel = gMessengerBundle.getString("saveLabel"); if (!gSaveLabelAccesskey) gSaveLabelAccesskey = gMessengerBundle.getString("saveLabelAccesskey"); if (!gOpenLabel) gOpenLabel = gMessengerBundle.getString("openLabel"); if (!gOpenLabelAccesskey) gOpenLabelAccesskey = gMessengerBundle.getString("openLabelAccesskey"); if (!gDetachLabel) gDetachLabel = gMessengerBundle.getString("detachLabel"); if (!gDetachLabelAccesskey) gDetachLabelAccesskey = gMessengerBundle.getString("detachLabelAccesskey"); if (!gDeleteLabel) gDeleteLabel = gMessengerBundle.getString("deleteLabel"); if (!gDeleteLabelAccesskey) gDeleteLabelAccesskey = gMessengerBundle.getString("deleteLabelAccesskey"); // we should also check if an attachment has been detached... // but that uses X-Mozilla-External-Attachment-URL, which // we'd need to check for somehow. var signedOrEncrypted = false; if ("content-type" in currentHeaderData) { var contentType = currentHeaderData["content-type"].headerValue; signedOrEncrypted = contentType.indexOf("application/x-pkcs7-mime") >= 0 || contentType.indexOf("application/x-pkcs7-signature") >= 0; } var canDetach = !(/news-message:/.test(attachment.uri)) && !signedOrEncrypted && (!(/imap-message/.test(attachment.uri)) || CheckOnline()); menuitementry.setAttribute('label', gOpenLabel); menuitementry.setAttribute('accesskey', gOpenLabelAccesskey); menuitementry = openpopup.appendChild(menuitementry); if (attachment.contentType == 'text/x-moz-deleted') menuitementry.setAttribute('disabled', true); var menuseparator = document.createElement('menuseparator'); openpopup.appendChild(menuseparator); menuitementry = document.createElement('menuitem'); menuitementry.attachment = cloneAttachment(attachment); menuitementry.setAttribute('oncommand', 'saveAttachment(this.attachment)'); menuitementry.setAttribute('label', gSaveLabel); menuitementry.setAttribute('accesskey', gSaveLabelAccesskey); if (attachment.contentType == 'text/x-moz-deleted') menuitementry.setAttribute('disabled', true); menuitementry = openpopup.appendChild(menuitementry); var menuseparator = document.createElement('menuseparator'); openpopup.appendChild(menuseparator); menuitementry = document.createElement('menuitem'); menuitementry.attachment = attachment; menuitementry.setAttribute('oncommand', 'this.attachment.detachAttachment()'); menuitementry.setAttribute('label', gDetachLabel); menuitementry.setAttribute('accesskey', gDetachLabelAccesskey); if (attachment.contentType == 'text/x-moz-deleted' || !canDetach) menuitementry.setAttribute('disabled', true); menuitementry = openpopup.appendChild(menuitementry); menuitementry = document.createElement('menuitem'); menuitementry.attachment = attachment; menuitementry.setAttribute('oncommand', 'this.attachment.deleteAttachment()'); menuitementry.setAttribute('label', gDeleteLabel); menuitementry.setAttribute('accesskey', gDeleteLabelAccesskey); if (attachment.contentType == 'text/x-moz-deleted' || !canDetach) menuitementry.setAttribute('disabled', true); menuitementry = openpopup.appendChild(menuitementry); } // if we created a menu item for this attachment... } // if we have a popup } function HandleAllAttachments(action) { try { // convert our attachment data into some c++ friendly structs var attachmentContentTypeArray = new Array(); var attachmentUrlArray = new Array(); var attachmentDisplayNameArray = new Array(); var attachmentMessageUriArray = new Array(); // populate these arrays.. var actionIndex = 0; for (index in currentAttachments) { // exclude all attachments already deleted var attachment = currentAttachments[index]; if ( attachment.contentType != 'text/x-moz-deleted' ) { attachmentContentTypeArray[actionIndex] = attachment.contentType; attachmentUrlArray[actionIndex] = attachment.url; attachmentDisplayNameArray[actionIndex] = encodeURI(attachment.displayName); attachmentMessageUriArray[actionIndex] = attachment.uri; ++actionIndex; } } // okay the list has been built... now call our action code... if ( action == 'save' ) messenger.saveAllAttachments(attachmentContentTypeArray.length, attachmentContentTypeArray, attachmentUrlArray, attachmentDisplayNameArray, attachmentMessageUriArray); else if ( action == 'detach' ) messenger.detachAllAttachments(attachmentContentTypeArray.length, attachmentContentTypeArray, attachmentUrlArray, attachmentDisplayNameArray, attachmentMessageUriArray, true); // save else if ( action == 'delete' ) messenger.detachAllAttachments(attachmentContentTypeArray.length, attachmentContentTypeArray, attachmentUrlArray, attachmentDisplayNameArray, attachmentMessageUriArray, false); // don't save else dump ("** unknown HandleAllAttachments action: " + action + "**\n"); } catch (ex) { dump ("** failed to handle all attachments **\n"); } } function ClearAttachmentList() { // we also have to disable the File/Attachments menuitem node = document.getElementById("fileAttachmentMenu"); if (node) node.setAttribute("disabled", "true"); // clear selection var list = document.getElementById('attachmentList'); while (list.hasChildNodes()) list.removeChild(list.lastChild); } function ShowEditMessageButton() { var editBox = document.getElementById("editMessageBox"); if (editBox) editBox.collapsed = false; } function ClearEditMessageButton() { var editBox = document.getElementById("editMessageBox"); if (editBox) editBox.collapsed = true; } // CopyWebsiteAddress takes the website address title button, extracts // the website address we stored in there and copies it to the clipboard function CopyWebsiteAddress(websiteAddressNode) { if (websiteAddressNode) { var websiteAddress = websiteAddressNode.getAttribute("value"); var contractid = "@mozilla.org/widget/clipboardhelper;1"; var iid = Components.interfaces.nsIClipboardHelper; var clipboard = Components.classes[contractid].getService(iid); clipboard.copyString(websiteAddress); } } var attachmentAreaDNDObserver = { onDragStart: function (aEvent, aAttachmentData, aDragAction) { var target = aEvent.target; if (target.localName == "descriptionitem") { var attachmentUrl = target.getAttribute("attachmentUrl"); var attachmentDisplayName = target.getAttribute("label"); var attachmentContentType = target.getAttribute("attachmentContentType"); var tmpurl = attachmentUrl; var tmpurlWithExtraInfo = tmpurl + "&type=" + attachmentContentType + "&filename=" + attachmentDisplayName; aAttachmentData.data = new TransferData(); if (attachmentUrl && attachmentDisplayName) { aAttachmentData.data.addDataForFlavour("text/x-moz-url", tmpurlWithExtraInfo + "\n" + attachmentDisplayName); aAttachmentData.data.addDataForFlavour("text/x-moz-url-data", tmpurl); aAttachmentData.data.addDataForFlavour("text/x-moz-url-desc", attachmentDisplayName); aAttachmentData.data.addDataForFlavour("application/x-moz-file-promise-url", tmpurl); aAttachmentData.data.addDataForFlavour("application/x-moz-file-promise", new nsFlavorDataProvider(), 0, Components.interfaces.nsISupports); } } } }; function nsFlavorDataProvider() { } nsFlavorDataProvider.prototype = { QueryInterface : function(iid) { if (iid.equals(Components.interfaces.nsIFlavorDataProvider) || iid.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; }, getFlavorData : function(aTransferable, aFlavor, aData, aDataLen) { // get the url for the attachment if (aFlavor == "application/x-moz-file-promise") { var urlPrimitive = { }; var dataSize = { }; aTransferable.getTransferData("application/x-moz-file-promise-url", urlPrimitive, dataSize); var srcUrlPrimitive = urlPrimitive.value.QueryInterface(Components.interfaces.nsISupportsString); // now get the destination file location from kFilePromiseDirectoryMime var dirPrimitive = {}; aTransferable.getTransferData("application/x-moz-file-promise-dir", dirPrimitive, dataSize); var destDirectory = dirPrimitive.value.QueryInterface(Components.interfaces.nsILocalFile); // now save the attachment to the specified location // XXX: we need more information than just the attachment url to save it, fortunately, we have an array // of all the current attachments so we can cheat and scan through them var attachment = null; for (index in currentAttachments) { attachment = currentAttachments[index]; if (attachment.url == srcUrlPrimitive) break; } // call our code for saving attachments if (attachment) { var destFilePath = messenger.saveAttachmentToFolder(attachment.contentType, attachment.url, encodeURIComponent(attachment.displayName), attachment.uri, destDirectory); aData.value = destFilePath.QueryInterface(Components.interfaces.nsISupports); aDataLen.value = 4; } } } } function nsDummyMsgHeader() { } nsDummyMsgHeader.prototype = { mProperties : new Array, getStringProperty : function(property) {return this.mProperties[property];}, setStringProperty : function(property, val) {this.mProperties[property] = val;}, messageSize : 0, recipients : null, from : null, subject : null, ccList : null, messageId : null, accountKey : "", folder : null };