summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/components
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/mailnews/components')
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abCardOverlay.js1397
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abCardOverlay.xul515
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abCardViewOverlay.js527
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abCommon.js1185
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abEditCardDialog.xul18
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abEditListDialog.xul22
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abListOverlay.xul86
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abMailListDialog.xul34
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abNewCardDialog.xul35
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul94
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js399
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.xul92
-rw-r--r--comm/suite/mailnews/components/addrbook/content/abTrees.js332
-rw-r--r--comm/suite/mailnews/components/addrbook/content/addressbook-panel.js119
-rw-r--r--comm/suite/mailnews/components/addrbook/content/addressbook-panel.xul96
-rw-r--r--comm/suite/mailnews/components/addrbook/content/addressbook.js581
-rw-r--r--comm/suite/mailnews/components/addrbook/content/addressbook.xul722
-rw-r--r--comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.js24
-rw-r--r--comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.xul92
-rw-r--r--comm/suite/mailnews/components/addrbook/jar.mn24
-rw-r--r--comm/suite/mailnews/components/addrbook/moz.build8
-rw-r--r--comm/suite/mailnews/components/calendar/content/suite-overlay-addons.xhtml39
-rw-r--r--comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml66
-rw-r--r--comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js47
-rw-r--r--comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml39
-rw-r--r--comm/suite/mailnews/components/compose/content/MsgComposeCommands.js3936
-rw-r--r--comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js1167
-rw-r--r--comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul16
-rw-r--r--comm/suite/mailnews/components/compose/content/messengercompose.xul720
-rw-r--r--comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul23
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js30
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul212
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js151
-rw-r--r--comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul120
-rw-r--r--comm/suite/mailnews/components/compose/jar.mn14
-rw-r--r--comm/suite/mailnews/components/compose/moz.build6
-rw-r--r--comm/suite/mailnews/components/moz.build11
-rw-r--r--comm/suite/mailnews/components/prefs/content/mailPrefsOverlay.xul102
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-character_encoding.js41
-rwxr-xr-xcomm/suite/mailnews/components/prefs/content/pref-character_encoding.xul111
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-junk.js45
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-junk.xul134
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-mailnews.js25
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-mailnews.xul141
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-notifications.js91
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-notifications.xul187
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-offline.js19
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-offline.xul121
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-receipts.js28
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-receipts.xul146
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-tags.js478
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-tags.xul83
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-viewing_messages.js26
-rw-r--r--comm/suite/mailnews/components/prefs/content/pref-viewing_messages.xul174
-rw-r--r--comm/suite/mailnews/components/prefs/jar.mn23
-rw-r--r--comm/suite/mailnews/components/prefs/moz.build6
-rw-r--r--comm/suite/mailnews/components/smime/content/msgCompSMIMEOverlay.js354
-rw-r--r--comm/suite/mailnews/components/smime/content/msgHdrViewSMIMEOverlay.js258
-rw-r--r--comm/suite/mailnews/components/smime/jar.mn15
-rw-r--r--comm/suite/mailnews/components/smime/moz.build6
60 files changed, 15613 insertions, 0 deletions
diff --git a/comm/suite/mailnews/components/addrbook/content/abCardOverlay.js b/comm/suite/mailnews/components/addrbook/content/abCardOverlay.js
new file mode 100644
index 0000000000..06167ca240
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abCardOverlay.js
@@ -0,0 +1,1397 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const kNonVcardFields =
+ ["NickNameContainer", "SecondaryEmailContainer", "ScreenNameContainer",
+ "customFields", "preferDisplayName"];
+
+const kPhoneticFields =
+ ["PhoneticLastName", "PhoneticLabel1", "PhoneticSpacer1",
+ "PhoneticFirstName", "PhoneticLabel2", "PhoneticSpacer2"];
+
+// Item is |[dialogField, cardProperty]|.
+const kVcardFields =
+ [ // Contact > Name
+ ["FirstName", "FirstName"],
+ ["LastName", "LastName"],
+ ["DisplayName", "DisplayName"],
+ ["NickName", "NickName"],
+ // Contact > Internet
+ ["PrimaryEmail", "PrimaryEmail"],
+ ["SecondEmail", "SecondEmail"],
+ // Contact > Phones
+ ["WorkPhone", "WorkPhone"],
+ ["HomePhone", "HomePhone"],
+ ["FaxNumber", "FaxNumber"],
+ ["PagerNumber", "PagerNumber"],
+ ["CellularNumber", "CellularNumber"],
+ // Address > Home
+ ["HomeAddress", "HomeAddress"],
+ ["HomeAddress2", "HomeAddress2"],
+ ["HomeCity", "HomeCity"],
+ ["HomeState", "HomeState"],
+ ["HomeZipCode", "HomeZipCode"],
+ ["HomeCountry", "HomeCountry"],
+ ["WebPage2", "WebPage2"],
+ // Address > Work
+ ["JobTitle", "JobTitle"],
+ ["Department", "Department"],
+ ["Company", "Company"],
+ ["WorkAddress", "WorkAddress"],
+ ["WorkAddress2", "WorkAddress2"],
+ ["WorkCity", "WorkCity"],
+ ["WorkState", "WorkState"],
+ ["WorkZipCode", "WorkZipCode"],
+ ["WorkCountry", "WorkCountry"],
+ ["WebPage1", "WebPage1"],
+ // Other > (custom)
+ ["Custom1", "Custom1"],
+ ["Custom2", "Custom2"],
+ ["Custom3", "Custom3"],
+ ["Custom4", "Custom4"],
+ // Other > Notes
+ ["Notes", "Notes"],
+ // Chat
+ ["Yahoo", "_Yahoo"],
+ ["Skype", "_Skype"],
+ ["QQ", "_QQ"],
+ ["MSN", "_MSN"],
+ ["ICQ", "_ICQ"],
+ ["XMPP", "_JabberId"],
+ ["IRC", "_IRC"]
+ ];
+
+var gEditCard;
+var gOnSaveListeners = [];
+var gOnLoadListeners = [];
+var gOkCallback = null;
+var gHideABPicker = false;
+var gPhotoHandlers = {};
+// If any new photos were added to the card, this stores the name of the original
+// and any temporary new filenames used to store photos of the card.
+// 'null' is a valid value when there was no photo (e.g. the generic photo).
+var gOldPhotos = [];
+// If a new photo was added, the name is stored here.
+var gNewPhoto = null;
+
+function OnLoadNewCard()
+{
+ InitEditCard();
+
+ gEditCard.card =
+ (("arguments" in window) && (window.arguments.length > 0) &&
+ (window.arguments[0] instanceof Ci.nsIAbCard))
+ ? window.arguments[0]
+ : Cc["@mozilla.org/addressbook/cardproperty;1"]
+ .createInstance(Ci.nsIAbCard);
+ gEditCard.titleProperty = "newContactTitle";
+ gEditCard.selectedAB = "";
+
+ if ("arguments" in window && window.arguments[0])
+ {
+ gEditCard.selectedAB = kPersonalAddressbookURI;
+
+ if ("selectedAB" in window.arguments[0] &&
+ (window.arguments[0].selectedAB != kAllDirectoryRoot + "?")) {
+ // check if selected ab is a mailing list
+ var abURI = window.arguments[0].selectedAB;
+
+ var directory = GetDirectoryFromURI(abURI);
+ if (directory.isMailList) {
+ var parentURI = GetParentDirectoryFromMailingListURI(abURI);
+ if (parentURI)
+ gEditCard.selectedAB = parentURI;
+ }
+ else if (!directory.readOnly)
+ gEditCard.selectedAB = window.arguments[0].selectedAB;
+ }
+
+ // we may have been given properties to pre-initialize the window with....
+ // we'll fill these in here...
+ if ("primaryEmail" in window.arguments[0])
+ gEditCard.card.primaryEmail = window.arguments[0].primaryEmail;
+ if ("displayName" in window.arguments[0]) {
+ gEditCard.card.displayName = window.arguments[0].displayName;
+ // if we've got a display name, don't generate
+ // a display name (and stomp on the existing display name)
+ // when the user types a first or last name
+ if (gEditCard.card.displayName)
+ gEditCard.generateDisplayName = false;
+ }
+ if ("okCallback" in window.arguments[0])
+ gOkCallback = window.arguments[0].okCallback;
+
+ if ("escapedVCardStr" in window.arguments[0]) {
+ // hide non vcard values
+ HideNonVcardFields();
+ gEditCard.card = Cc["@mozilla.org/addressbook/msgvcardservice;1"]
+ .getService(Ci.nsIMsgVCardService)
+ .escapedVCardToAbCard(window.arguments[0].escapedVCardStr);
+ }
+
+ if ("titleProperty" in window.arguments[0])
+ gEditCard.titleProperty = window.arguments[0].titleProperty;
+
+ if ("hideABPicker" in window.arguments[0])
+ gHideABPicker = window.arguments[0].hideABPicker;
+ }
+
+ // set popup with address book names
+ var abPopup = document.getElementById('abPopup');
+ abPopup.value = gEditCard.selectedAB || kPersonalAddressbookURI;
+
+ if (gHideABPicker && abPopup) {
+ abPopup.hidden = true;
+ document.getElementById("abPopupLabel").hidden = true;
+ }
+
+ SetCardDialogTitle(gEditCard.card.displayName);
+
+ GetCardValues(gEditCard.card, document);
+
+ // FIX ME - looks like we need to focus on both the text field and the tab widget
+ // probably need to do the same in the addressing widget
+
+ // focus on first or last name based on the pref
+ var focus = document.getElementById(gEditCard.displayLastNameFirst
+ ? "LastName" : "FirstName");
+ if (focus) {
+ // XXX Using the setTimeout hack until bug 103197 is fixed
+ setTimeout( function(firstTextBox) { firstTextBox.focus(); }, 0, focus );
+ }
+}
+
+/**
+ * Get the source directory containing the card we are editing.
+ */
+function getContainingDirectory() {
+ let directory = GetDirectoryFromURI(gEditCard.abURI);
+ // If the source directory is "All Address Books", find the parent
+ // address book of the card being edited and reflect the changes in it.
+ if (directory.URI == kAllDirectoryRoot + "?") {
+ let dirId =
+ gEditCard.card.directoryId
+ .substring(0, gEditCard.card.directoryId.indexOf("&"));
+ directory = MailServices.ab.getDirectoryFromId(dirId);
+ }
+ return directory;
+}
+
+function EditCardOKButton()
+{
+ if (!CheckCardRequiredDataPresence(document))
+ return false; // don't close window
+
+ // See if this card is in any mailing list
+ // if so then we need to update the addresslists of those mailing lists
+ let directory = getContainingDirectory();
+
+ // if the directory is a mailing list we need to search all the mailing lists
+ // in the parent directory if the card exists.
+ if (directory.isMailList) {
+ var parentURI = GetParentDirectoryFromMailingListURI(gEditCard.abURI);
+ directory = GetDirectoryFromURI(parentURI);
+ }
+
+ var listDirectoriesCount = directory.addressLists.length;
+ var foundDirectories = [];
+
+ // create a list of mailing lists and the index where the card is at.
+ for (let i = 0; i < listDirectoriesCount; i++)
+ {
+ var subdirectory = directory.addressLists.queryElementAt(i, Ci.nsIAbDirectory);
+ if (subdirectory.isMailList)
+ {
+ // See if any card in this list is the one we edited.
+ // Must compare card contents using .equals() instead of .indexOf()
+ // because gEditCard is not really a member of the .addressLists array.
+ let listCardsCount = subdirectory.addressLists.length;
+ for (let index = 0; index < listCardsCount; index++)
+ {
+ let card = subdirectory.addressLists.queryElementAt(index, Ci.nsIAbCard);
+ if (card.equals(gEditCard.card))
+ foundDirectories.push({directory:subdirectory, cardIndex:index});
+ }
+ }
+ }
+
+ CheckAndSetCardValues(gEditCard.card, document, false);
+
+ directory.modifyCard(gEditCard.card);
+
+ while (foundDirectories.length)
+ {
+ // Update the addressLists item for this card
+ let foundItem = foundDirectories.pop();
+ foundItem.directory.addressLists.replaceElementAt(gEditCard.card, foundItem.cardIndex);
+ }
+
+ NotifySaveListeners(directory);
+
+ // callback to allow caller to update
+ if (gOkCallback)
+ gOkCallback();
+
+ return true; // close the window
+}
+
+function EditCardCancelButton()
+{
+ // If a new photo was created, remove it now as it won't be used.
+ purgeOldPhotos(false);
+}
+
+function OnLoadEditCard()
+{
+ InitEditCard();
+
+ gEditCard.titleProperty = "editContactTitle";
+
+ if (window.arguments && window.arguments[0])
+ {
+ if ( window.arguments[0].card )
+ gEditCard.card = window.arguments[0].card;
+ if ( window.arguments[0].okCallback )
+ gOkCallback = window.arguments[0].okCallback;
+ if ( window.arguments[0].abURI )
+ gEditCard.abURI = window.arguments[0].abURI;
+ }
+
+ // set global state variables
+ // if first or last name entered, disable generateDisplayName
+ if (gEditCard.generateDisplayName &&
+ (gEditCard.card.firstName.length +
+ gEditCard.card.lastName.length +
+ gEditCard.card.displayName.length > 0))
+ {
+ gEditCard.generateDisplayName = false;
+ }
+
+ GetCardValues(gEditCard.card, document);
+
+ SetCardDialogTitle(gEditCard.card.displayName);
+
+ // check if selectedAB is a writeable
+ // if not disable all the fields
+ if ("arguments" in window && window.arguments[0])
+ {
+ if ("abURI" in window.arguments[0]) {
+ var abURI = window.arguments[0].abURI;
+ var directory = GetDirectoryFromURI(abURI);
+
+ if (directory.readOnly)
+ {
+ // Set all the editable vcard fields to read only
+ for (var i = kVcardFields.length; i-- > 0; )
+ document.getElementById(kVcardFields[i][0]).readOnly = true;
+
+ // the birthday fields
+ document.getElementById("Birthday").readOnly = true;
+ document.getElementById("BirthYear").readOnly = true;
+ document.getElementById("Age").readOnly = true;
+
+ // the photo field and buttons
+ document.getElementById("PhotoType").disabled = true;
+ document.getElementById("GenericPhotoList").disabled = true;
+ document.getElementById("PhotoURI").disabled = true;
+ document.getElementById("PhotoURI").placeholder = "";
+ document.getElementById("BrowsePhoto").disabled = true;
+ document.getElementById("UpdatePhoto").disabled = true;
+
+ // And the phonetic fields
+ document.getElementById(kPhoneticFields[0]).readOnly = true;
+ document.getElementById(kPhoneticFields[3]).readOnly = true;
+
+ // Also disable the mail format popup.
+ document.getElementById("PreferMailFormatPopup").disabled = true;
+
+ // And the "prefer display name" checkbox.
+ document.getElementById("preferDisplayName").disabled = true;
+
+ document.documentElement.buttons = "accept";
+ document.documentElement.removeAttribute("ondialogaccept");
+ }
+ }
+ }
+}
+
+/* Registers functions that are called when loading the card
+ * values into the contact editor dialog. This is useful if
+ * extensions have added extra fields to the nsIAbCard, and
+ * need to display them in the contact editor.
+ */
+function RegisterLoadListener(aFunc)
+{
+ gOnLoadListeners.push(aFunc);
+}
+
+function UnregisterLoadListener(aFunc)
+{
+ var fIndex = gOnLoadListeners.indexOf(aFunc);
+ if (fIndex != -1)
+ gOnLoadListeners.splice(fIndex, 1);
+}
+
+// Notifies load listeners that an nsIAbCard is being loaded.
+function NotifyLoadListeners(aCard, aDoc)
+{
+ if (!gOnLoadListeners.length)
+ return;
+
+ for (let listener of gOnLoadListeners)
+ listener(aCard, aDoc);
+}
+
+/* Registers functions that are called when saving the card
+ * values. This is useful if extensions have added extra
+ * fields to the user interface, and need to set those values
+ * in their nsIAbCard.
+ */
+function RegisterSaveListener(aFunc)
+{
+ gOnSaveListeners.push(aFunc);
+}
+
+function UnregisterSaveListener(aFunc)
+{
+ var fIndex = gOnSaveListeners.indexOf(aFunc);
+ if (fIndex != -1)
+ gOnSaveListeners.splice(fIndex, 1);
+}
+
+// Notifies save listeners that an nsIAbCard is being saved.
+function NotifySaveListeners(directory)
+{
+ if (!gOnSaveListeners.length)
+ return;
+
+ for (let listener of gOnSaveListeners)
+ listener(gEditCard.card, document);
+
+ // the save listeners might have tweaked the card
+ // in which case we need to commit it.
+ directory.modifyCard(gEditCard.card);
+}
+
+function InitPhoneticFields()
+{
+ var showPhoneticFields =
+ Services.prefs.getComplexValue("mail.addr_book.show_phonetic_fields",
+ Ci.nsIPrefLocalizedString).data;
+
+ // show phonetic fields if indicated by the pref
+ if (showPhoneticFields == "true")
+ {
+ for (var i = kPhoneticFields.length; i-- > 0; )
+ document.getElementById(kPhoneticFields[i]).hidden = false;
+ }
+}
+
+function InitEditCard()
+{
+ InitPhoneticFields();
+
+ InitCommonJS();
+ // Create gEditCard object that contains global variables for the current js
+ // file.
+ gEditCard = new Object();
+
+ // get specific prefs that gEditCard will need
+ try {
+ var displayLastNameFirst =
+ Services.prefs.getComplexValue("mail.addr_book.displayName.lastnamefirst",
+ Ci.nsIPrefLocalizedString).data;
+ gEditCard.displayLastNameFirst = (displayLastNameFirst == "true");
+ gEditCard.generateDisplayName =
+ Services.prefs.getBoolPref("mail.addr_book.displayName.autoGeneration");
+ }
+ catch (ex) {
+ dump("ex: failed to get pref" + ex + "\n");
+ }
+}
+
+function NewCardOKButton()
+{
+ if (gOkCallback)
+ {
+ if (!CheckAndSetCardValues(gEditCard.card, document, true))
+ return false; // don't close window
+
+ gOkCallback(gEditCard.card.translateTo("vcard"));
+ return true; // close the window
+ }
+
+ var popup = document.getElementById('abPopup');
+ if ( popup )
+ {
+ var uri = popup.value;
+
+ // FIX ME - hack to avoid crashing if no ab selected because of blank option bug from template
+ // should be able to just remove this if we are not seeing blank lines in the ab popup
+ if ( !uri )
+ return false; // don't close window
+ // -----
+
+ if (gEditCard.card)
+ {
+ if (!CheckAndSetCardValues(gEditCard.card, document, true))
+ return false; // don't close window
+
+ // replace gEditCard.card with the card we added
+ // so that save listeners can get / set attributes on
+ // the card that got created.
+ var directory = GetDirectoryFromURI(uri);
+ gEditCard.card = directory.addCard(gEditCard.card);
+ NotifySaveListeners(directory);
+ }
+ }
+
+ return true; // close the window
+}
+
+function NewCardCancelButton()
+{
+ // If a new photo was created, remove it now as it won't be used.
+ purgeOldPhotos(false);
+}
+
+// Move the data from the cardproperty to the dialog
+function GetCardValues(cardproperty, doc)
+{
+ if (!cardproperty)
+ return;
+
+ // Pass the nsIAbCard and the Document through the listeners
+ // to give extensions a chance to populate custom fields.
+ NotifyLoadListeners(cardproperty, doc);
+
+ for (var i = kVcardFields.length; i-- > 0; ) {
+ doc.getElementById(kVcardFields[i][0]).value =
+ cardproperty.getProperty(kVcardFields[i][1], "");
+ }
+
+ var birthday = doc.getElementById("Birthday");
+ modifyDatepicker(birthday);
+
+ // Get the year first, so that the following month/day
+ // calculations can take leap years into account.
+ var year = cardproperty.getProperty("BirthYear", null);
+ var birthYear = doc.getElementById("BirthYear");
+ // set the year in the datepicker to the stored year
+ // if the year isn't present, default to 2000 (a leap year)
+ birthday.year = saneBirthYear(year);
+ birthYear.value = year;
+
+ // get the month of the year (1 - 12)
+ var month = cardproperty.getProperty("BirthMonth", null);
+ if (month > 0 && month < 13)
+ birthday.month = month - 1;
+ else
+ birthday.monthField.value = null;
+
+ // get the date of the month (1 - 31)
+ var date = cardproperty.getProperty("BirthDay", null);
+ if (date > 0 && date < 32)
+ birthday.date = date;
+ else
+ birthday.dateField.value = null;
+
+ // get the current age
+ calculateAge(null, birthYear);
+ // when the birth year changes, update the datepicker's year to the new value
+ // or to kDefaultYear if the value is null
+ birthYear.onchange = calculateAge;
+ birthday.onchange = calculateAge;
+ var age = doc.getElementById("Age");
+ age.onchange = calculateYear;
+
+ var popup = document.getElementById("PreferMailFormatPopup");
+ if (popup)
+ popup.value = cardproperty.getProperty("PreferMailFormat", "");
+
+ var preferDisplayNameEl = document.getElementById("preferDisplayName");
+ if (preferDisplayNameEl)
+ // getProperty may return a "1" or "0" string, we want a boolean
+ preferDisplayNameEl.checked = cardproperty.getProperty("PreferDisplayName", true) != false;
+
+ // get phonetic fields if exist
+ try {
+ doc.getElementById("PhoneticFirstName").value = cardproperty.getProperty("PhoneticFirstName", "");
+ doc.getElementById("PhoneticLastName").value = cardproperty.getProperty("PhoneticLastName", "");
+ }
+ catch (ex) {}
+
+ // Select the type if there is a valid value stored for that type, otherwise
+ // select the generic photo
+ loadPhoto(cardproperty);
+
+ updateChatName();
+}
+
+// when the ab card dialog is being loaded to show a vCard,
+// hide the fields which aren't supported
+// by vCard so the user does not try to edit them.
+function HideNonVcardFields()
+{
+ document.getElementById("homeTabButton").hidden = true;
+ document.getElementById("photoTabButton").hidden = true;
+ var i;
+ for (i = kNonVcardFields.length; i-- > 0; )
+ document.getElementById(kNonVcardFields[i]).collapsed = true;
+ for (i = kPhoneticFields.length; i-- > 0; )
+ document.getElementById(kPhoneticFields[i]).collapsed = true;
+}
+
+// Move the data from the dialog to the cardproperty to be stored in the database
+// @Returns false - Some required data are missing (card values were not set);
+// true - Card values were set, or there is no card to set values on.
+function CheckAndSetCardValues(cardproperty, doc, check)
+{
+ // If requested, check the required data presence.
+ if (check && !CheckCardRequiredDataPresence(document))
+ return false;
+
+ if (!cardproperty)
+ return true;
+
+ for (var i = kVcardFields.length; i-- > 0; )
+ cardproperty.setProperty(kVcardFields[i][1],
+ doc.getElementById(kVcardFields[i][0]).value);
+
+ // get the birthday information from the dialog
+ var birthdayElem = doc.getElementById("Birthday");
+ var birthMonth = birthdayElem.monthField.value;
+ var birthDay = birthdayElem.dateField.value;
+ var birthYear = doc.getElementById("BirthYear").value;
+
+ // set the birth day, month, and year properties
+ cardproperty.setProperty("BirthDay", birthDay);
+ cardproperty.setProperty("BirthMonth", birthMonth);
+ cardproperty.setProperty("BirthYear", birthYear);
+
+ var popup = document.getElementById("PreferMailFormatPopup");
+ if (popup)
+ cardproperty.setProperty("PreferMailFormat", popup.value);
+
+ var preferDisplayNameEl = document.getElementById("preferDisplayName");
+ if (preferDisplayNameEl)
+ cardproperty.setProperty("PreferDisplayName", preferDisplayNameEl.checked);
+
+ // set phonetic fields if exist
+ try {
+ cardproperty.setProperty("PhoneticFirstName", doc.getElementById("PhoneticFirstName").value);
+ cardproperty.setProperty("PhoneticLastName", doc.getElementById("PhoneticLastName").value);
+ }
+ catch (ex) {}
+
+ let photoType = doc.getElementById("PhotoType").value;
+ if (gPhotoHandlers[photoType]) {
+ if (!gPhotoHandlers[photoType].onSave(cardproperty, doc)) {
+ photoType = "generic";
+ onSwitchPhotoType("generic");
+ gPhotoHandlers[photoType].onSave(cardproperty, doc);
+ }
+ }
+ cardproperty.setProperty("PhotoType", photoType);
+ purgeOldPhotos(true);
+
+ // Remove obsolete chat names.
+ try {
+ cardproperty.setProperty("_GoogleTalk", "");
+ }
+ catch (ex) {}
+ try {
+ cardproperty.setProperty("_AimScreenName", "");
+ }
+ catch (ex) {}
+
+ return true;
+}
+
+function CleanUpWebPage(webPage)
+{
+ // no :// yet so we should add something
+ if ( webPage.length && webPage.search("://") == -1 )
+ {
+ // check for missing / on http://
+ if ( webPage.substr(0, 6) == "http:/" )
+ return( "http://" + webPage.substr(6) );
+ else
+ return( "http://" + webPage );
+ }
+ else
+ return(webPage);
+}
+
+// @Returns false - Some required data are missing;
+// true - All required data are present.
+function CheckCardRequiredDataPresence(doc)
+{
+ // Bug 314995 - We require at least one of the following fields to be
+ // filled in: email address, first name, last name, display name,
+ // organization (company name).
+ var primaryEmail = doc.getElementById("PrimaryEmail");
+ if (primaryEmail.textLength == 0 &&
+ doc.getElementById("FirstName").textLength == 0 &&
+ doc.getElementById("LastName").textLength == 0 &&
+ doc.getElementById("DisplayName").textLength == 0 &&
+ doc.getElementById("Company").textLength == 0)
+ {
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("cardRequiredDataMissingTitle"),
+ gAddressBookBundle.getString("cardRequiredDataMissingMessage"));
+
+ return false;
+ }
+
+ // Simple checks that the primary email should be of the form |user@host|.
+ // Note: if the length of the primary email is 0 then we skip the check
+ // as some other field must have something as per the check above.
+ if (primaryEmail.textLength != 0 && !/.@./.test(primaryEmail.value))
+ {
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("incorrectEmailAddressFormatTitle"),
+ gAddressBookBundle.getString("incorrectEmailAddressFormatMessage"));
+
+ // Focus the dialog field, to help the user.
+ document.getElementById("abTabs").selectedIndex = 0;
+ primaryEmail.focus();
+
+ return false;
+ }
+
+ return true;
+}
+
+function GenerateDisplayName()
+{
+ if (!gEditCard.generateDisplayName)
+ return;
+
+ var displayName;
+
+ var firstNameValue = document.getElementById("FirstName").value;
+ var lastNameValue = document.getElementById("LastName").value;
+ if (lastNameValue && firstNameValue) {
+ displayName = (gEditCard.displayLastNameFirst)
+ ? gAddressBookBundle.getFormattedString("lastFirstFormat", [lastNameValue, firstNameValue])
+ : gAddressBookBundle.getFormattedString("firstLastFormat", [firstNameValue, lastNameValue]);
+ }
+ else {
+ // one (or both) of these is empty, so this works.
+ displayName = firstNameValue + lastNameValue;
+ }
+
+ document.getElementById("DisplayName").value = displayName;
+
+ SetCardDialogTitle(displayName);
+}
+
+function DisplayNameChanged()
+{
+ // turn off generateDisplayName if the user changes the display name
+ gEditCard.generateDisplayName = false;
+
+ SetCardDialogTitle(document.getElementById("DisplayName").value);
+}
+
+function SetCardDialogTitle(displayName)
+{
+ document.title = displayName
+ ? gAddressBookBundle.getFormattedString(gEditCard.titleProperty + "WithDisplayName", [displayName])
+ : gAddressBookBundle.getString(gEditCard.titleProperty);
+}
+
+/**
+ * Calculates the duration of time between an event and now and updates the year
+ * of whichever element did not call this function.
+ * @param aEvent The event calling this method.
+ * @param aElement Optional, but required if this function is not called from an
+ * element's event listener. The element that would call this.
+ */
+function calculateAge(aEvent, aElement) {
+ var datepicker, yearElem, ageElem;
+ if (aEvent)
+ aElement = this;
+ if (aElement.id == "BirthYear" || aElement.id == "Birthday") {
+ datepicker = document.getElementById("Birthday");
+ yearElem = document.getElementById("BirthYear");
+ ageElem = document.getElementById("Age");
+ }
+ if (!datepicker || !yearElem || !ageElem)
+ return;
+
+ // if the datepicker was updated, update the year element
+ if (aElement == datepicker && !(datepicker.year == kDefaultYear && !yearElem.value))
+ yearElem.value = datepicker.year;
+ var year = yearElem.value;
+ // if the year element's value is invalid set the year and age elements to null
+ if (isNaN(year) || year < kMinYear || year > kMaxYear) {
+ yearElem.value = null;
+ ageElem.value = null;
+ datepicker.year = kDefaultYear;
+ return;
+ }
+ else if (aElement == yearElem)
+ datepicker.year = year;
+ // calculate the length of time between the event and now
+ try {
+ var event = new Date(datepicker.year, datepicker.month, datepicker.date);
+ // if the year is only 2 digits, then the year won't be set correctly
+ // using setFullYear fixes this issue
+ event.setFullYear(datepicker.year);
+ // get the difference between today and the event
+ var age = new Date(new Date() - event);
+ // get the number of years of the difference and subtract 1970 (epoch)
+ ageElem.value = age.getFullYear() - 1970;
+ }
+ catch(e) {
+ datepicker.year = kDefaultYear;
+ // if there was an error (like invalid year) set the year and age to null
+ yearElem.value = null;
+ ageElem.value = null;
+ }
+}
+
+/**
+ * Calculates the year an event ocurred based on the number of years, months,
+ * and days since the event and updates the relevant element.
+ * @param aEvent The event calling this method.
+ * @param aElement Optional, but required if this function is not called from an
+ * element's event listener. The element that would call this.
+ */
+function calculateYear(aEvent, aElement) {
+ var yearElem, datepicker;
+ if (aEvent)
+ aElement = this;
+ if (aElement.id == "Age") {
+ datepicker = document.getElementById("Birthday");
+ yearElem = document.getElementById("BirthYear");
+ }
+ if (!datepicker || !yearElem)
+ return;
+
+ // if the age is null, remove the year from the year element, and set the
+ // datepicker to the default year
+ if (!aElement.value) {
+ datepicker.year = kDefaultYear;
+ yearElem.value = null;
+ return;
+ }
+ var today = new Date();
+ try {
+ var date = new Date(aElement.value, datepicker.month, datepicker.date);
+ date.setFullYear(aElement.value);
+ // get the difference between today and the age (the year is offset by 1970)
+ var difference = new Date(today - date);
+ datepicker.year = yearElem.value = difference.getFullYear() - 1970;
+ }
+ // the above code may throw an invalid year exception. If that happens, set
+ // the year to kDefaultYear and set the year element's value to 0
+ catch (e) {
+ datepicker.year = kDefaultYear;
+ // if there was an error (like invalid year) set the year and age to null
+ yearElem.value = null;
+ let ageElem = document.getElementById("Age");
+ if (ageElem)
+ ageElem.value = null;
+ }
+}
+
+/**
+ * Modifies a datepicker in the following ways:
+ * - Removes the scroll arrows
+ * - Hides the year
+ * - Allows the day and month to be blank
+ * NOTE:
+ * The datepicker's date, month, year, and dateValue properties are not always
+ * what appear physically to the user in the datepicker fields.
+ * If any field is blank, the corresponding property is either the previous
+ * value if there was one since the card was opened or the relevant portion of
+ * the current date.
+ *
+ * To get the displayed values, get the value of the individual field, such as
+ * datepicker.yyyyField.value where yyyy is "year", "month", or "date" for the
+ * year, month, and day, respectively.
+ * If the value is null, then the field is blank and vice versa.
+ * @param aDatepicker The datepicker to modify.
+ */
+function modifyDatepicker(aDatepicker) {
+ // collapse the year field and separator
+ aDatepicker.yearField.parentNode.collapsed = true;
+ if (aDatepicker.yearField == aDatepicker._fieldThree ||
+ aDatepicker.yearField == aDatepicker._fieldTwo)
+ aDatepicker._separatorSecond.collapsed = true;
+ else
+ aDatepicker._separatorFirst.collapsed = true;
+ // collapse the spinner element
+ document.getAnonymousElementByAttribute(aDatepicker, "anonid", "buttons")
+ .collapsed = true;
+ // this modified constrain value function ignores values less than the minimum
+ // to let the value be blank (null)
+ // from: mozilla/toolkit/content/widgets/datetimepicker.xml#759
+ aDatepicker._constrainValue = function newConstrainValue(aField, aValue, aNoWrap) {
+ // if the value is less than one, make the field's value null
+ if (aValue < 1) {
+ aField.value = null;
+ return null;
+ }
+ if (aNoWrap && aField == this.monthField)
+ aValue--;
+ // make sure the date is valid for the given month
+ if (aField == this.dateField) {
+ var currentMonth = this.month;
+ var dt = new Date(this.year, currentMonth, aValue);
+ return dt.getMonth() != currentMonth ? 1 : aValue;
+ }
+ var min = (aField == this.monthField) ? 0 : 1;
+ var max = (aField == this.monthField) ? 11 : kMaxYear;
+ // make sure the value isn't too high
+ if (aValue > max)
+ return aNoWrap ? max : min;
+ return aValue;
+ }
+ // sets the specified field to the given value, but allows blank fields
+ // from: mozilla/toolkit/content/widgets/datetimepicker.xml#698
+ aDatepicker._setFieldValue = function setValue(aField, aValue) {
+ if (aField == this.yearField && aValue >= kMinYear && aValue <= kMaxYear) {
+ var oldDate = this._dateValue;
+ this._dateValue.setFullYear(aValue);
+ if (oldDate != this._dateValue) {
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ }
+ }
+ // update the month if the value isn't null
+ else if (aField == this.monthField && aValue != null) {
+ var oldDate = this.date;
+ this._dateValue.setMonth(aValue);
+ if (oldDate != this.date)
+ this._dateValue.setDate(0);
+ this._updateUI(this.dateField, this.date);
+ var date = this._dateValue.getDate();
+ this.dateField.value = date < 10 && this.dateLeadingZero ? "0" + date : date;
+ var month = this._dateValue.getMonth() + 1;
+ this.monthField.value = month < 10 && this.monthLeadingZero ? "0" + month : month;
+ }
+ // update the date if the value isn't null
+ else if (aField == this.dateField && aValue != null) {
+ this._dateValue.setDate(aValue);
+ this._updateUI(this.dateField, this.date);
+ var date = this._dateValue.getDate();
+ this.dateField.value = date < 10 && this.dateLeadingZero ? "0" + date : date;
+ var month = this._dateValue.getMonth() + 1;
+ this.monthField.value = month < 10 && this.monthLeadingZero ? "0" + month : month;
+ }
+ this.setAttribute("value", this.value);
+
+ if (this.attachedControl)
+ this.attachedControl._setValueNoSync(this._dateValue);
+ // if the aField's value is null or 0, set both field's values to null
+ if (!aField.value && aField != this.yearField) {
+ this.dateField.value = null;
+ this.monthField.value = null;
+ }
+ // make the field's value null if aValue is null and the field's value isn't
+ if (aValue == null && aField.value != null)
+ aField.value = null;
+ }
+}
+
+var chatNameFieldIds =
+ ["Yahoo", "Skype", "QQ", "MSN", "ICQ", "XMPP", "IRC"];
+
+/**
+ * Show the 'Chat' tab and focus the first field that has a value, or
+ * the first field if none of them has a value.
+ */
+function showChat()
+{
+ document.getElementById('abTabPanels').parentNode.selectedTab =
+ document.getElementById('chatTabButton');
+ for (let id of chatNameFieldIds) {
+ let elt = document.getElementById(id);
+ if (elt.value) {
+ elt.focus();
+ return;
+ }
+ }
+ document.getElementById(chatNameFieldIds[0]).focus();
+}
+
+/**
+ * Fill in the value of the ChatName readonly field with the first
+ * value of the fields in the Chat tab.
+ */
+function updateChatName()
+{
+ let value = "";
+ for (let id of chatNameFieldIds) {
+ let val = document.getElementById(id).value;
+ if (val) {
+ value = val;
+ break;
+ }
+ }
+ document.getElementById("ChatName").value = value;
+}
+
+/**
+ * Extract the photo information from an nsIAbCard, and populate
+ * the appropriate input fields in the contact editor. If the
+ * nsIAbCard returns an unrecognized PhotoType, the generic
+ * display photo is switched to.
+ *
+ * @param aCard The nsIAbCard to extract the information from.
+ *
+ */
+function loadPhoto(aCard) {
+ var type = aCard.getProperty("PhotoType", "");
+
+ if (!gPhotoHandlers[type] || !gPhotoHandlers[type].onLoad(aCard, document)) {
+ type = "generic";
+ gPhotoHandlers[type].onLoad(aCard, document);
+ }
+
+ document.getElementById("PhotoType").value = type;
+ gPhotoHandlers[type].onShow(aCard, document, "photo");
+}
+
+/**
+ * Event handler for when the user switches the type of
+ * photo for the nsIAbCard being edited. Tries to initiate a
+ * photo download.
+ *
+ * @param aPhotoType {string} The type to switch to
+ * @param aEvent {Event} The event object if used as an event handler
+ */
+function onSwitchPhotoType(aPhotoType, aEvent) {
+ if (!gEditCard)
+ return;
+
+ // Stop event propagation to the radiogroup command event in case that the
+ // child button is pressed. Otherwise, the download is started twice in a row.
+ if (aEvent) {
+ aEvent.stopPropagation();
+ }
+
+ if (aPhotoType) {
+ if (aPhotoType != document.getElementById("PhotoType").value) {
+ document.getElementById("PhotoType").value = aPhotoType;
+ }
+ } else {
+ aPhotoType = document.getElementById("PhotoType").value;
+ }
+
+ if (gPhotoHandlers[aPhotoType]) {
+ if (!gPhotoHandlers[aPhotoType].onRead(gEditCard.card, document)) {
+ onSwitchPhotoType("generic");
+ }
+ }
+}
+
+/**
+ * Removes the photo file at the given path, if present.
+ *
+ * @param aName The name of the photo to remove from the Photos directory.
+ * 'null' value is allowed and means to remove no file.
+ *
+ * @return {boolean} True if the file was deleted, false otherwise.
+ */
+function removePhoto(aName) {
+ if (!aName)
+ return false;
+ // Get the directory with all the photos
+ var file = getPhotosDir();
+ // Get the photo (throws an exception for invalid names)
+ try {
+ file.append(aName);
+ file.remove(false);
+ return true;
+ }
+ catch (e) {}
+ return false;
+}
+
+/**
+ * Remove previous and temporary photo files from the Photos directory.
+ *
+ * @param aSaved {boolean} Whether the new card is going to be saved/committed.
+ */
+function purgeOldPhotos(aSaved = true) {
+ // If photo was changed, the array contains at least one member, the original photo.
+ while (gOldPhotos.length > 0) {
+ let photoName = gOldPhotos.pop();
+ if (!aSaved && (gOldPhotos.length == 0)) {
+ // If the saving was cancelled, we want to keep the original photo of the card.
+ break;
+ }
+ removePhoto(photoName);
+ }
+
+ if (aSaved) {
+ // The new photo should stay so we clear the reference to it.
+ gNewPhoto = null;
+ } else {
+ // Changes to card not saved, we don't need the new photo.
+ // It may be null when there was no change of it.
+ removePhoto(gNewPhoto);
+ }
+}
+
+/**
+ * Opens a file picker with image filters to look for a contact photo.
+ * If the user selects a file and clicks OK then the PhotoURI textbox is set
+ * with a file URI pointing to that file and updatePhoto is called.
+ *
+ * @param aEvent {Event} The event object if used as an event handler.
+ */
+function browsePhoto(aEvent) {
+ // Stop event propagation to the radiogroup command event in case that the
+ // child button is pressed. Otherwise, the download is started twice in a row.
+ if (aEvent)
+ aEvent.stopPropagation();
+
+ let fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(Ci.nsIFilePicker);
+ fp.init(window, gAddressBookBundle.getString("browsePhoto"),
+ Ci.nsIFilePicker.modeOpen);
+
+ // Open the directory of the currently chosen photo (if any)
+ let currentPhotoFile = document.getElementById("PhotoFile").file
+ if (currentPhotoFile) {
+ fp.displayDirectory = currentPhotoFile.parent;
+ }
+
+ // Add All Files & Image Files filters and select the latter
+ fp.appendFilters(Ci.nsIFilePicker.filterImages);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+
+ fp.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+ document.getElementById("PhotoFile").file = fp.file;
+ onSwitchPhotoType("file");
+ });
+}
+
+/**
+ * Handlers to add drag and drop support.
+ */
+function checkDropPhoto(aEvent) {
+ // Just allow anything to be dropped. Different types of data are handled
+ // in doDropPhoto() below.
+ aEvent.preventDefault();
+}
+
+function doDropPhoto(aEvent) {
+ aEvent.preventDefault();
+
+ let photoType = "";
+
+ // Check if a file has been dropped.
+ let file = aEvent.dataTransfer.mozGetDataAt("application/x-moz-file", 0);
+ if (file instanceof Ci.nsIFile) {
+ photoType = "file";
+ document.getElementById("PhotoFile").file = file;
+ } else {
+ // Check if a URL has been dropped.
+ let link = aEvent.dataTransfer.getData("URL");
+ if (link) {
+ photoType = "web";
+ document.getElementById("PhotoURI").value = link;
+ } else {
+ // Check if dropped text is a URL.
+ link = aEvent.dataTransfer.getData("text/plain");
+ if (/^(ftps?|https?):\/\//i.test(link)) {
+ photoType = "web";
+ document.getElementById("PhotoURI").value = link;
+ }
+ }
+ }
+
+ onSwitchPhotoType(photoType);
+}
+
+/**
+ * Self-contained object to manage the user interface used for downloading
+ * and storing contact photo images.
+ */
+var gPhotoDownloadUI = (function() {
+ // UI DOM elements
+ let elProgressbar;
+ let elProgressLabel;
+ let elPhotoType;
+ let elProgressContainer;
+
+ window.addEventListener("load", function load(event) {
+ if (!elProgressbar)
+ elProgressbar = document.getElementById("PhotoDownloadProgress");
+ if (!elProgressLabel)
+ elProgressLabel = document.getElementById("PhotoStatus");
+ if (!elPhotoType)
+ elPhotoType = document.getElementById("PhotoType");
+ if (!elProgressContainer)
+ elProgressContainer = document.getElementById("ProgressContainer");
+ }, false);
+
+ function onStart() {
+ elProgressContainer.setAttribute("class", "expanded");
+ elProgressLabel.value = "";
+ elProgressbar.hidden = false;
+ elProgressbar.value = 3; // Start with a tiny visible progress
+ }
+
+ function onSuccess() {
+ elProgressLabel.value = "";
+ elProgressContainer.setAttribute("class", "");
+ }
+
+ function onError(state) {
+ let msg;
+ switch (state) {
+ case gImageDownloader.ERROR_INVALID_URI:
+ msg = gAddressBookBundle.getString("errorInvalidUri");
+ break;
+ case gImageDownloader.ERROR_UNAVAILABLE:
+ msg = gAddressBookBundle.getString("errorNotAvailable");
+ break;
+ case gImageDownloader.ERROR_INVALID_IMG:
+ msg = gAddressBookBundle.getString("errorInvalidImage");
+ break;
+ case gImageDownloader.ERROR_SAVE:
+ msg = gAddressBookBundle.getString("errorSaveOperation");
+ break;
+ }
+ if (msg) {
+ elProgressLabel.value = msg;
+ elProgressbar.hidden = true;
+ onSwitchPhotoType("generic");
+ }
+ }
+
+ function onProgress(state, percent) {
+ elProgressbar.value = percent;
+ elProgressLabel.value = gAddressBookBundle.getString("stateImageSave");
+ }
+
+ return {
+ onStart: onStart,
+ onSuccess: onSuccess,
+ onError: onError,
+ onProgress: onProgress
+ }
+})();
+
+/* A photo handler defines the behaviour of the contact editor
+ * for a particular photo type. Each photo handler must implement
+ * the following interface:
+ *
+ * onLoad: function(aCard, aDocument):
+ * Called when the editor wants to populate the contact editor
+ * input fields with information about aCard's photo. Note that
+ * this does NOT make aCard's photo appear in the contact editor -
+ * this is left to the onShow function. Returns true on success.
+ * If the function returns false, the generic photo handler onLoad
+ * function will be called.
+ *
+ * onShow: function(aCard, aDocument, aTargetID):
+ * Called when the editor wants to show this photo type.
+ * The onShow method should take the input fields in the document,
+ * and render the requested photo in the IMG tag with id
+ * aTargetID. Note that onShow does NOT save the photo for aCard -
+ * this job is left to the onSave function. Returns true on success.
+ * If the function returns false, the generic photo handler onShow
+ * function will be called.
+ *
+ * onRead: function(aCard, aDocument)
+ * Called when the editor wants to read the user supplied new photo.
+ * The onRead method is responsible for analyzing the photo of this
+ * type requested by the user, and storing it, as well as the
+ * other fields required by onLoad/onShow to retrieve and display
+ * the photo again. Returns true on success. If the function
+ * returns false, the generic photo handler onRead function will
+ * be called.
+ *
+ * onSave: function(aCard, aDocument)
+ * Called when the editor wants to save this photo type to the card.
+ * Returns true on success.
+ */
+
+var gGenericPhotoHandler = {
+ onLoad: function(aCard, aDocument) {
+ return true;
+ },
+
+ onShow: function(aCard, aDocument, aTargetID) {
+ // XXX TODO: this ignores any other value from the generic photos
+ // menulist than "default".
+ aDocument.getElementById(aTargetID)
+ .setAttribute("src", defaultPhotoURI);
+ return true;
+ },
+
+ onRead: function(aCard, aDocument) {
+ gPhotoDownloadUI.onSuccess();
+
+ newPhotoAdded("", aCard);
+
+ gGenericPhotoHandler.onShow(aCard, aDocument, "photo");
+ return true;
+ },
+
+ onSave: function(aCard, aDocument) {
+ // XXX TODO: this ignores any other value from the generic photos
+ // menulist than "default".
+
+ // Update contact
+ aCard.setProperty("PhotoName", "");
+ aCard.setProperty("PhotoURI", "");
+ return true;
+ }
+};
+
+var gFilePhotoHandler = {
+
+ onLoad: function(aCard, aDocument) {
+ let photoURI = aCard.getProperty("PhotoURI", "");
+ let file;
+ try {
+ // The original file may not exist anymore, but we still display it.
+ file = Services.io.newURI(photoURI)
+ .QueryInterface(Ci.nsIFileURL)
+ .file;
+ } catch (e) {}
+
+ if (!file)
+ return false;
+
+ aDocument.getElementById("PhotoFile").file = file;
+ return true;
+ },
+
+ onShow: function(aCard, aDocument, aTargetID) {
+ let photoName = gNewPhoto || aCard.getProperty("PhotoName", null);
+ let photoURI = getPhotoURI(photoName);
+ aDocument.getElementById(aTargetID).setAttribute("src", photoURI);
+ return true;
+ },
+
+ onRead: function(aCard, aDocument) {
+ let file = aDocument.getElementById("PhotoFile").file;
+ if (!file)
+ return false;
+
+ // If the local file has been removed/renamed, keep the current photo as is.
+ if (!file.exists() || !file.isFile())
+ return false;
+
+ let photoURI = Services.io.newFileURI(file).spec;
+
+ gPhotoDownloadUI.onStart();
+
+ let cbSuccess = function(newPhotoName) {
+ gPhotoDownloadUI.onSuccess();
+
+ newPhotoAdded(newPhotoName, aCard);
+ aDocument.getElementById("PhotoFile").setAttribute("PhotoURI", photoURI);
+
+ gFilePhotoHandler.onShow(aCard, aDocument, "photo");
+ };
+
+ gImageDownloader.savePhoto(photoURI, cbSuccess,
+ gPhotoDownloadUI.onError,
+ gPhotoDownloadUI.onProgress);
+ return true;
+ },
+
+ onSave: function(aCard, aDocument) {
+ // Update contact
+ if (gNewPhoto) {
+ // The file may not be valid unless the photo has changed.
+ let photoURI = aDocument.getElementById("PhotoFile").getAttribute("PhotoURI");
+ aCard.setProperty("PhotoName", gNewPhoto);
+ aCard.setProperty("PhotoURI", photoURI);
+ }
+ return true;
+ }
+};
+
+var gWebPhotoHandler = {
+ onLoad: function(aCard, aDocument) {
+ let photoURI = aCard.getProperty("PhotoURI", null);
+
+ if (!photoURI)
+ return false;
+
+ aDocument.getElementById("PhotoURI").value = photoURI;
+ return true;
+ },
+
+ onShow: function(aCard, aDocument, aTargetID) {
+ let photoName = gNewPhoto || aCard.getProperty("PhotoName", null);
+ if (!photoName)
+ return false;
+
+ let photoURI = getPhotoURI(photoName);
+
+ aDocument.getElementById(aTargetID).setAttribute("src", photoURI);
+ return true;
+ },
+
+ onRead: function(aCard, aDocument) {
+ let photoURI = aDocument.getElementById("PhotoURI").value;
+ if (!photoURI)
+ return false;
+
+ gPhotoDownloadUI.onStart();
+
+ let cbSuccess = function(newPhotoName) {
+ gPhotoDownloadUI.onSuccess();
+
+ newPhotoAdded(newPhotoName, aCard);
+
+ gWebPhotoHandler.onShow(aCard, aDocument, "photo");
+
+ }
+
+ gImageDownloader.savePhoto(photoURI, cbSuccess,
+ gPhotoDownloadUI.onError,
+ gPhotoDownloadUI.onProgress);
+ return true;
+ },
+
+ onSave: function(aCard, aDocument) {
+ // Update contact
+ if (gNewPhoto) {
+ let photoURI = aDocument.getElementById("PhotoURI").value;
+ aCard.setProperty("PhotoName", gNewPhoto);
+ aCard.setProperty("PhotoURI", photoURI);
+ }
+ return true;
+ }
+};
+
+function newPhotoAdded(aPhotoName, aCard) {
+ // If we had the photo saved locally, shedule it for removal if card is saved.
+ gOldPhotos.push(gNewPhoto !== null ? gNewPhoto : aCard.getProperty("PhotoName", null));
+ gNewPhoto = aPhotoName;
+}
+
+/* In order for other photo handlers to be recognized for
+ * a particular type, they must be registered through this
+ * function.
+ * @param aType the type of photo to handle
+ * @param aPhotoHandler the photo handler to register
+ */
+function registerPhotoHandler(aType, aPhotoHandler)
+{
+ gPhotoHandlers[aType] = aPhotoHandler;
+}
+
+registerPhotoHandler("generic", gGenericPhotoHandler);
+registerPhotoHandler("web", gWebPhotoHandler);
+registerPhotoHandler("file", gFilePhotoHandler);
diff --git a/comm/suite/mailnews/components/addrbook/content/abCardOverlay.xul b/comm/suite/mailnews/components/addrbook/content/abCardOverlay.xul
new file mode 100644
index 0000000000..40287e8411
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abCardOverlay.xul
@@ -0,0 +1,515 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/cardDialog.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/addressbook/abCardOverlay.dtd">
+
+<overlay id="editcardOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+</stringbundleset>
+
+<script src="chrome://messenger/content/addressbook/abCommon.js"/>
+<script src="chrome://messenger/content/addressbook/abCardOverlay.js"/>
+
+<vbox id="editcard">
+ <tabbox>
+ <tabs id="abTabs">
+ <tab id="contactTabButton" label="&Contact.tab;"
+ accesskey="&Contact.accesskey;"/>
+ <tab id="homeTabButton" label="&Home.tab;" accesskey="&Home.accesskey;"/>
+ <tab id="workTabButton" label="&Work.tab;" accesskey="&Work.accesskey;"/>
+ <tab id="otherTabButton" label="&Other.tab;" accesskey="&Other.accesskey;"/>
+ <tab id="chatTabButton" label="&Chat.tab;" accesskey="&Chat.accesskey;"/>
+ <tab id="photoTabButton" label="&Photo.tab;" accesskey="&Photo.accesskey;"/>
+ </tabs>
+
+ <tabpanels id="abTabPanels" flex="1">
+ <!-- ** Name Tab ** -->
+ <!-- The following vbox contains two hboxes
+ top: Name/Email/Phonenumber bottom: Email prefs. -->
+ <vbox id="abNameTab" >
+ <!-- This hbox contains two vboxes
+ left: Name/Email, right: Phonenumbers -->
+ <hbox>
+ <vbox id="namesAndEmailAddresses">
+ <!-- This box contains the Names and Emailnames -->
+
+ <!-- LOCALIZATION NOTE:
+ NameField1, NameField2, PhoneticField1, PhoneticField2
+ those fields are either LN or FN depends on the target country.
+ They are configurable in the .dtd file.
+ -->
+
+ <hbox id="NameField1Container" align="center">
+ <spacer flex="1"/>
+ <label control="&NameField1.id;" value="&NameField1.label;"
+ accesskey="&NameField1.accesskey;"/>
+ <hbox class="CardEditWidth" align="center">
+ <textbox id="&NameField1.id;" flex="1"
+ oninput="GenerateDisplayName()"/>
+
+ <!-- LOCALIZATION NOTE:
+ Fields for phonetic are disabled as default and can be
+ enabled by^editing "mail.addr_book.show_phonetic_fields"
+ -->
+
+ <spacer id="PhoneticSpacer1" flex="1" hidden="true"/>
+ <label id="PhoneticLabel1" control="&PhoneticField1.id;"
+ value="&PhoneticField1.label;" hidden="true"/>
+ <textbox id="&PhoneticField1.id;" flex="1" hidden="true"/>
+ </hbox>
+ </hbox>
+ <hbox id="NameField2Container" align="center">
+ <spacer flex="1"/>
+ <label control="&NameField2.id;" value="&NameField2.label;"
+ accesskey="&NameField2.accesskey;"/>
+ <hbox class="CardEditWidth" align="center">
+ <textbox id="&NameField2.id;" flex="1"
+ oninput="GenerateDisplayName()"/>
+
+ <!-- LOCALIZATION NOTE:
+ Fields for phonetic are disabled as default and can be
+ enabled by editing "mail.addr_book.show_phonetic_fields"
+ -->
+
+ <spacer id="PhoneticSpacer2" flex="1" hidden="true"/>
+ <label id="PhoneticLabel2" control="&PhoneticField2.id;"
+ value="&PhoneticField2.label;" hidden="true"/>
+ <textbox id="&PhoneticField2.id;" flex="1" hidden="true"/>
+ </hbox>
+ </hbox>
+ <hbox id="DisplayNameContainer" align="center">
+ <spacer flex="1"/>
+ <label control="DisplayName" value="&DisplayName.label;"
+ accesskey="&DisplayName.accesskey;" />
+ <hbox class="CardEditWidth">
+ <textbox id="DisplayName" flex="1"
+ oninput="DisplayNameChanged()"/>
+ </hbox>
+ </hbox>
+ <hbox id="PreferDisplayNameContainer" align="center">
+ <spacer flex="1"/>
+ <hbox class="CardEditWidth">
+ <checkbox id="preferDisplayName"
+ label="&preferDisplayName.label;"
+ accesskey="&preferDisplayName2.accesskey;"/>
+ </hbox>
+ </hbox>
+
+ <hbox id="NickNameContainer" align="center">
+ <spacer flex="1"/>
+ <label control="NickName" value="&NickName.label;"
+ accesskey="&NickName.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="NickName" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="PrimaryEmailContainer" align="center">
+ <spacer flex="1"/>
+ <label control="PrimaryEmail" value="&PrimaryEmail.label;"
+ accesskey="&PrimaryEmail.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="PrimaryEmail" flex="1" class="uri-element"/>
+ </hbox>
+ </hbox>
+ <hbox id="SecondaryEmailContainer" align="center">
+ <spacer flex="1"/>
+ <label control="SecondEmail" value="&SecondEmail.label;"
+ accesskey="&SecondEmail.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="SecondEmail" flex="1" class="uri-element"/>
+ </hbox>
+ </hbox>
+ <hbox id="ScreenNameContainer" align="center">
+ <spacer flex="1"/>
+ <label class="text-link" value="&chatName.label;"
+ onclick="showChat();"/>
+ <hbox class="CardEditWidth">
+ <textbox id="ChatName" readonly="true" flex="1"
+ onclick="showChat();"/>
+ </hbox>
+ </hbox>
+ </vbox> <!-- End of Name/Email -->
+ <!-- Phone Number section -->
+ <vbox id="PhoneNumbers">
+ <hbox id="WorkPhoneContainer" align="center">
+ <spacer flex="1"/>
+ <label control="WorkPhone" value="&WorkPhone.label;"
+ accesskey="&WorkPhone.accesskey;" />
+ <textbox id="WorkPhone" class="PhoneEditWidth"/>
+ </hbox>
+ <hbox id="HomePhoneContainer" align="center">
+ <spacer flex="1"/>
+ <label control="HomePhone" value="&HomePhone.label;"
+ accesskey="&HomePhone.accesskey;"/>
+ <textbox id="HomePhone" class="PhoneEditWidth"/>
+ </hbox>
+ <hbox id="FaxNumberContainer" align="center">
+ <spacer flex="1"/>
+ <label control="FaxNumber" value="&FaxNumber.label;"
+ accesskey="&FaxNumber.accesskey;"/>
+ <textbox id="FaxNumber" class="PhoneEditWidth"/>
+ </hbox>
+ <hbox id="PagerNumberContainer" align="center">
+ <spacer flex="1"/>
+ <label control="PagerNumber" value="&PagerNumber.label;"
+ accesskey="&PagerNumber.accesskey;"/>
+ <textbox id="PagerNumber" class="PhoneEditWidth"/>
+ </hbox>
+ <hbox id="CellularNumberContainer" align="center">
+ <spacer flex="1"/>
+ <label control="CellularNumber" value="&CellularNumber.label;"
+ accesskey="&CellularNumber.accesskey;"/>
+ <textbox id="CellularNumber" class="PhoneEditWidth"/>
+ </hbox>
+ </vbox> <!-- End of Phonenumbers -->
+ </hbox> <!-- End of Name/Email/Phonenumbers -->
+ <!-- Email Preferences -->
+ <hbox>
+ <vbox valign="middle">
+ <label control="PreferMailFormatPopup"
+ value="&PreferMailFormat.label;"
+ accesskey="&PreferMailFormat.accesskey;"/>
+ </vbox>
+ <menulist id="PreferMailFormatPopup">
+ <menupopup>
+ <!-- 0,1,2 come from nsIAbPreferMailFormat in nsIAbCard.idl -->
+ <menuitem value="0" label="&Unknown.label;"/>
+ <menuitem value="1" label="&PlainText.label;"/>
+ <menuitem value="2" label="&HTML.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox> <!-- End of Name Tab -->
+
+ <!-- ** Home Address Tab ** -->
+ <vbox id="abHomeTab" >
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="HomeAddress" value="&HomeAddress.label;"
+ accesskey="&HomeAddress.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="HomeAddress" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="HomeAddress2" value="&HomeAddress2.label;"
+ accesskey="&HomeAddress2.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="HomeAddress2" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="HomeCityContainer" align="center">
+ <spacer flex="1"/>
+ <label control="HomeCity" value="&HomeCity.label;"
+ accesskey="&HomeCity.accesskey;"/>
+ <hbox id="HomeCityFieldContainer"
+ class="AddressCardEditWidth"
+ align="center">
+ <textbox id="HomeCity" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="HomeState" value="&HomeState.label;"
+ accesskey="&HomeState.accesskey;"/>
+ <hbox align="center" class="AddressCardEditWidth">
+ <textbox id="HomeState" flex="1"/>
+ <spacer class="stateZipSpacer"/>
+ <label control="HomeZipCode" value="&HomeZipCode.label;"
+ accesskey="&HomeZipCode.accesskey;"/>
+ <textbox id="HomeZipCode" class="ZipWidth"/>
+ </hbox>
+ </hbox>
+ <hbox align="center">
+ <spacer flex="1"/>
+ <label control="HomeCountry" value="&HomeCountry.label;"
+ accesskey="&HomeCountry.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="HomeCountry" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WebPage2Container" align="center">
+ <spacer flex="1"/>
+ <label control="WebPage2" value="&HomeWebPage.label;"
+ accesskey="&HomeWebPage.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="WebPage2" flex="1" class="uri-element"/>
+ </hbox>
+ </hbox>
+ <hbox id="birthdayField" align="center">
+ <spacer flex="1"/>
+ <label control="Birthday" value="&Birthday.label;"
+ accesskey="&Birthday.accesskey;"/>
+ <hbox class="AddressCardEditWidth" align="center">
+ <!-- NOTE: This datepicker is modified.
+ See abCardOverlay.js for details-->
+ <datepicker id="Birthday" type="popup"/>
+ <label value="&In.label;"/>
+ <textbox id="BirthYear" maxlength="4"
+ placeholder="&Year.placeholder;" class="YearWidth" />
+ <label control="Age" value="&Or.value;"/>
+ <textbox id="Age" maxlength="4"
+ placeholder="&Age.placeholder;" class="YearWidth" />
+ <label value="&YearsOld.label;"/>
+ <spacer flex="1"/>
+ </hbox>
+ </hbox>
+ </vbox>
+
+ <!-- ** Business Address Tab ** -->
+ <vbox id="abBusinessTab" >
+ <hbox id="JobTitleDepartmentContainer" align="center">
+ <spacer flex="1"/>
+ <label control="JobTitle" value="&JobTitle.label;"
+ accesskey="&JobTitle.accesskey;"/>
+ <hbox class="AddressCardEditWidth" align="center">
+ <textbox id="JobTitle" flex="1"/>
+ <label control="Department" value="&Department.label;"
+ accesskey="&Department.accesskey;"/>
+ <textbox id="Department" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="CompanyContainer" align="center">
+ <spacer flex="1"/>
+ <label control="Company" value="&Company.label;"
+ accesskey="&Company.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="Company" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WorkAddressContainer" align="center">
+ <spacer flex="1"/>
+ <label control="WorkAddress" value="&WorkAddress.label;"
+ accesskey="&WorkAddress.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="WorkAddress" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WorkAddress2Container" align="center">
+ <spacer flex="1"/>
+ <label control="WorkAddress2" value="&WorkAddress2.label;"
+ accesskey="&WorkAddress2.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="WorkAddress2" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WorkCityContainer" align="center">
+ <spacer flex="1"/>
+ <label control="WorkCity" value="&WorkCity.label;"
+ accesskey="&WorkCity.accesskey;"/>
+ <hbox id="WorkCityFieldContainer"
+ class="AddressCardEditWidth"
+ align="center">
+ <textbox id="WorkCity" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WorkStateZipContainer" align="center">
+ <spacer flex="1"/>
+ <label control="WorkState" value="&WorkState.label;"
+ accesskey="&WorkState.accesskey;"/>
+ <hbox class="AddressCardEditWidth" align="center">
+ <textbox id="WorkState" flex="1"/>
+ <spacer class="stateZipSpacer"/>
+ <label control="WorkZipCode" value="&WorkZipCode.label;"
+ accesskey="&WorkZipCode.accesskey;"/>
+ <textbox id="WorkZipCode" class="ZipWidth"/>
+ </hbox>
+ </hbox>
+ <hbox id="WorkCountryContainer" align="center">
+ <spacer flex="1"/>
+ <label control="WorkCountry" value="&WorkCountry.label;"
+ accesskey="&WorkCountry.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="WorkCountry" flex="1"/>
+ </hbox>
+ </hbox>
+ <hbox id="WebPage1Container" align="center">
+ <spacer flex="1"/>
+ <label control="WebPage1" value="&WorkWebPage.label;"
+ accesskey="&WorkWebPage.accesskey;"/>
+ <hbox class="AddressCardEditWidth">
+ <textbox id="WebPage1" flex="1" class="uri-element"/>
+ </hbox>
+ </hbox>
+ </vbox>
+
+ <!-- ** Other Tab ** -->
+ <vbox id="abOtherTab" >
+ <vbox id="customFields">
+ <hbox flex="1" align="center">
+ <label control="Custom1" value="&Custom1.label;"
+ accesskey="&Custom1.accesskey;"/>
+ <textbox id="Custom1" flex="1"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <label control="Custom2" value="&Custom2.label;"
+ accesskey="&Custom2.accesskey;"/>
+ <textbox id="Custom2" flex="1"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <label control="Custom3" value="&Custom3.label;"
+ accesskey="&Custom3.accesskey;"/>
+ <textbox id="Custom3" flex="1"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <label control="Custom4" value="&Custom4.label;"
+ accesskey="&Custom4.accesskey;"/>
+ <textbox id="Custom4" flex="1"/>
+ </hbox>
+ </vbox>
+ <label control="Notes" value="&Notes.label;"
+ accesskey="&Notes.accesskey;"/>
+ <textbox id="Notes" multiline="true" wrap="virtual" flex="1"/>
+ </vbox>
+
+ <!-- ** Chat Tab ** -->
+ <hbox id="abChatTab">
+ <vbox>
+ <hbox id="YahooContainer" align="center">
+ <spacer flex="1"/>
+ <label control="Yahoo" value="&Yahoo.label;"
+ accesskey="&Yahoo.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="Yahoo" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="SkypeContainer" align="center">
+ <spacer flex="1"/>
+ <label control="Skype" value="&Skype.label;"
+ accesskey="&Skype.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="Skype" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="QQContainer" align="center">
+ <spacer flex="1"/>
+ <label control="QQ" value="&QQ.label;"
+ accesskey="&QQ.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="QQ" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="MSNContainer" align="center">
+ <spacer flex="1"/>
+ <label control="MSN" value="&MSN.label;"
+ accesskey="&MSN.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="MSN" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="ICQContainer" align="center">
+ <spacer flex="1"/>
+ <label control="ICQ" value="&ICQ.label;"
+ accesskey="&ICQ.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="ICQ" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="XMPPContainer" align="center">
+ <spacer flex="1"/>
+ <label control="XMPP" value="&XMPP.label;"
+ accesskey="&XMPP.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="XMPP" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ <hbox id="IRCContainer" align="center">
+ <spacer flex="1"/>
+ <label control="IRC" value="&IRC.label;"
+ accesskey="&IRC.accesskey;"/>
+ <hbox class="CardEditWidth">
+ <textbox id="IRC" flex="1" onchange="updateChatName();"/>
+ </hbox>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- ** Photo Tab ** -->
+ <hbox id="abPhotoTab">
+ <vbox align="center" id="PhotoContainer"
+ style="height: 25ch; width: 25ch;"
+ ondrop="doDropPhoto(event);"
+ ondragenter="checkDropPhoto(event);"
+ ondragover="checkDropPhoto(event);">
+ <image id="photo" style="max-height: 25ch; max-width: 25ch;"/>
+ <hbox id="PhotoDropTarget" flex="1" pack="center" align="center">
+ <description>&PhotoDropTarget.label;</description>
+ </hbox>
+ </vbox>
+
+ <vbox flex="1">
+ <radiogroup id="PhotoType" oncommand="onSwitchPhotoType();">
+ <vbox id="GenericPhotoContainer">
+ <radio id="GenericPhotoType"
+ value="generic"
+ label="&GenericPhoto.label;"
+ accesskey="&GenericPhoto.accesskey;"/>
+ <menulist id="GenericPhotoList"
+ class="indent"
+ flex="1"
+ oncommand="onSwitchPhotoType('generic', event);">
+ <menupopup>
+ <menuitem label="&DefaultPhoto.label;"
+ selected="true"
+ value="default"/>
+ </menupopup>
+ </menulist>
+ </vbox>
+
+ <vbox id="FilePhotoContainer">
+ <radio id="FilePhotoType"
+ value="file"
+ label="&PhotoFile.label;"
+ accesskey="&PhotoFile.accesskey;"/>
+ <hbox class="indent">
+ <filefield id="PhotoFile"
+ readonly="true"
+ maxlength="255"
+ flex="1"/>
+ <button id="BrowsePhoto"
+ label="&BrowsePhoto.label;"
+ accesskey="&BrowsePhoto.accesskey;"
+ oncommand="browsePhoto(event);"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="WebPhotoContainer">
+ <radio id="WebPhotoType"
+ value="web"
+ label="&PhotoURL.label;"
+ accesskey="&PhotoURL.accesskey;"/>
+ <hbox class="indent">
+ <textbox id="PhotoURI"
+ maxlength="255"
+ flex="1"
+ placeholder="&PhotoURL.placeholder;"/>
+ <button id="UpdatePhoto"
+ label="&UpdatePhoto.label;"
+ accesskey="&UpdatePhoto.accesskey;"
+ oncommand="onSwitchPhotoType('web', event);"/>
+ </hbox>
+ </vbox>
+ </radiogroup>
+ <hbox id="ProgressContainer" align="begin">
+ <label id="PhotoStatus"/>
+ <spacer flex="2"/>
+ <progressmeter id="PhotoDownloadProgress"
+ value="0"
+ mode="determined"
+ hidden="true"
+ flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ </tabpanels>
+ </tabbox>
+</vbox>
+</overlay>
diff --git a/comm/suite/mailnews/components/addrbook/content/abCardViewOverlay.js b/comm/suite/mailnews/components/addrbook/content/abCardViewOverlay.js
new file mode 100644
index 0000000000..e9edfbfbe4
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abCardViewOverlay.js
@@ -0,0 +1,527 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//NOTE: gAddressBookBundle must be defined and set or this Overlay won't work
+
+var gMapItURLFormat;
+
+var gPhotoDisplayHandlers = {};
+
+var zListName;
+var zPrimaryEmail;
+var zSecondaryEmail;
+var zNickname;
+var zDisplayName;
+var zWork;
+var zHome;
+var zFax;
+var zCellular;
+var zPager;
+var zBirthday;
+var zCustom1;
+var zCustom2;
+var zCustom3;
+var zCustom4;
+var zYahoo;
+var zSkype;
+var zQQ;
+var zMSN;
+var zICQ;
+var zXMPP;
+var zIRC;
+
+var cvData;
+
+function OnLoadCardView()
+{
+ gMapItURLFormat = GetLocalizedStringPref("mail.addr_book.mapit_url.format");
+
+ zPrimaryEmail = gAddressBookBundle.getString("propertyPrimaryEmail");
+ zSecondaryEmail = gAddressBookBundle.getString("propertySecondaryEmail");
+ zNickname = gAddressBookBundle.getString("propertyNickname");
+ zDisplayName = gAddressBookBundle.getString("propertyDisplayName");
+ zListName = gAddressBookBundle.getString("propertyListName");
+ zWork = gAddressBookBundle.getString("propertyWork");
+ zHome = gAddressBookBundle.getString("propertyHome");
+ zFax = gAddressBookBundle.getString("propertyFax");
+ zCellular = gAddressBookBundle.getString("propertyCellular");
+ zPager = gAddressBookBundle.getString("propertyPager");
+ zBirthday = gAddressBookBundle.getString("propertyBirthday");
+ zCustom1 = gAddressBookBundle.getString("propertyCustom1");
+ zCustom2 = gAddressBookBundle.getString("propertyCustom2");
+ zCustom3 = gAddressBookBundle.getString("propertyCustom3");
+ zCustom4 = gAddressBookBundle.getString("propertyCustom4");
+ zYahoo = gAddressBookBundle.getString("propertyYahoo");
+ zSkype = gAddressBookBundle.getString("propertySkype");
+ zQQ = gAddressBookBundle.getString("propertyQQ");
+ zMSN = gAddressBookBundle.getString("propertyMSN");
+ zICQ = gAddressBookBundle.getString("propertyICQ");
+ zXMPP = gAddressBookBundle.getString("propertyXMPP");
+ zIRC = gAddressBookBundle.getString("propertyIRC");
+
+ var doc = document;
+
+ /* data for address book, prefixes: "cvb" = card view box
+ "cvh" = crad view header
+ "cv" = card view (normal fields) */
+ cvData = new Object;
+
+ // Card View Box
+ cvData.CardViewBox = doc.getElementById("CardViewInnerBox");
+ // Title
+ cvData.CardTitle = doc.getElementById("CardTitle");
+ // Name section
+ cvData.cvbContact = doc.getElementById("cvbContact");
+ cvData.cvhContact = doc.getElementById("cvhContact");
+ cvData.cvNickname = doc.getElementById("cvNickname");
+ cvData.cvDisplayName = doc.getElementById("cvDisplayName");
+ cvData.cvEmail1Box = doc.getElementById("cvEmail1Box");
+ cvData.cvEmail1 = doc.getElementById("cvEmail1");
+ cvData.cvBuddyIcon = doc.getElementById("cvBuddyIcon");
+ cvData.cvListNameBox = doc.getElementById("cvListNameBox");
+ cvData.cvListName = doc.getElementById("cvListName");
+ cvData.cvEmail2Box = doc.getElementById("cvEmail2Box");
+ cvData.cvEmail2 = doc.getElementById("cvEmail2");
+ // Home section
+ cvData.cvbHome = doc.getElementById("cvbHome");
+ cvData.cvhHome = doc.getElementById("cvhHome");
+ cvData.cvHomeAddress = doc.getElementById("cvHomeAddress");
+ cvData.cvHomeAddress2 = doc.getElementById("cvHomeAddress2");
+ cvData.cvHomeCityStZip = doc.getElementById("cvHomeCityStZip");
+ cvData.cvHomeCountry = doc.getElementById("cvHomeCountry");
+ cvData.cvbHomeMapItBox = doc.getElementById("cvbHomeMapItBox");
+ cvData.cvHomeMapIt = doc.getElementById("cvHomeMapIt");
+ cvData.cvHomeWebPageBox = doc.getElementById("cvHomeWebPageBox");
+ cvData.cvHomeWebPage = doc.getElementById("cvHomeWebPage");
+ // Other section
+ cvData.cvbOther = doc.getElementById("cvbOther");
+ cvData.cvBirthday = doc.getElementById("cvBirthday");
+ cvData.cvhOther = doc.getElementById("cvhOther");
+ cvData.cvCustom1 = doc.getElementById("cvCustom1");
+ cvData.cvCustom2 = doc.getElementById("cvCustom2");
+ cvData.cvCustom3 = doc.getElementById("cvCustom3");
+ cvData.cvCustom4 = doc.getElementById("cvCustom4");
+ cvData.cvNotes = doc.getElementById("cvNotes");
+ // Description section (mailing lists only)
+ cvData.cvbDescription = doc.getElementById("cvbDescription");
+ cvData.cvhDescription = doc.getElementById("cvhDescription");
+ cvData.cvDescription = doc.getElementById("cvDescription");
+ // Addresses section (mailing lists only)
+ cvData.cvbAddresses = doc.getElementById("cvbAddresses");
+ cvData.cvhAddresses = doc.getElementById("cvhAddresses");
+ cvData.cvAddresses = doc.getElementById("cvAddresses");
+ // Phone section
+ cvData.cvbPhone = doc.getElementById("cvbPhone");
+ cvData.cvhPhone = doc.getElementById("cvhPhone");
+ cvData.cvPhWork = doc.getElementById("cvPhWork");
+ cvData.cvPhHome = doc.getElementById("cvPhHome");
+ cvData.cvPhFax = doc.getElementById("cvPhFax");
+ cvData.cvPhCellular = doc.getElementById("cvPhCellular");
+ cvData.cvPhPager = doc.getElementById("cvPhPager");
+ // Work section
+ cvData.cvbWork = doc.getElementById("cvbWork");
+ cvData.cvhWork = doc.getElementById("cvhWork");
+ cvData.cvJobTitle = doc.getElementById("cvJobTitle");
+ cvData.cvDepartment = doc.getElementById("cvDepartment");
+ cvData.cvCompany = doc.getElementById("cvCompany");
+ cvData.cvWorkAddress = doc.getElementById("cvWorkAddress");
+ cvData.cvWorkAddress2 = doc.getElementById("cvWorkAddress2");
+ cvData.cvWorkCityStZip = doc.getElementById("cvWorkCityStZip");
+ cvData.cvWorkCountry = doc.getElementById("cvWorkCountry");
+ cvData.cvbWorkMapItBox = doc.getElementById("cvbWorkMapItBox");
+ cvData.cvWorkMapIt = doc.getElementById("cvWorkMapIt");
+ cvData.cvWorkWebPageBox = doc.getElementById("cvWorkWebPageBox");
+ cvData.cvWorkWebPage = doc.getElementById("cvWorkWebPage");
+ cvData.cvbPhoto = doc.getElementById("cvbPhoto");
+ cvData.cvPhoto = doc.getElementById("cvPhoto");
+ // Chat section
+ cvData.cvbChat = doc.getElementById("cvbChat");
+ cvData.cvhChat = doc.getElementById("cvhChat");
+ cvData.cvYahoo = doc.getElementById("cvYahoo");
+ cvData.cvSkype = doc.getElementById("cvSkype");
+ cvData.cvQQ = doc.getElementById("cvQQ");
+ cvData.cvMSN = doc.getElementById("cvMSN");
+ cvData.cvICQ = doc.getElementById("cvICQ");
+ cvData.cvXMPP = doc.getElementById("cvXMPP");
+ cvData.cvIRC = doc.getElementById("cvIRC");
+}
+
+// XXX todo
+// some similar code (in spirit) already exists, see OnLoadEditList()
+// perhaps we could combine and put in abCommon.js?
+function GetAddressesFromURI(uri)
+{
+ var addresses = "";
+
+ var editList = GetDirectoryFromURI(uri);
+ var addressList = editList.addressLists;
+ if (addressList) {
+ var total = addressList.length;
+ if (total > 0)
+ addresses = addressList.queryElementAt(0, Ci.nsIAbCard).primaryEmail;
+ for (var i = 1; i < total; i++ ) {
+ addresses += ", " + addressList.queryElementAt(i, Ci.nsIAbCard).primaryEmail;
+ }
+ }
+ return addresses;
+}
+
+function DisplayCardViewPane(realCard)
+{
+ var generatedName = realCard.generateName(Services.prefs.getIntPref("mail.addr_book.lastnamefirst"));
+
+ let data = top.cvData;
+ let visible = false;
+
+ let card = { getProperty : function (prop) {
+ return realCard.getProperty(prop, "");
+ },
+ primaryEmail : realCard.primaryEmail,
+ displayName : realCard.displayName,
+ isMailList : realCard.isMailList,
+ mailListURI : realCard.mailListURI
+ };
+
+ // Contact photo
+ displayPhoto(card, cvData.cvPhoto);
+
+ let titleString;
+ if (generatedName == "")
+ titleString = card.primaryEmail; // if no generatedName, use email
+ else
+ titleString = generatedName;
+
+ // set fields in card view pane
+ if (card.isMailList)
+ cvSetNode(data.CardTitle, gAddressBookBundle.getFormattedString("viewListTitle", [generatedName]));
+ else
+ cvSetNode(data.CardTitle, titleString);
+
+ // Contact section
+ cvSetNodeWithLabel(data.cvNickname, zNickname, card.getProperty("NickName"));
+
+ if (card.isMailList) {
+ // email1 and display name always hidden when a mailing list.
+ cvSetVisible(data.cvDisplayName, false);
+ cvSetVisible(data.cvEmail1Box, false);
+
+ visible = HandleLink(data.cvListName, zListName, card.displayName, data.cvListNameBox, "mailto:" + encodeURIComponent(GenerateAddressFromCard(card)));
+ }
+ else {
+ // listname always hidden if not a mailing list
+ cvSetVisible(data.cvListNameBox, false);
+
+ cvSetNodeWithLabel(data.cvDisplayName, zDisplayName, card.displayName);
+
+ visible = HandleLink(data.cvEmail1, zPrimaryEmail, card.primaryEmail, data.cvEmail1Box, "mailto:" + card.primaryEmail);
+ }
+
+ visible = HandleLink(data.cvEmail2, zSecondaryEmail, card.getProperty("SecondEmail"), data.cvEmail2Box, "mailto:" + card.getProperty("SecondEmail")) || visible;
+
+ // Home section
+ visible = cvSetNode(data.cvHomeAddress, card.getProperty("HomeAddress"));
+ visible = cvSetNode(data.cvHomeAddress2, card.getProperty("HomeAddress2")) || visible;
+ visible = cvSetCityStateZip(data.cvHomeCityStZip, card.getProperty("HomeCity"), card.getProperty("HomeState"), card.getProperty("HomeZipCode")) || visible;
+ visible = cvSetNode(data.cvHomeCountry, card.getProperty("HomeCountry")) || visible;
+
+ let mapURLList = data.cvHomeMapIt.firstChild;
+ if (visible)
+ mapURLList.initMapAddressFromCard(card, "Home");
+
+ cvSetVisible(data.cvbHomeMapItBox, visible && !!mapURLList.mapURL);
+
+ visible = HandleLink(data.cvHomeWebPage, "", card.getProperty("WebPage2"), data.cvHomeWebPageBox, card.getProperty("WebPage2")) || visible;
+
+ cvSetVisible(data.cvhHome, visible);
+ cvSetVisible(data.cvbHome, visible);
+ if (card.isMailList) {
+ // Description section
+ visible = cvSetNode(data.cvDescription, card.getProperty("Notes"))
+ cvSetVisible(data.cvbDescription, visible);
+
+ // Addresses section
+ visible = cvAddAddressNodes(data.cvAddresses, card.mailListURI);
+ cvSetVisible(data.cvbAddresses, visible);
+
+ // Other and Chat sections, not shown for mailing lists.
+ cvSetVisible(data.cvbOther, false);
+ cvSetVisible(data.cvbChat, false);
+ }
+ else {
+ // Other section
+ /// setup the birthday information
+ let day = card.getProperty("BirthDay", null);
+ let month = card.getProperty("BirthMonth", null);
+ let year = card.getProperty("BirthYear", null);
+ let dateStr;
+ if (day > 0 && day < 32 && month > 0 && month < 13) {
+ let date;
+ let formatter;
+ if (year) {
+ // use UTC-based calculations to avoid off-by-one day
+ // due to time zone/dst discontinuity
+ date = new Date(Date.UTC(year, month - 1, day));
+ date.setUTCFullYear(year); // to handle two-digit years properly
+ formatter = new Services.intl.DateTimeFormat(undefined,
+ { dateStyle: "long", timeZone: "UTC" });
+ }
+ // if the year doesn't exist, display Month DD (ex. January 1)
+ else {
+ date = new Date(Date.UTC(saneBirthYear(year), month - 1, day));
+ formatter = new Services.intl.DateTimeFormat(undefined,
+ { month: "long", day: "numeric", timeZone: "UTC" });
+ }
+ dateStr = formatter.format(date);
+ }
+ else if (year) {
+ dateStr = year;
+ }
+
+ visible = cvSetNodeWithLabel(data.cvBirthday, zBirthday, dateStr);
+
+ visible = cvSetNodeWithLabel(data.cvCustom1, zCustom1, card.getProperty("Custom1")) || visible;
+ visible = cvSetNodeWithLabel(data.cvCustom2, zCustom2, card.getProperty("Custom2")) || visible;
+ visible = cvSetNodeWithLabel(data.cvCustom3, zCustom3, card.getProperty("Custom3")) || visible;
+ visible = cvSetNodeWithLabel(data.cvCustom4, zCustom4, card.getProperty("Custom4")) || visible;
+ visible = cvSetNode(data.cvNotes, card.getProperty("Notes")) || visible;
+
+ cvSetVisible(data.cvhOther, visible);
+ cvSetVisible(data.cvbOther, visible);
+
+ // Chat section
+ visible = cvSetNodeWithLabel(data.cvYahoo, zYahoo,
+ card.getProperty("_Yahoo"));
+ visible = cvSetNodeWithLabel(data.cvSkype, zSkype,
+ card.getProperty("_Skype")) || visible;
+ visible = cvSetNodeWithLabel(data.cvQQ, zQQ,
+ card.getProperty("_QQ")) || visible;
+ visible = cvSetNodeWithLabel(data.cvMSN, zMSN,
+ card.getProperty("_MSN")) || visible;
+ visible = cvSetNodeWithLabel(data.cvICQ, zICQ,
+ card.getProperty("_ICQ")) || visible;
+ visible = cvSetNodeWithLabel(data.cvXMPP, zXMPP,
+ card.getProperty("_JabberId")) || visible;
+ visible = cvSetNodeWithLabel(data.cvIRC, zIRC,
+ card.getProperty("_IRC")) || visible;
+ cvSetVisible(data.cvhChat, visible);
+ cvSetVisible(data.cvbChat, visible);
+
+ // hide description section, not show for non-mailing lists
+ cvSetVisible(data.cvbDescription, false);
+
+ // hide addresses section, not show for non-mailing lists
+ cvSetVisible(data.cvbAddresses, false);
+ }
+
+ // Phone section
+ visible = cvSetNodeWithLabel(data.cvPhWork, zWork, card.getProperty("WorkPhone"));
+ visible = cvSetNodeWithLabel(data.cvPhHome, zHome, card.getProperty("HomePhone")) || visible;
+ visible = cvSetNodeWithLabel(data.cvPhFax, zFax, card.getProperty("FaxNumber")) || visible;
+ visible = cvSetNodeWithLabel(data.cvPhCellular, zCellular, card.getProperty("CellularNumber")) || visible;
+ visible = cvSetNodeWithLabel(data.cvPhPager, zPager, card.getProperty("PagerNumber")) || visible;
+ cvSetVisible(data.cvhPhone, visible);
+ cvSetVisible(data.cvbPhone, visible);
+
+ // Work section
+ visible = cvSetNode(data.cvJobTitle, card.getProperty("JobTitle"));
+ visible = cvSetNode(data.cvDepartment, card.getProperty("Department")) || visible;
+ visible = cvSetNode(data.cvCompany, card.getProperty("Company")) || visible;
+
+ let addressVisible = cvSetNode(data.cvWorkAddress, card.getProperty("WorkAddress"));
+ addressVisible = cvSetNode(data.cvWorkAddress2, card.getProperty("WorkAddress2")) || addressVisible;
+ addressVisible = cvSetCityStateZip(data.cvWorkCityStZip, card.getProperty("WorkCity"), card.getProperty("WorkState"), card.getProperty("WorkZipCode")) || addressVisible;
+ addressVisible = cvSetNode(data.cvWorkCountry, card.getProperty("WorkCountry")) || addressVisible;
+
+ mapURLList = data.cvWorkMapIt.firstChild;
+ if (addressVisible)
+ mapURLList.initMapAddressFromCard(card, "Work");
+
+ cvSetVisible(data.cvbWorkMapItBox, addressVisible && !!mapURLList.mapURL);
+
+ visible = HandleLink(data.cvWorkWebPage, "", card.getProperty("WebPage1"), data.cvWorkWebPageBox, card.getProperty("WebPage1")) || addressVisible || visible;
+
+ cvSetVisible(data.cvhWork, visible);
+ cvSetVisible(data.cvbWork, visible);
+
+ // make the card view box visible
+ cvSetVisible(top.cvData.CardViewBox, true);
+}
+
+function ClearCardViewPane()
+{
+ cvSetVisible(top.cvData.CardViewBox, false);
+}
+
+function cvSetNodeWithLabel(node, label, text)
+{
+ if (text) {
+ if (label)
+ return cvSetNode(node, label + ": " + text);
+ else
+ return cvSetNode(node, text);
+ }
+ else
+ return cvSetNode(node, "");
+}
+
+function cvSetCityStateZip(node, city, state, zip)
+{
+ let text = "";
+
+ if (city && state && zip)
+ text = gAddressBookBundle.getFormattedString("cityAndStateAndZip",
+ [city, state, zip]);
+ else if (city && state && !zip)
+ text = gAddressBookBundle.getFormattedString("cityAndStateNoZip",
+ [city, state]);
+ else if (zip && ((!city && state) || (city && !state)))
+ text = gAddressBookBundle.getFormattedString("cityOrStateAndZip",
+ [city + state, zip]);
+ else {
+ // Only one of the strings is non-empty so contatenating them produces that string.
+ text = city + state + zip;
+ }
+
+ return cvSetNode(node, text);
+}
+
+function cvSetNode(node, text)
+{
+ if (!node)
+ return false;
+
+ node.textContent = text;
+ let visible = !!text;
+ cvSetVisible(node, visible);
+
+ return visible;
+}
+
+function cvAddAddressNodes(node, uri)
+{
+ var visible = false;
+
+ if (node) {
+ var editList = GetDirectoryFromURI(uri);
+ var addressList = editList.addressLists;
+
+ if (addressList) {
+ var total = addressList.length;
+ if (total > 0) {
+ while (node.hasChildNodes()) {
+ node.lastChild.remove();
+ }
+ for (i = 0; i < total; i++ ) {
+ var descNode = document.createElement("description");
+ var card = addressList.queryElementAt(i, Ci.nsIAbCard);
+
+ descNode.setAttribute("class", "CardViewLink");
+ node.appendChild(descNode);
+
+ var linkNode = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
+ linkNode.setAttribute("id", "addr#" + i);
+ linkNode.setAttribute("href", "mailto:" + card.primaryEmail);
+ descNode.appendChild(linkNode);
+
+ var textNode = document.createTextNode(card.displayName + " <" + card.primaryEmail + ">");
+ linkNode.appendChild(textNode);
+ }
+ visible = true;
+ }
+ }
+ cvSetVisible(node, visible);
+ }
+ return visible;
+}
+
+function cvSetVisible(node, visible)
+{
+ if ( visible )
+ node.removeAttribute("collapsed");
+ else
+ node.setAttribute("collapsed", "true");
+}
+
+function HandleLink(node, label, value, box, link)
+{
+ var visible = cvSetNodeWithLabel(node, label, value);
+ if (visible)
+ node.setAttribute('href', link);
+ cvSetVisible(box, visible);
+
+ return visible;
+}
+
+function openLink(aEvent)
+{
+ openAsExternal(aEvent.target.getAttribute("href"));
+ // return false, so we don't load the href in the addressbook window
+ return false;
+}
+
+function openLinkWithUrl(aUrl)
+{
+ if (aUrl)
+ openAsExternal(aUrl);
+ // return false, so we don't load the href in the addressbook window
+ return false;
+}
+
+/* Display the contact photo from the nsIAbCard in the IMG element.
+ * If the photo cannot be displayed, show the generic contact
+ * photo.
+ */
+function displayPhoto(aCard, aImg)
+{
+ var type = aCard.getProperty("PhotoType", "");
+ if (!gPhotoDisplayHandlers[type] ||
+ !gPhotoDisplayHandlers[type](aCard, aImg))
+ gPhotoDisplayHandlers["generic"](aCard, aImg);
+}
+
+/* In order to display the contact photos in the card view, there
+ * must be a registered photo display handler for the card photo
+ * type. The generic, file, and web photo types are handled
+ * by default.
+ *
+ * A photo display handler is a function that behaves as follows:
+ *
+ * function(aCard, aImg):
+ * The function is responsible for determining how to retrieve
+ * the photo from nsIAbCard aCard, and for displaying it in img
+ * img element aImg. Returns true if successful. If it returns
+ * false, the generic photo display handler will be called.
+ *
+ * The following display handlers are for the generic, file and
+ * web photo types.
+ */
+
+var gGenericPhotoDisplayHandler = function(aCard, aImg)
+{
+ aImg.setAttribute("src", defaultPhotoURI);
+ return true;
+};
+
+var gPhotoNameDisplayHandler = function(aCard, aImg)
+{
+ var photoSrc = getPhotoURI(aCard.getProperty("PhotoName"));
+ aImg.setAttribute("src", photoSrc);
+ return true;
+};
+
+/* In order for a photo display handler to be registered for
+ * a particular photo type, it must be registered here.
+ */
+function registerPhotoDisplayHandler(aType, aPhotoDisplayHandler)
+{
+ if (!gPhotoDisplayHandlers[aType])
+ gPhotoDisplayHandlers[aType] = aPhotoDisplayHandler;
+}
+
+registerPhotoDisplayHandler("generic", gGenericPhotoDisplayHandler);
+// File and Web are treated the same, and therefore use the
+// same handler.
+registerPhotoDisplayHandler("file", gPhotoNameDisplayHandler);
+registerPhotoDisplayHandler("web", gPhotoNameDisplayHandler);
diff --git a/comm/suite/mailnews/components/addrbook/content/abCommon.js b/comm/suite/mailnews/components/addrbook/content/abCommon.js
new file mode 100644
index 0000000000..a8e9d37c21
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abCommon.js
@@ -0,0 +1,1185 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { IOUtils } = ChromeUtils.import("resource:///modules/IOUtils.js");
+const { MailServices } =
+ ChromeUtils.import("resource:///modules/MailServices.jsm");
+const { FileUtils } =
+ ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
+const { PrivateBrowsingUtils } =
+ ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+
+var gDirTree = null;
+var abList = null;
+var gAbResultsTree = null;
+var gAbView = null;
+var gAddressBookBundle;
+// A boolean variable determining whether AB column should be shown in AB
+// sidebar in compose window.
+var gShowAbColumnInComposeSidebar = false;
+
+const kDefaultSortColumn = "GeneratedName";
+const kDefaultAscending = "ascending";
+const kDefaultDescending = "descending";
+// kDefaultYear will be used in birthday calculations when no year is given;
+// this is a leap year so that Feb 29th works.
+const kDefaultYear = nearestLeap(new Date().getFullYear());
+const kMaxYear = 9999;
+const kMinYear = 1;
+const kAllDirectoryRoot = "moz-abdirectory://";
+const kLdapUrlPrefix = "moz-abldapdirectory://";
+const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+const kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+// The default, generic contact image is displayed via CSS when the photoURI is
+// blank.
+var defaultPhotoURI = "";
+
+// Controller object for Dir Pane
+var DirPaneController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ case "cmd_delete":
+ case "button_delete":
+ case "cmd_properties":
+ case "cmd_printcard":
+ case "cmd_printcardpreview":
+ case "cmd_print":
+ case "cmd_printpreview":
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ // The gDirTree pane only handles single selection, but normally we
+ // enable cmd_selectAll as it will get forwarded to the results pane.
+ // But if there is no gAbView, disable as we can't forward to anywhere.
+ return (gAbView != null);
+ case "cmd_delete":
+ case "button_delete": {
+ let selectedDir = getSelectedDirectory();
+ if (!selectedDir)
+ return false;
+ let selectedDirURI = selectedDir.URI;
+
+ // Context-sensitive labels for Edit > Delete menuitem.
+ if (command == "cmd_delete") {
+ goSetMenuValue(command, selectedDir.isMailList ?
+ "valueList" : "valueAddressBook");
+ }
+
+ // If it's one of these special ABs, return false to disable deletion.
+ if (selectedDirURI == kPersonalAddressbookURI ||
+ selectedDirURI == kCollectedAddressbookURI ||
+ selectedDirURI == (kAllDirectoryRoot + "?"))
+ return false;
+
+ // If the directory is a mailing list, and it is read-only,
+ // return false to disable deletion.
+ if (selectedDir.isMailList && selectedDir.readOnly)
+ return false;
+
+ // If the selected directory is an ldap directory,
+ // and if the prefs for this directory are locked,
+ // return false to disable deletion.
+ if (selectedDirURI.startsWith(kLdapUrlPrefix)) {
+ let disable = false;
+ try {
+ let prefName = selectedDirURI.substr(kLdapUrlPrefix.length);
+ disable = Services.prefs.getBoolPref(prefName + ".disable_delete");
+ }
+ catch(ex) {
+ // If this preference is not set, that's ok.
+ }
+ if (disable)
+ return false;
+ }
+
+ // Else return true to enable deletion (default).
+ return true;
+ }
+ case "cmd_printcard":
+ case "cmd_printcardpreview":
+ return (GetSelectedCardIndex() != -1);
+ case "cmd_print":
+ case "cmd_printpreview":
+ document.querySelectorAll("[command=cmd_print]").forEach(e => {
+ e.disabled = false;
+ });
+ return true;
+ case "cmd_properties":
+ return (getSelectedDirectoryURI() != null);
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_printcard":
+ case "cmd_printcardpreview":
+ case "cmd_selectAll":
+ SendCommandToResultsPane(command);
+ break;
+ case "cmd_print":
+ AbPrintAddressBook();
+ break;
+ case "cmd_printpreview":
+ AbPrintPreviewAddressBook();
+ break;
+ case "cmd_delete":
+ case "button_delete":
+ if (gDirTree)
+ AbDeleteSelectedDirectory();
+ break;
+ case "cmd_properties":
+ AbEditSelectedDirectory();
+ break;
+ case "cmd_newlist":
+ AbNewList();
+ break;
+ case "cmd_newCard":
+ AbNewCard();
+ break;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ // on blur events set the menu item texts back to the normal values
+ if (event == "blur")
+ goSetMenuValue("cmd_delete", "valueDefault");
+ }
+};
+
+function SendCommandToResultsPane(command)
+{
+ ResultsPaneController.doCommand(command);
+
+ // if we are sending the command so the results pane
+ // we should focus the results pane
+ gAbResultsTree.focus();
+}
+
+function AbNewLDAPDirectory()
+{
+ window.openDialog("chrome://messenger/content/addressbook/pref-directory-add.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ null);
+}
+
+function AbNewAddressBook()
+{
+ window.openDialog("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ null);
+}
+
+function AbEditSelectedDirectory()
+{
+ let selectedDir = getSelectedDirectory();
+ if (!selectedDir)
+ return;
+
+ if (selectedDir.isMailList) {
+ goEditListDialog(null, selectedDir.URI);
+ } else {
+ window.openDialog(selectedDir.propertiesChromeURI,
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ {selectedDirectory: selectedDir});
+ }
+}
+
+function AbDeleteSelectedDirectory()
+{
+ let selectedDirURI = getSelectedDirectoryURI();
+ if (!selectedDirURI)
+ return;
+
+ AbDeleteDirectory(selectedDirURI);
+}
+
+function AbDeleteDirectory(aURI)
+{
+ // Determine strings for smart and context-sensitive user prompts
+ // for confirming deletion.
+ let directory = GetDirectoryFromURI(aURI);
+ let confirmDeleteTitleID;
+ let confirmDeleteTitle;
+ let confirmDeleteMessageID;
+ let confirmDeleteMessage;
+ let brandShortName;
+ let clearCollectionPrefs = false;
+
+ if (directory.isMailList) {
+ // It's a mailing list.
+ confirmDeleteMessageID = "confirmDeleteThisMailingList";
+ confirmDeleteTitleID = "confirmDeleteThisMailingListTitle";
+ } else {
+ // It's an address book: check which type.
+ if (Services.prefs.getCharPref("mail.collect_addressbook") == aURI &&
+ (Services.prefs.getBoolPref("mail.collect_email_address_outgoing") ||
+ Services.prefs.getBoolPref("mail.collect_email_address_incoming") ||
+ Services.prefs.getBoolPref("mail.collect_email_address_newsgroup"))) {
+ // It's a collection address book: let's be clear about the consequences.
+ brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
+ confirmDeleteMessageID = "confirmDeleteThisCollectionAddressbook";
+ confirmDeleteTitleID = "confirmDeleteThisCollectionAddressbookTitle";
+ clearCollectionPrefs = true;
+ } else if (directory.URI.startsWith(kLdapUrlPrefix)) {
+ // It's an LDAP directory, so we only delete our offline copy.
+ confirmDeleteMessageID = "confirmDeleteThisLDAPDir";
+ confirmDeleteTitleID = "confirmDeleteThisLDAPDirTitle";
+ } else {
+ // It's a normal personal address book: we'll delete its contacts, too.
+ confirmDeleteMessageID = "confirmDeleteThisAddressbook";
+ confirmDeleteTitleID = "confirmDeleteThisAddressbookTitle";
+ }
+ }
+
+ // Get the raw strings with placeholders.
+ confirmDeleteTitle = gAddressBookBundle.getString(confirmDeleteTitleID);
+ confirmDeleteMessage = gAddressBookBundle.getString(confirmDeleteMessageID);
+
+ // Substitute placeholders as required.
+ // Replace #1 with the name of the selected address book or mailing list.
+ confirmDeleteMessage = confirmDeleteMessage.replace("#1", directory.dirName);
+ if (brandShortName) {
+ // For a collection address book, replace #2 with the brandShortName.
+ confirmDeleteMessage = confirmDeleteMessage.replace("#2", brandShortName);
+ }
+
+ // Ask for confirmation before deleting
+ if (!Services.prompt.confirm(window, confirmDeleteTitle,
+ confirmDeleteMessage)) {
+ // Deletion cancelled by user.
+ return;
+ }
+
+ // If we're about to delete the collection AB, update the respective prefs.
+ if (clearCollectionPrefs) {
+ Services.prefs.setBoolPref("mail.collect_email_address_outgoing", false);
+ Services.prefs.setBoolPref("mail.collect_email_address_incoming", false);
+ Services.prefs.setBoolPref("mail.collect_email_address_newsgroup", false);
+
+ // Change the collection AB pref to "Personal Address Book" so that we
+ // don't get a blank item in prefs dialog when collection is re-enabled.
+ Services.prefs.setCharPref("mail.collect_addressbook",
+ kPersonalAddressbookURI);
+ }
+
+ MailServices.ab.deleteAddressBook(aURI);
+}
+
+function InitCommonJS()
+{
+ gDirTree = document.getElementById("dirTree");
+ abList = document.getElementById("addressbookList");
+ gAddressBookBundle = document.getElementById("bundle_addressBook");
+
+ // Make an entry for "All Address Books".
+ if (abList) {
+ abList.insertItemAt(0, gAddressBookBundle.getString("allAddressBooks"),
+ kAllDirectoryRoot + "?");
+ }
+}
+
+function UpgradeAddressBookResultsPaneUI(prefName)
+{
+ // placeholder in case any new columns get added to the address book
+ // var resultsPaneUIVersion = Services.prefs.getIntPref(prefName);
+}
+
+function AbDelete()
+{
+ let types = GetSelectedCardTypes();
+ if (types == kNothingSelected)
+ return;
+
+ // Determine strings for smart and context-sensitive user prompts
+ // for confirming deletion.
+ let confirmDeleteTitleID;
+ let confirmDeleteTitle;
+ let confirmDeleteMessageID;
+ let confirmDeleteMessage;
+ let itemName;
+ let containingListName;
+ let selectedDir = getSelectedDirectory();
+ let numSelectedItems = gAbView.selection.count;
+
+ switch(types) {
+ case kListsAndCards:
+ confirmDeleteMessageID = "confirmDelete2orMoreContactsAndLists";
+ confirmDeleteTitleID = "confirmDelete2orMoreContactsAndListsTitle";
+ break;
+ case kSingleListOnly:
+ // Set item name for single mailing list.
+ let theCard = GetSelectedAbCards()[0];
+ itemName = theCard.displayName;
+ confirmDeleteMessageID = "confirmDeleteThisMailingList";
+ confirmDeleteTitleID = "confirmDeleteThisMailingListTitle";
+ break;
+ case kMultipleListsOnly:
+ confirmDeleteMessageID = "confirmDelete2orMoreMailingLists";
+ confirmDeleteTitleID = "confirmDelete2orMoreMailingListsTitle";
+ break;
+ case kCardsOnly:
+ if (selectedDir.isMailList) {
+ // Contact(s) in mailing lists will be removed from the list, not deleted.
+ if (numSelectedItems == 1) {
+ confirmDeleteMessageID = "confirmRemoveThisContact";
+ confirmDeleteTitleID = "confirmRemoveThisContactTitle";
+ } else {
+ confirmDeleteMessageID = "confirmRemove2orMoreContacts";
+ confirmDeleteTitleID = "confirmRemove2orMoreContactsTitle";
+ }
+ // For removing contacts from mailing list, set placeholder value
+ containingListName = selectedDir.dirName;
+ } else {
+ // Contact(s) in address books will be deleted.
+ if (numSelectedItems == 1) {
+ confirmDeleteMessageID = "confirmDeleteThisContact";
+ confirmDeleteTitleID = "confirmDeleteThisContactTitle";
+ } else {
+ confirmDeleteMessageID = "confirmDelete2orMoreContacts";
+ confirmDeleteTitleID = "confirmDelete2orMoreContactsTitle";
+ }
+ }
+ if (numSelectedItems == 1) {
+ // Set item name for single contact.
+ let theCard = GetSelectedAbCards()[0];
+ let nameFormatFromPref = Services.prefs.getIntPref("mail.addr_book.lastnamefirst");
+ itemName = theCard.generateName(nameFormatFromPref);
+ }
+ break;
+ }
+
+ // Get the raw model strings.
+ // For numSelectedItems == 1, it's simple strings.
+ // For messages with numSelectedItems > 1, it's multi-pluralform string sets.
+ // confirmDeleteMessage has placeholders for some forms.
+ confirmDeleteTitle = gAddressBookBundle.getString(confirmDeleteTitleID);
+ confirmDeleteMessage = gAddressBookBundle.getString(confirmDeleteMessageID);
+
+ // Get plural form where applicable; substitute placeholders as required.
+ if (numSelectedItems == 1) {
+ // If single selected item, substitute itemName.
+ confirmDeleteMessage = confirmDeleteMessage.replace("#1", itemName);
+ } else {
+ // If multiple selected items, get the right plural string from the
+ // localized set, then substitute numSelectedItems.
+ confirmDeleteMessage = PluralForm.get(numSelectedItems, confirmDeleteMessage);
+ confirmDeleteMessage = confirmDeleteMessage.replace("#1", numSelectedItems);
+ }
+ // If contact(s) in a mailing list, substitute containingListName.
+ if (containingListName)
+ confirmDeleteMessage = confirmDeleteMessage.replace("#2", containingListName);
+
+ // Finally, show our smart confirmation message, and act upon it!
+ if (!Services.prompt.confirm(window, confirmDeleteTitle,
+ confirmDeleteMessage)) {
+ // Deletion cancelled by user.
+ return;
+ }
+
+ if (selectedDir.URI == (kAllDirectoryRoot + "?")) {
+ // Delete cards from "All Address Books" view.
+ let cards = GetSelectedAbCards();
+ for (let i = 0; i < cards.length; i++) {
+ let dirId = cards[i].directoryId
+ .substring(0, cards[i].directoryId.indexOf("&"));
+ let directory = MailServices.ab.getDirectoryFromId(dirId);
+
+ let cardArray =
+ Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ cardArray.appendElement(cards[i], false);
+ if (directory)
+ directory.deleteCards(cardArray);
+ }
+ SetAbView(kAllDirectoryRoot + "?");
+ } else {
+ // Delete cards from address books or mailing lists.
+ gAbView.deleteSelectedCards();
+ }
+}
+
+function AbNewCard()
+{
+ goNewCardDialog(getSelectedDirectoryURI());
+}
+
+function AbEditCard(card)
+{
+ // Need a card,
+ if (!card)
+ return;
+
+ if (card.isMailList) {
+ goEditListDialog(card, card.mailListURI);
+ } else {
+ goEditCardDialog(getSelectedDirectoryURI(), card);
+ }
+}
+
+function AbNewMessage()
+{
+ let params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(Ci.nsIMsgComposeParams);
+ if (params) {
+ let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+ if (composeFields) {
+ params.type = Ci.nsIMsgCompType.New;
+ params.format = Ci.nsIMsgCompFormat.Default;
+ if (DirPaneHasFocus()) {
+ let selectedDir = getSelectedDirectory();
+ let hidesRecipients = false;
+ try {
+ // This is a bit of hackery so that extensions can have mailing lists
+ // where recipients are sent messages via BCC.
+ hidesRecipients = selectedDir.getBoolValue("HidesRecipients", false);
+ } catch(e) {
+ // Standard Thunderbird mailing lists do not have preferences
+ // associated with them, so we'll silently eat the error.
+ }
+
+ if (selectedDir && selectedDir.isMailList && hidesRecipients)
+ // Bug 669301 (https://bugzilla.mozilla.org/show_bug.cgi?id=669301)
+ // We're using BCC right now to hide recipients from one another.
+ // We should probably use group syntax, but that's broken
+ // right now, so this will have to do.
+ composeFields.bcc = GetSelectedAddressesFromDirTree();
+ else
+ composeFields.to = GetSelectedAddressesFromDirTree();
+ } else {
+ composeFields.to = GetSelectedAddresses();
+ }
+ params.composeFields = composeFields;
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+ }
+ }
+}
+
+function AbCopyAddress()
+{
+ var cards = GetSelectedAbCards();
+ if (!cards)
+ return;
+
+ var count = cards.length;
+ if (!count)
+ return;
+
+ var addresses = cards[0].primaryEmail;
+ for (var i = 1; i < count; i++)
+ addresses += "," + cards[i].primaryEmail;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(addresses);
+}
+
+/**
+ * Set up items in the View > Layout menupopup. This function is responsible
+ * for updating the menu items' state to reflect reality.
+ *
+ * @param aEvent the event that caused the View > Layout menupopup to be shown
+ */
+function InitViewLayoutMenuPopup(aEvent)
+{
+ let dirTreeVisible = document.getElementById("dirTree-splitter")
+ .getAttribute("state") != "collapsed";
+ document.getElementById("menu_showDirectoryPane")
+ .setAttribute("checked", dirTreeVisible);
+
+ let cardPaneVisible = document.getElementById("results-splitter")
+ .getAttribute("state") != "collapsed";
+ document.getElementById("menu_showCardPane")
+ .setAttribute("checked", cardPaneVisible);
+}
+
+// Generate a list of cards from the selected mailing list
+// and get a comma separated list of card addresses. If the
+// item selected in the directory pane is not a mailing list,
+// an empty string is returned.
+function GetSelectedAddressesFromDirTree()
+{
+ let selectedDir = getSelectedDirectory();
+
+ if (!selectedDir || !selectedDir.isMailList)
+ return "";
+
+ let listCardsCount = selectedDir.addressLists.length;
+ let cards = new Array(listCardsCount);
+ for (let i = 0; i < listCardsCount; ++i)
+ cards[i] = selectedDir.addressLists
+ .queryElementAt(i, Ci.nsIAbCard);
+ return GetAddressesForCards(cards);
+}
+
+// Generate a comma separated list of addresses from a given
+// set of cards.
+function GetAddressesForCards(cards)
+{
+ var addresses = "";
+
+ if (!cards)
+ return addresses;
+
+ var count = cards.length;
+ for (var i = 0; i < count; ++i) {
+ var generatedAddress = GenerateAddressFromCard(cards[i]);
+ if (generatedAddress) {
+ // If it's not the first address in the list, add a comma separator.
+ if (addresses)
+ addresses += ",";
+ addresses += generatedAddress;
+ }
+ }
+
+ return addresses;
+}
+
+
+function SelectFirstAddressBook()
+{
+ if (gDirectoryTreeView.selection.currentIndex != 0) {
+ gDirectoryTreeView.selection.select(0);
+ ChangeDirectoryByURI(getSelectedDirectoryURI());
+ }
+ gAbResultsTree.focus();
+}
+
+function DirPaneClick(event)
+{
+ // we only care about left button events
+ if (event.button != 0)
+ return;
+
+ // if the user clicks on the header / trecol, do nothing
+ if (event.originalTarget.localName == "treecol") {
+ event.stopPropagation();
+ return;
+ }
+}
+
+function DirPaneDoubleClick(event)
+{
+ // We only care about left button events.
+ if (event.button != 0)
+ return;
+
+ // Ignore double clicking on invalid rows.
+ let row = gDirTree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1 || row >= gDirectoryTreeView.rowCount)
+ return;
+
+ // Default action for double click is expand/collapse which ships with the tree.
+ // For convenience, allow double-click to edit the properties of mailing
+ // lists in directory tree.
+ if (gDirTree && gDirTree.view.selection &&
+ gDirTree.view.selection.count == 1 &&
+ getSelectedDirectory().isMailList) {
+ AbEditSelectedDirectory();
+ }
+}
+
+function DirPaneSelectionChange()
+{
+ let uri = getSelectedDirectoryURI();
+ // clear out the search box when changing folders...
+ onAbClearSearch(false);
+ if (gDirectoryTreeView.selection &&
+ gDirectoryTreeView.selection.count == 1) {
+ ChangeDirectoryByURI(uri);
+ document.getElementById("localResultsOnlyMessage")
+ .setAttribute("hidden",
+ !gDirectoryTreeView.hasRemoteAB ||
+ uri != kAllDirectoryRoot + "?");
+ }
+}
+
+function ChangeDirectoryByURI(uri = kPersonalAddressbookURI)
+{
+ SetAbView(uri);
+
+ // Actively de-selecting if there are any pre-existing selections
+ // in the results list.
+ if (gAbView && gAbView.getCardFromRow(0))
+ gAbView.selection.clearSelection();
+ else
+ // the selection changes if we were switching directories.
+ ResultsPaneSelectionChanged()
+}
+
+function AbNewList()
+{
+ goNewListDialog(getSelectedDirectoryURI());
+}
+
+function goNewListDialog(selectedAB)
+{
+ window.openDialog("chrome://messenger/content/addressbook/abMailListDialog.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ {selectedAB:selectedAB});
+}
+
+function goEditListDialog(abCard, listURI)
+{
+ let params = {
+ abCard: abCard,
+ listURI: listURI,
+ refresh: false, // This is an out param, true if OK in dialog is clicked.
+ };
+
+ window.openDialog("chrome://messenger/content/addressbook/abEditListDialog.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ params);
+
+ if (params.refresh) {
+ ChangeDirectoryByURI(listURI); // force refresh
+ }
+}
+
+function goNewCardDialog(selectedAB)
+{
+ window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ {selectedAB:selectedAB});
+}
+
+function goEditCardDialog(abURI, card)
+{
+ window.openDialog("chrome://messenger/content/addressbook/abEditCardDialog.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ {abURI:abURI, card:card});
+}
+
+function setSortByMenuItemCheckState(id, value)
+{
+ var menuitem = document.getElementById(id);
+ if (menuitem) {
+ menuitem.setAttribute("checked", value);
+ }
+}
+
+function InitViewSortByMenu()
+{
+ var sortColumn = kDefaultSortColumn;
+ var sortDirection = kDefaultAscending;
+
+ if (gAbView) {
+ sortColumn = gAbView.sortColumn;
+ sortDirection = gAbView.sortDirection;
+ }
+
+ // this approach is necessary to support generic columns that get overlayed.
+ let elements = document.querySelectorAll('[name="sortas"]');
+ for (let i = 0; i < elements.length; i++) {
+ let cmd = elements[i].id;
+ let columnForCmd = cmd.substr(10); // everything right of cmd_SortBy
+ setSortByMenuItemCheckState(cmd, (sortColumn == columnForCmd));
+ }
+
+ setSortByMenuItemCheckState("sortAscending", (sortDirection == kDefaultAscending));
+ setSortByMenuItemCheckState("sortDescending", (sortDirection == kDefaultDescending));
+}
+
+function GenerateAddressFromCard(card)
+{
+ if (!card)
+ return "";
+
+ var email;
+
+ if (card.isMailList)
+ {
+ var directory = GetDirectoryFromURI(card.mailListURI);
+ email = directory.description || card.displayName;
+ }
+ else
+ email = card.primaryEmail;
+
+ return MailServices.headerParser.makeMimeAddress(card.displayName, email);
+}
+
+function GetDirectoryFromURI(uri)
+{
+ return MailServices.ab.getDirectory(uri);
+}
+
+// returns null if abURI is not a mailing list URI
+function GetParentDirectoryFromMailingListURI(abURI)
+{
+ var abURIArr = abURI.split("/");
+ /*
+ turn turn "moz-abmdbdirectory://abook.mab/MailList6"
+ into ["moz-abmdbdirectory:","","abook.mab","MailList6"]
+ then, turn ["moz-abmdbdirectory:","","abook.mab","MailList6"]
+ into "moz-abmdbdirectory://abook.mab"
+ */
+ if (abURIArr.length == 4 && abURIArr[0] == "moz-abmdbdirectory:" && abURIArr[3] != "") {
+ return abURIArr[0] + "/" + abURIArr[1] + "/" + abURIArr[2];
+ }
+
+ return null;
+}
+
+/**
+ * Return true if the directory pane has focus, otherwise false.
+ */
+function DirPaneHasFocus()
+{
+ return (top.document.commandDispatcher.focusedElement == gDirTree);
+}
+
+/**
+ * Get the selected directory object.
+ *
+ * @return The object of the currently selected directory
+ */
+function getSelectedDirectory()
+{
+ // Select Addresses Dialog
+ if (abList)
+ return MailServices.ab.getDirectory(abList.value);
+
+ // Main Address Book
+ if (gDirTree.currentIndex < 0)
+ return null;
+ return gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex);
+}
+
+/**
+ * Get the URI of the selected directory.
+ *
+ * @return The URI of the currently selected directory
+ */
+function getSelectedDirectoryURI()
+{
+ // Select Addresses Dialog
+ if (abList)
+ return abList.value;
+
+ // Main Address Book
+ if (gDirTree.currentIndex < 0)
+ return null;
+ return gDirectoryTreeView.getDirectoryAtIndex(gDirTree.currentIndex).URI;
+}
+
+/**
+ * DEPRECATED legacy function wrapper for addon compatibility;
+ * use getSelectedDirectoryURI() instead!
+ * Return the URI of the selected directory.
+ */
+function GetSelectedDirectory()
+{
+ return getSelectedDirectoryURI();
+}
+
+/**
+ * Clears the contents of the search input field,
+ * possibly causing refresh of results.
+ *
+ * @param aRefresh Set to false if the refresh isn't needed,
+ * e.g. window/AB is going away so user will not see anything.
+ */
+function onAbClearSearch(aRefresh = true)
+{
+ let searchInput = document.getElementById("searchInput");
+ if (!searchInput || !searchInput.value)
+ return;
+
+ searchInput.value = "";
+ if (aRefresh)
+ onEnterInSearchBar();
+}
+
+/**
+ * Returns an nsIFile of the directory in which contact photos are stored.
+ * This will create the directory if it does not yet exist.
+ */
+function getPhotosDir() {
+ var file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ // Get the Photos directory
+ file.append("Photos");
+ if (!file.exists() || !file.isDirectory())
+ file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0777", 8));
+ return file;
+}
+
+/**
+ * Returns a URI specifying the location of a photo based on its name.
+ * If the name is blank, or if the photo with that name is not in the Photos
+ * directory then the default photo URI is returned.
+ *
+ * @param aPhotoName The name of the photo from the Photos folder, if any.
+ *
+ * @return A URI pointing to a photo.
+ */
+function getPhotoURI(aPhotoName) {
+ if (!aPhotoName)
+ return defaultPhotoURI;
+ var file = getPhotosDir();
+ try {
+ file.append(aPhotoName);
+ }
+ catch (e) {
+ return defaultPhotoURI;
+ }
+ if (!file.exists())
+ return defaultPhotoURI;
+ return Services.io.newFileURI(file).spec;
+}
+
+/**
+ * Generates a unique filename to be used for a local copy of a contact's photo.
+ *
+ * @param aPath The path to the folder in which the photo will be saved.
+ * @param aExtension The file extension of the photo.
+ *
+ * @return A unique filename in the given path.
+ */
+function makePhotoFile(aDir, aExtension) {
+ var filename, newFile;
+ // Find a random filename for the photo that doesn't exist yet
+ do {
+ filename = new String(Math.random()).replace("0.", "") + "." + aExtension;
+ newFile = aDir.clone();
+ newFile.append(filename);
+ } while (newFile.exists());
+ return newFile;
+}
+
+/**
+ * Public self-contained object for image transfers.
+ * Responsible for file transfer, validating the image and downscaling.
+ * Attention: It is the responsibility of the caller to remove the old photo
+ * and update the card!
+ */
+var gImageDownloader = (function() {
+ let downloadInProgress = false;
+
+ // Current instance of nsIWebBrowserPersist. It is used two times, during
+ // the actual download and for saving canvas data.
+ let downloader;
+
+ // Temporary nsIFile used for download.
+ let tempFile;
+
+ // Images are downsized to this size while keeping the aspect ratio.
+ const maxSize = 300;
+
+ // Callback function for various states
+ let callbackSuccess;
+ let callbackError;
+ let callbackProgress;
+
+ // Start at 4% to show a slight progress initially.
+ const initProgress = 4;
+
+ // Constants indicating error and file transfer status
+ const STATE_TRANSFERRING = 0;
+ const STATE_RESIZING = 1;
+ const STATE_OK = 2;
+ // The URI does not have a valid format.
+ const ERROR_INVALID_URI = 0;
+ // In case of HTTP transfers: server did not answer with a 200 status code.
+ const ERROR_UNAVAILABLE = 1;
+ // The file type is not supported. Only jpeg, png and gif are.
+ const ERROR_INVALID_IMG = 2;
+ // An error occurred while saving the image to the hard drive.
+ const ERROR_SAVE = 4;
+
+
+ /**
+ * Saves a target photo in the profile's photo directory. Only one concurrent
+ * file transfer is supported. Starting a new transfer while another is still
+ * in progress will cancel the former file transfer.
+ *
+ * @param aURI {string} URI pointing to the photo.
+ * @param cbSuccess(photoName) {function} A callback function which is called
+ * on success.
+ * The photo file name is passed in.
+ * @param cbError(state) {function} A callback function which is called
+ * in case of an error. The error
+ * state is passed in.
+ * @param cbProgress(errcode, percent) {function} A callback function which
+ * provides progress report.
+ * An error code (see above)
+ * and the progress percentage
+ * (0-100) is passed in.
+ * State transitions: STATE_TRANSFERRING -> STATE_RESIZING -> STATE_OK (100%)
+ */
+ function savePhoto(aURI, aCBSuccess, aCBError, aCBProgress) {
+ callbackSuccess = typeof aCBSuccess == "function" ? aCBSuccess : null;
+ callbackError = typeof aCBError == "function" ? aCBError : null;
+ callbackProgress = typeof aCBProgress == "function" ? aCBProgress : null;
+
+ // Make sure that there is no running download.
+ cancelSave();
+ downloadInProgress = true;
+
+ if (callbackProgress) {
+ callbackProgress(STATE_TRANSFERRING, initProgress);
+ }
+
+ downloader = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(Ci.nsIWebBrowserPersist);
+ downloader.persistFlags =
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE |
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_CLEANUP_ON_FAILURE;
+ downloader.progressListener = {
+ onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
+ if (aMaxTotalProgress > -1 && callbackProgress) {
+ // Download progress is 0-90%, 90-100% is verifying and scaling the
+ // image.
+ let percent =
+ Math.round(initProgress +
+ (aCurTotalProgress / aMaxTotalProgress) *
+ (90 - initProgress));
+ callbackProgress(STATE_TRANSFERRING, percent);
+ }
+ },
+ onStateChange(aWebProgress, aRequest, aStateFlag, aStatus) {
+ // Check if the download successfully finished.
+ if ((aStateFlag & Ci.nsIWebProgressListener.STATE_STOP) &&
+ !(aStateFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST)) {
+ try {
+ // Check the response code in case of an HTTP request to catch 4xx
+ // errors.
+ let http = aRequest.QueryInterface(Ci.nsIHttpChannel);
+ if (http.responseStatus == 200) {
+ verifyImage();
+ } else if (callbackError) {
+ callbackError(ERROR_UNAVAILABLE);
+ }
+ } catch (err) {
+ // The nsIHttpChannel interface is not available - just proceed.
+ verifyImage();
+ }
+ }
+ },
+ };
+
+ let source;
+ try {
+ source = Services.io.newURI(aURI);
+ } catch (err) {
+ if (callbackError) {
+ callbackError(ERROR_INVALID_URI);
+ }
+ return;
+ }
+
+ // Start the transfer to a temporary file.
+ tempFile = FileUtils.getFile("TmpD",
+ ["tb-photo-" + new Date().getTime() + ".tmp"]);
+ tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+ try {
+ // Obtain the privacy context of the browser window that the URL
+ // we are downloading comes from. If, and only if, the URL is not
+ // related to a window, null should be used instead.
+ let privacy = PrivateBrowsingUtils.privacyContextFromWindow(window);
+ downloader.saveURI(source, null, null, null, null, null, tempFile,
+ privacy);
+ } catch (err) {
+ cleanup();
+ if (callbackError) {
+ callbackError(ERROR_SAVE);
+ }
+ }
+ }
+
+ /**
+ * Verifies the downloaded file to be an image.
+ * Scales the image and starts the saving operation.
+ */
+ function verifyImage() {
+ let img = new Image();
+ img.onerror = function() {
+ cleanup();
+ if (callbackError) {
+ callbackError(ERROR_INVALID_IMG);
+ }
+ };
+ img.onload = function() {
+ if (callbackProgress) {
+ callbackProgress(STATE_RESIZING, 95);
+ }
+
+ // Images are scaled down in two steps to improve quality. Resizing
+ // ratios larger than 2 use a different interpolation algorithm than
+ // small ratios. Resize three times (instead of just two steps) to
+ // improve the final quality.
+ let canvas = downscale(img, 3.8 * maxSize);
+ canvas = downscale(canvas, 1.9 * maxSize);
+ canvas = downscale(canvas, maxSize);
+
+ saveCanvas(canvas);
+
+ if (callbackProgress) {
+ callbackProgress(STATE_OK, 100);
+ }
+
+ // Remove the temporary file.
+ cleanup();
+ };
+
+ if (callbackProgress) {
+ callbackProgress(STATE_RESIZING, 92);
+ }
+
+ img.src = Services.io.newFileURI(tempFile).spec;
+ }
+
+ /**
+ * Scale a graphics object down to a specified maximum dimension while
+ * preserving the aspect ratio. Does not upscale an image.
+ *
+ * @param aGraphicsObject {image | canvas} Image or canvas object
+ * @param aMaxDimension {integer} The maximal allowed width or
+ * height
+ *
+ * @return A canvas object.
+ */
+ function downscale(aGraphicsObject, aMaxDimension) {
+ let w = aGraphicsObject.width;
+ let h = aGraphicsObject.height;
+
+ if (w > h && w > aMaxDimension) {
+ h = Math.round(aMaxDimension * h / w);
+ w = aMaxDimension;
+ } else if (h > aMaxDimension) {
+ w = Math.round(aMaxDimension * w / h);
+ h = aMaxDimension;
+ }
+
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
+ "canvas");
+ canvas.width = w;
+ canvas.height = h;
+
+ let ctx = canvas.getContext("2d");
+ ctx.drawImage(aGraphicsObject, 0, 0, w, h);
+ return canvas;
+ }
+
+ /**
+ * Cancel a running download (if any).
+ */
+ function cancelSave() {
+ if (!downloadInProgress) {
+ return;
+ }
+
+ // Cancel the nsIWebBrowserPersist file transfer.
+ if (downloader) {
+ downloader.cancelSave();
+ }
+ cleanup();
+ }
+
+ /**
+ * Remove the temporary file and reset internal status.
+ */
+ function cleanup() {
+ if (tempFile) {
+ try {
+ if (tempFile.exists()) {
+ tempFile.remove(false);
+ }
+ } catch (err) {}
+ tempFile = null;
+ }
+
+ downloadInProgress = false;
+ }
+
+ /**
+ * Save the contents of a canvas to the photos directory of the profile.
+ */
+ function saveCanvas(aCanvas) {
+ // Get the photos directory and check that it exists.
+ let file = getPhotosDir();
+ file = makePhotoFile(file, "png");
+
+ // Create a data url from the canvas and then create URIs of the source and
+ // targets.
+ let source = Services.io.newURI(aCanvas.toDataURL("image/png", ""), "UTF8");
+ let target = Services.io.newFileURI(file);
+
+ downloader = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(Ci.nsIWebBrowserPersist);
+ downloader.persistFlags =
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE |
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ Ci.nsIWebBrowserPersist.PERSIST_FLAGS_CLEANUP_ON_FAILURE;
+ downloader.progressListener = {
+ onStateChange(aWebProgress, aRequest, aFlag, aStatus) {
+ if ((aFlag & Ci.nsIWebProgressListener.STATE_STOP) &&
+ !(aFlag & Ci.nsIWebProgressListener.STATE_IS_REQUEST)) {
+ if (callbackSuccess) {
+ callbackSuccess(file.leafName);
+ }
+ }
+ },
+ };
+
+ // Obtain the privacy context of the browser window that the URL
+ // we are downloading comes from. If, and only if, the URL is not
+ // related to a window, null should be used instead.
+ let privacy = PrivateBrowsingUtils.privacyContextFromWindow(window);
+ downloader.saveURI(source, null, null, null, null, null, target, privacy);
+ }
+
+ // Publicly accessible methods.
+ return { cancelSave, savePhoto, STATE_TRANSFERRING, STATE_RESIZING, STATE_OK,
+ ERROR_UNAVAILABLE, ERROR_INVALID_URI, ERROR_INVALID_IMG,
+ ERROR_SAVE, };
+})();
+
+/**
+ * Validates the given year and returns it, if it looks sane.
+ * Returns kDefaultYear (a leap year), if no valid date is given.
+ * This ensures that month/day calculations still work.
+ */
+function saneBirthYear(aYear) {
+ return aYear && (aYear <= kMaxYear) && (aYear >= kMinYear) ? aYear : kDefaultYear;
+}
+
+/**
+ * Returns the nearest leap year before aYear.
+ */
+function nearestLeap(aYear) {
+ for (let year = aYear; year > 0; year--) {
+ if (new Date(year, 1, 29).getMonth() == 1)
+ return year;
+ }
+ return 2000;
+}
diff --git a/comm/suite/mailnews/components/addrbook/content/abEditCardDialog.xul b/comm/suite/mailnews/components/addrbook/content/abEditCardDialog.xul
new file mode 100644
index 0000000000..752934becb
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abEditCardDialog.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abCardOverlay.xul"?>
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="abcardWindow"
+ onload="OnLoadEditCard()"
+ ondialogaccept="return EditCardOKButton();"
+ ondialogcancel="return EditCardCancelButton();">
+
+ <stringbundleset id="stringbundleset"/>
+ <vbox id="editcard"/>
+</dialog>
diff --git a/comm/suite/mailnews/components/addrbook/content/abEditListDialog.xul b/comm/suite/mailnews/components/addrbook/content/abEditListDialog.xul
new file mode 100644
index 0000000000..3162664e99
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abEditListDialog.xul
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abListOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abMailListDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ id="ablistWindow"
+ onload="OnLoadEditList()"
+ ondialogaccept="return EditListOKButton();"
+ ondragover="DragOverAddressListTree(event);"
+ ondrop="DropOnAddressListTree(event);">
+
+ <stringbundleset id="stringbundleset"/>
+ <vbox id="editlist" flex="1"/>
+</dialog>
diff --git a/comm/suite/mailnews/components/addrbook/content/abListOverlay.xul b/comm/suite/mailnews/components/addrbook/content/abListOverlay.xul
new file mode 100644
index 0000000000..9f8c07f010
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abListOverlay.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressingWidget.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/cardDialog.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/addressbook/abMailListDialog.dtd">
+
+<overlay id="editListOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
+ <script src="chrome://messenger/content/addressbook/abCommon.js"/>
+ <script src="chrome://messenger/content/addressbook/abMailListDialog.js"/>
+
+<vbox id="editlist">
+ <grid>
+ <columns>
+ <column/>
+ <column class="CardEditWidth" flex="1"/>
+ </columns>
+
+ <rows>
+ <row id="ListNameContainer" align="center">
+ <label control="ListName"
+ class="CardEditLabel"
+ value="&ListName.label;"
+ accesskey="&ListName.accesskey;"/>
+ <textbox id="ListName"/>
+ </row>
+
+ <row id="ListNickNameContainer" align="center">
+ <label control="ListNickName"
+ class="CardEditLabel"
+ value="&ListNickName.label;"
+ accesskey="&ListNickName.accesskey;"/>
+ <textbox id="ListNickName"/>
+ </row>
+
+ <row id="ListDescriptionContainer" align="center">
+ <label control="ListDescription"
+ class="CardEditLabel"
+ value="&ListDescription.label;"
+ accesskey="&ListDescription.accesskey;"/>
+ <textbox id="ListDescription"/>
+ </row>
+ </rows>
+ </grid>
+
+ <spacer style="height:1em"/>
+ <label control="addressCol1#1"
+ value="&AddressTitle.label;"
+ accesskey="&AddressTitle.accesskey;"/>
+
+ <spacer style="height:0.1em"/>
+
+ <listbox id="addressingWidget" style="height: 15em;"
+ onclick="awClickEmptySpace(event.target, true)">
+ <listitem class="addressingWidgetItem" allowevents="true">
+ <listcell class="addressingWidgetCell">
+ <textbox id="addressCol1#1"
+ class="plain textbox-addressingWidget uri-element"
+ type="autocomplete"
+ flex="1"
+ autocompletesearch="addrbook ldap"
+ autocompletesearchparam="{}" timeout="300" maxrows="4"
+ completedefaultindex="true" forcecomplete="true"
+ minresultsforpopup="3"
+ ontextentered="awRecipientTextCommand(eventParam, this)"
+ onkeydown="awRecipientKeyDown(event, this);"
+ onclick="awNotAnEmptyArea(event);">
+ <image onclick="awNotAnEmptyArea(event)" class="person-icon"/>
+ </textbox>
+ </listcell>
+ </listitem>
+ </listbox>
+</vbox>
+
+</overlay>
+
diff --git a/comm/suite/mailnews/components/addrbook/content/abMailListDialog.xul b/comm/suite/mailnews/components/addrbook/content/abMailListDialog.xul
new file mode 100644
index 0000000000..f335bfdce3
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abMailListDialog.xul
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abListOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abMailListDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="ablistWindow"
+ title="&mailListWindow.title;"
+ ondialogaccept="return MailListOKButton();"
+ onload="OnLoadNewMailList()"
+ ondragover="DragOverAddressListTree(event);"
+ ondrop="DropOnAddressListTree(event);">
+
+ <stringbundleset id="stringbundleset"/>
+
+ <hbox align="center" valign="center">
+ <label control="abPopup" value="&addToAddressBook.label;" accesskey="&addToAddressBook.accesskey;"/>
+ <menulist id="abPopup">
+ <menupopup id="abPopup-menupopup" class="addrbooksPopup" writable="true"
+ supportsmaillists="true"/>
+ </menulist>
+ </hbox>
+
+ <spacer style="height:1em"/>
+
+ <vbox id="editlist"/>
+
+</dialog>
diff --git a/comm/suite/mailnews/components/addrbook/content/abNewCardDialog.xul b/comm/suite/mailnews/components/addrbook/content/abNewCardDialog.xul
new file mode 100644
index 0000000000..80f4e578e3
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abNewCardDialog.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abCardOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abNewCardDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="abcardWindow"
+ windowtype="mailnews:newcarddialog"
+ onload="OnLoadNewCard()"
+ ondialogaccept="return NewCardOKButton();"
+ ondialogcancel="return NewCardCancelButton();">
+
+ <stringbundleset id="stringbundleset"/>
+
+ <hbox align="center">
+
+ <label id="abPopupLabel" control="abPopup" value="&chooseAddressBook.label;" accesskey="&chooseAddressBook.accesskey;"/>
+
+ <menulist id="abPopup">
+ <menupopup id="abPopup-menupopup" class="addrbooksPopup" writable="true"/>
+ </menulist>
+
+ </hbox>
+
+ <spacer style="height:1em"/>
+
+ <vbox id="editcard"/>
+
+</dialog>
diff --git a/comm/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul b/comm/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul
new file mode 100644
index 0000000000..08af2de3fc
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/abResultsPane.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/addressbook/abResultsPaneOverlay.dtd">
+
+<overlay
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script src="chrome://messenger/content/addressbook/abResultsPane.js"/>
+<script src="chrome://global/content/nsDragAndDrop.js"/>
+<script src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+
+<tree id="abResultsTree" flex="1" enableColumnDrag="true" class="plain focusring"
+ onclick="AbResultsPaneOnClick(event);"
+ onselect="this.view.selectionChanged(); document.commandDispatcher.updateCommands('addrbook-select');"
+ sortCol="GeneratedName"
+ persist="sortCol height">
+
+ <treecols id="abResultsTreeCols">
+ <!-- these column ids must match up to the mork column names, except for GeneratedName and ChatName, see nsIAddrDatabase.idl -->
+ <treecol id="GeneratedName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&GeneratedName.label;" primary="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="PrimaryEmail"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&PrimaryEmail.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="ChatName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&ChatName.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Company"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&Company.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="NickName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&NickName.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="SecondEmail"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&SecondEmail.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="Department"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&Department.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="JobTitle"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&JobTitle.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="CellularNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&CellularNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="PagerNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&PagerNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="FaxNumber"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&FaxNumber.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="HomePhone"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&HomePhone.label;" hidden="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="WorkPhone"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&WorkPhone.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="addrbook"
+ persist="hidden ordinal width sortDirection" flex="1"
+ hidden="true"
+ label="&Addrbook.label;"/>
+ <!-- LOCALIZATION NOTE: _PhoneticName may be enabled for Japanese builds. -->
+ <!--
+ <splitter class="tree-splitter"/>
+ <treecol id="_PhoneticName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&_PhoneticName.label;" hidden="true"/>
+ -->
+
+ </treecols>
+ <treechildren ondragstart="nsDragAndDrop.startDrag(event, abResultsPaneObserver);"/>
+</tree>
+
+</overlay>
+
diff --git a/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js b/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js
new file mode 100644
index 0000000000..7e23a0b89d
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js
@@ -0,0 +1,399 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {encodeABTermValue, getModelQuery} = ChromeUtils.import("resource:///modules/ABQueryUtils.jsm");
+
+var addressbook = 0;
+var composeWindow = 0;
+var msgCompFields = 0;
+var editCardCallback = 0;
+
+var gSearchInput;
+var gSearchTimer = null;
+var gQueryURIFormat = null;
+
+// localization strings
+var prefixTo;
+var prefixCc;
+var prefixBcc;
+
+var gToButton;
+var gCcButton;
+var gBccButton;
+
+var gActivatedButton;
+
+var gDragService = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService);
+
+var gSelectAddressesAbViewListener = {
+ onSelectionChanged: function() {
+ ResultsPaneSelectionChanged();
+ },
+ onCountChanged: function(total) {
+ // do nothing
+ }
+};
+
+function GetAbViewListener()
+{
+ return gSelectAddressesAbViewListener;
+}
+
+function OnLoadSelectAddress()
+{
+ InitCommonJS();
+
+ prefixTo = gAddressBookBundle.getString("prefixTo") + ": ";
+ prefixCc = gAddressBookBundle.getString("prefixCc") + ": ";
+ prefixBcc = gAddressBookBundle.getString("prefixBcc") + ": ";
+
+ UpgradeAddressBookResultsPaneUI("mailnews.ui.select_addresses_results.version");
+
+ var toAddress="", ccAddress="", bccAddress="";
+
+ // look in arguments[0] for parameters
+ if (window.arguments && window.arguments[0])
+ {
+ // keep parameters in global for later
+ if ( window.arguments[0].composeWindow )
+ top.composeWindow = window.arguments[0].composeWindow;
+ if ( window.arguments[0].msgCompFields )
+ top.msgCompFields = window.arguments[0].msgCompFields;
+ if ( window.arguments[0].toAddress )
+ toAddress = window.arguments[0].toAddress;
+ if ( window.arguments[0].ccAddress )
+ ccAddress = window.arguments[0].ccAddress;
+ if ( window.arguments[0].bccAddress )
+ bccAddress = window.arguments[0].bccAddress;
+
+ // put the addresses into the bucket
+ AddAddressFromComposeWindow(toAddress, prefixTo);
+ AddAddressFromComposeWindow(ccAddress, prefixCc);
+ AddAddressFromComposeWindow(bccAddress, prefixBcc);
+ }
+
+ gSearchInput = document.getElementById("searchInput");
+
+ // Reselect the persisted address book if possible, if not just select the
+ // first in the list.
+ var temp = abList.value;
+ abList.selectedItem = null;
+ abList.value = temp;
+ if (!abList.selectedItem)
+ abList.selectedIndex = 0;
+
+ ChangeDirectoryByURI(abList.value);
+
+ DialogBucketPaneSelectionChanged();
+
+ var workPhoneCol = document.getElementById("WorkPhone");
+ workPhoneCol.setAttribute("hidden", "true");
+
+ var companyCol = document.getElementById("Company");
+ companyCol.setAttribute("hidden", "true");
+
+ gToButton = document.getElementById("toButton");
+ gCcButton = document.getElementById("ccButton");
+ gBccButton = document.getElementById("bccButton");
+
+ gAbResultsTree.focus();
+
+ gActivatedButton = gToButton;
+
+ document.documentElement.addEventListener("keypress", OnReturnHit, true);
+}
+
+function OnUnloadSelectAddress()
+{
+ CloseAbView();
+}
+
+function AddAddressFromComposeWindow(addresses, prefix)
+{
+ if ( addresses )
+ {
+ var emails = {};
+ var names = {};
+ var fullNames = {};
+ var numAddresses = MailServices.headerParser.parseHeadersWithArray(addresses, emails, names, fullNames);
+
+ for ( var index = 0; index < numAddresses; index++ )
+ {
+ AddAddressIntoBucket(prefix, fullNames.value[index], emails.value[index]);
+ }
+ }
+}
+
+function SelectAddressOKButton()
+{
+ // Empty email checks are now done in AddAddressIntoBucket below.
+ var body = document.getElementById('bucketBody');
+ var item, row, cell, prefix, address, email;
+ var toAddress="", ccAddress="", bccAddress="", emptyEmail="";
+
+ for ( var index = 0; index < body.childNodes.length; index++ )
+ {
+ item = body.childNodes[index];
+ if ( item.childNodes && item.childNodes.length )
+ {
+ row = item.childNodes[0];
+ if ( row.childNodes && row.childNodes.length )
+ {
+ cell = row.childNodes[0];
+ prefix = cell.getAttribute('prefix');
+ address = cell.getAttribute('address');
+ email = cell.getAttribute('email');
+ if ( prefix )
+ {
+ switch ( prefix )
+ {
+ case prefixTo:
+ if ( toAddress )
+ toAddress += ", ";
+ toAddress += address;
+ break;
+ case prefixCc:
+ if ( ccAddress )
+ ccAddress += ", ";
+ ccAddress += address;
+ break;
+ case prefixBcc:
+ if ( bccAddress )
+ bccAddress += ", ";
+ bccAddress += address;
+ break;
+ }
+ }
+ }
+ }
+ }
+ // reset the UI in compose window
+ msgCompFields.to = toAddress;
+ msgCompFields.cc = ccAddress;
+ msgCompFields.bcc = bccAddress;
+ top.composeWindow.CompFields2Recipients(top.msgCompFields);
+
+ return true;
+}
+
+function SelectAddressToButton()
+{
+ AddSelectedAddressesIntoBucket(prefixTo);
+ gActivatedButton = gToButton;
+}
+
+function SelectAddressCcButton()
+{
+ AddSelectedAddressesIntoBucket(prefixCc);
+ gActivatedButton = gCcButton;
+}
+
+function SelectAddressBccButton()
+{
+ AddSelectedAddressesIntoBucket(prefixBcc);
+ gActivatedButton = gBccButton;
+}
+
+function AddSelectedAddressesIntoBucket(prefix)
+{
+ var cards = GetSelectedAbCards();
+ var count = cards.length;
+
+ for (var i = 0; i < count; i++) {
+ AddCardIntoBucket(prefix, cards[i]);
+ }
+}
+
+function AddCardIntoBucket(prefix, card)
+{
+ var address = GenerateAddressFromCard(card);
+ if (card.isMailList) {
+ AddAddressIntoBucket(prefix, address, card.displayName);
+ }
+ else {
+ AddAddressIntoBucket(prefix, address, card.primaryEmail);
+ }
+}
+
+function AddAddressIntoBucket(prefix, address, email)
+{
+ if (!email)
+ {
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("emptyEmailAddCardTitle"),
+ gAddressBookBundle.getString("emptyEmailAddCard"));
+ }
+ else
+ {
+ var body = document.getElementById("bucketBody");
+
+ var item = document.createElement('treeitem');
+ var row = document.createElement('treerow');
+ var cell = document.createElement('treecell');
+ cell.setAttribute('label', prefix + address);
+ cell.setAttribute('prefix', prefix);
+ cell.setAttribute('address', address);
+ cell.setAttribute('email', email);
+
+ row.appendChild(cell);
+ item.appendChild(row);
+ body.appendChild(item);
+ }
+}
+
+function RemoveSelectedFromBucket()
+{
+ var bucketTree = document.getElementById("addressBucket");
+ if ( bucketTree )
+ {
+ var body = document.getElementById("bucketBody");
+ var selection = bucketTree.view.selection;
+ var rangeCount = selection.getRangeCount();
+
+ for (var i = rangeCount-1; i >= 0; --i)
+ {
+ var start = {}, end = {};
+ selection.getRangeAt(i,start,end);
+ for (var j = end.value; j >= start.value; --j)
+ {
+ bucketTree.contentView.getItemAtIndex(j).remove();
+ }
+ }
+ }
+}
+
+/* Function: ResultsPaneSelectionChanged()
+ * Callers : OnLoadSelectAddress(), abCommon.js:ResultsPaneSelectionChanged()
+ * -------------------------------------------------------------------------
+ * This function is used to grab the selection state of the results tree to maintain
+ * the appropriate enabled/disabled states of the "Edit", "To:", "CC:", and "Bcc:" buttons.
+ * If an entry is selected in the results Tree, then the "disabled" attribute is removed.
+ * Otherwise, if nothing is selected, "disabled" is set to true.
+ */
+
+function ResultsPaneSelectionChanged()
+{;
+ var editButton = document.getElementById("edit");
+ var toButton = document.getElementById("toButton");
+ var ccButton = document.getElementById("ccButton");
+ var bccButton = document.getElementById("bccButton");
+
+ var numSelected = GetNumSelectedCards();
+ if (numSelected > 0)
+ {
+ if (numSelected == 1)
+ editButton.removeAttribute("disabled");
+ else
+ editButton.setAttribute("disabled", "true");
+
+ toButton.removeAttribute("disabled");
+ ccButton.removeAttribute("disabled");
+ bccButton.removeAttribute("disabled");
+ }
+ else
+ {
+ editButton.setAttribute("disabled", "true");
+ toButton.setAttribute("disabled", "true");
+ ccButton.setAttribute("disabled", "true");
+ bccButton.setAttribute("disabled", "true");
+ }
+}
+
+/* Function: DialogBucketPaneSelectionChanged()
+ * Callers : OnLoadSelectAddress(), abSelectAddressesDialog.xul:id="addressBucket"
+ * -------------------------------------------------------------------------------
+ * This function is used to grab the selection state of the bucket tree to maintain
+ * the appropriate enabled/disabled states of the "Remove" button.
+ * If an entry is selected in the bucket Tree, then the "disabled" attribute is removed.
+ * Otherwise, if nothing is selected, "disabled" is set to true.
+ */
+
+function DialogBucketPaneSelectionChanged()
+{
+ var bucketTree = document.getElementById("addressBucket");
+ var removeButton = document.getElementById("remove");
+
+ removeButton.disabled = bucketTree.view.selection.count == 0;
+}
+
+function AbResultsPaneDoubleClick(card)
+{
+ AddCardIntoBucket(prefixTo, card);
+}
+
+function OnClickedCard(card)
+{
+ // in the select address dialog, do nothing on click
+}
+
+function UpdateCardView()
+{
+ // in the select address dialog, do nothing
+}
+
+function DropRecipient(address)
+{
+ AddAddressFromComposeWindow(address, prefixTo);
+}
+
+function OnReturnHit(event)
+{
+ if (event.keyCode == 13) {
+ var focusedElement = document.commandDispatcher.focusedElement;
+ if (focusedElement && (focusedElement.id == "addressBucket"))
+ return;
+ event.stopPropagation();
+ if (focusedElement && (focusedElement.id == "abResultsTree"))
+ gActivatedButton.doCommand();
+ }
+}
+
+
+function onEnterInSearchBar()
+{
+ var selectedNode = abList.selectedItem;
+
+ if (!selectedNode)
+ return;
+
+ if (!gQueryURIFormat) {
+ // Get model query from pref. We don't want the query starting with "?"
+ // as we have to prefix "?and" to this format.
+ gQueryURIFormat = getModelQuery("mail.addr_book.quicksearchquery.format");
+ }
+
+ var searchURI = selectedNode.value;
+
+ // Use helper method to split up search query to multi-word search
+ // query against multiple fields.
+ let searchWords = getSearchTokens(gSearchInput.value);
+ searchURI += generateQueryURI(gQueryURIFormat, searchWords);
+
+ SetAbView(searchURI);
+
+ SelectFirstCard();
+}
+
+function DirPaneSelectionChangeMenulist()
+{
+ if (abList && abList.selectedItem) {
+ if (gSearchInput.value && (gSearchInput.value != ""))
+ onEnterInSearchBar();
+ else
+ ChangeDirectoryByURI(abList.value);
+ }
+
+ // Hide the addressbook column if the selected addressbook isn't
+ // "All address books". Since the column is redundant in all other cases.
+ let addrbookColumn = document.getElementById("addrbook");
+ if (abList.value.startsWith(kAllDirectoryRoot + "?")) {
+ addrbookColumn.hidden = !gShowAbColumnInComposeSidebar;
+ addrbookColumn.removeAttribute("ignoreincolumnpicker");
+ } else {
+ addrbookColumn.hidden = true;
+ addrbookColumn.setAttribute("ignoreincolumnpicker", "true");
+ }
+}
diff --git a/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.xul b/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.xul
new file mode 100644
index 0000000000..067139e7ec
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.xul
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/selectAddressesDialog.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/addressbook/abResultsPaneOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % abSelectAddressesDialogDTD SYSTEM "chrome://messenger/locale/addressbook/abSelectAddressesDialog.dtd" >
+%abSelectAddressesDialogDTD;
+<!ENTITY % abResultsPaneOverlayDTD SYSTEM "chrome://messenger/locale/addressbook/abResultsPaneOverlay.dtd" >
+%abResultsPaneOverlayDTD;
+]>
+
+<dialog id="selectAddressesWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&selectAddressWindow.title;"
+ style="width: 50em; height: 35em;"
+ persist="width height screenX screenY"
+ buttons="accept,cancel"
+ ondialogaccept="return SelectAddressOKButton();"
+ onload="OnLoadSelectAddress();"
+ onunload="OnUnloadSelectAddress();">
+
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_composeMsgs" src="chrome://messenger/locale/messengercompose/composeMsgs.properties"/>
+
+ <script src="chrome://messenger/content/addressbook/abCommon.js"/>
+ <script src="chrome://messenger/content/addressbook/abSelectAddressesDialog.js"/>
+ <script src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+ <script src="chrome://messenger/content/messengercompose/MsgComposeCommands.js"/>
+ <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
+ <script src="chrome://global/content/globalOverlay.js"/>
+
+ <vbox flex="1">
+
+ <hbox id="topBox" align="center">
+ <label value="&lookIn.label;" accesskey="&lookIn.accesskey;" control="addressbookList"/>
+ <menulist id="addressbookList"
+ oncommand="DirPaneSelectionChangeMenulist(); document.commandDispatcher.updateCommands('addrbook-select');">
+ <menupopup id="addressbookList-menupopup" class="addrbooksPopup"/>
+ </menulist>
+ <label value="&for.label;" accesskey="&for.accesskey;" control="searchInput"/>
+ <textbox id="searchInput" flex="1" type="search"
+ aria-controls="abResultsTree"
+ placeholder="&for.placeholder;"
+ oncommand="onEnterInSearchBar();" clickSelectsAll="true"/>
+ </hbox>
+
+ <hbox flex="1">
+
+ <vbox id="resultsBox" flex="4">
+ <tree id="abResultsTree" flex="1" persist="height"/>
+ </vbox>
+
+ <vbox id="addToBucketButtonBox">
+ <spacer flex="1"/>
+ <button id="toButton" label="&toButton.label;" accesskey="&toButton.accesskey;" oncommand="SelectAddressToButton()"/>
+ <spacer class="middle-button-spacer"/>
+ <button id="ccButton" label="&ccButton.label;" accesskey="&ccButton.accesskey;" oncommand="SelectAddressCcButton()"/>
+ <spacer class="middle-button-spacer"/>
+ <button id="bccButton" label="&bccButton.label;" accesskey="&bccButton.accesskey;" oncommand="SelectAddressBccButton()"/>
+ <spacer class="above-remove-spacer"/>
+ <button id="remove" label="&removeButton.label;" accesskey="&removeButton.accesskey;" class="dialog" oncommand="RemoveSelectedFromBucket()"/>
+ <spacer flex="1"/>
+ </vbox>
+
+ <vbox id="bucketBox" flex="1">
+ <label value="&addressMessageTo.label;" control="addressBucket"/>
+ <tree id="addressBucket" flex="1" hidecolumnpicker="true"
+ ondragover="DragAddressOverTargetControl(event);"
+ ondrop="DropOnAddressingTarget(event, false);"
+ onselect="DialogBucketPaneSelectionChanged();">
+ <treecols>
+ <treecol id="addressCol" flex="1" hideheader="true"/>
+ </treecols>
+ <treechildren id="bucketBody" flex="1"/>
+ </tree>
+ </vbox>
+
+ </hbox>
+
+ <hbox id="newEditButtonBox">
+ <button id="new" label="&newButton.label;" accesskey="&newButton.accesskey;" tooltiptext="&addressPickerNewButton.tooltip;" oncommand="AbNewCard();"/>
+ <button id="edit" label="&editButton.label;" accesskey="&editButton.accesskey;" tooltiptext="&addressPickerEditButton.tooltip;" oncommand="AbEditSelectedCard()"/>
+ </hbox>
+ </vbox>
+
+</dialog>
diff --git a/comm/suite/mailnews/components/addrbook/content/abTrees.js b/comm/suite/mailnews/components/addrbook/content/abTrees.js
new file mode 100644
index 0000000000..ac52d3464d
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/abTrees.js
@@ -0,0 +1,332 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This file contains our implementation for various addressbook trees. It
+ * depends on jsTreeView.js being loaded before this script is loaded.
+ */
+
+const {IOUtils} = ChromeUtils.import("resource:///modules/IOUtils.js");
+
+// Tree Sort helper methods.
+var AB_ORDER = ["aab", "pab", "mork", "ldap", "mapi+other", "anyab", "cab"];
+
+function getDirectoryValue(aDir, aKey) {
+ if (aKey == "ab_type") {
+ if (aDir._directory.URI == kAllDirectoryRoot + "?")
+ return "aab";
+ if (aDir._directory.URI == kPersonalAddressbookURI)
+ return "pab";
+ if (aDir._directory.URI == kCollectedAddressbookURI)
+ return "cab";
+ if (aDir._directory instanceof Ci.nsIAbMDBDirectory)
+ return "mork";
+ if (aDir._directory instanceof Ci.nsIAbLDAPDirectory)
+ return "ldap";
+
+ // If there is any other AB type.
+ return "mapi+other";
+ } else if (aKey == "ab_name") {
+ return aDir._directory.dirName;
+ }
+
+ // This should never happen.
+ return null;
+}
+
+function abNameCompare(a, b) {
+ return a.localeCompare(b);
+}
+
+function abTypeCompare(a, b) {
+ return (AB_ORDER.indexOf(a) - AB_ORDER.indexOf(b));
+}
+
+var SORT_PRIORITY = ["ab_type", "ab_name"];
+var SORT_FUNCS = [abTypeCompare, abNameCompare];
+
+function abSort(a, b) {
+ for (let i = 0; i < SORT_FUNCS.length; i++) {
+ let sortBy = SORT_PRIORITY[i];
+ let aValue = getDirectoryValue(a, sortBy);
+ let bValue = getDirectoryValue(b, sortBy);
+
+ if (!aValue && !bValue)
+ return 0;
+ if (!aValue)
+ return -1;
+ if (!bValue)
+ return 1;
+ if (aValue != bValue) {
+ let result = SORT_FUNCS[i](aValue, bValue);
+
+ if (result != 0)
+ return result;
+ }
+ }
+ return 0;
+}
+
+/**
+ * Each abDirTreeItem corresponds to one row in the tree view.
+ */
+function abDirTreeItem(aDirectory)
+{
+ this._directory = aDirectory;
+}
+
+abDirTreeItem.prototype =
+{
+ getText: function atv_getText()
+ {
+ return this._directory.dirName;
+ },
+
+ get id()
+ {
+ return this._directory.URI;
+ },
+
+ _open: false,
+ get open()
+ {
+ return this._open;
+ },
+
+ _level: 0,
+ get level()
+ {
+ return this._level;
+ },
+
+ _children: null,
+ get children()
+ {
+ if (!this._children)
+ {
+ this._children = [];
+ let myEnum;
+ if (this._directory.URI == (kAllDirectoryRoot + "?"))
+ myEnum = MailServices.ab.directories;
+ else
+ myEnum = this._directory.childNodes;
+
+ while (myEnum.hasMoreElements())
+ {
+ var abItem = new abDirTreeItem(
+ myEnum.getNext().QueryInterface(Ci.nsIAbDirectory));
+ if (gDirectoryTreeView&&
+ this.id == kAllDirectoryRoot + "?" &&
+ getDirectoryValue(abItem, "ab_type") == "ldap")
+ gDirectoryTreeView.hasRemoteAB = true;
+
+ abItem._level = this._level + 1;
+ abItem._parent = this;
+ this._children.push(abItem);
+ }
+
+ this._children.sort(abSort);
+ }
+ return this._children;
+ },
+
+ getProperties: function atv_getProps()
+ {
+ var properties = []
+ if (this._directory.isMailList)
+ properties.push("IsMailList-true");
+ if (this._directory.isRemote)
+ properties.push("IsRemote-true");
+ if (this._directory.isSecure)
+ properties.push("IsSecure-true");
+ return properties.join(" ");
+ }
+};
+
+/**
+ * Our actual implementation of nsITreeView.
+ */
+function directoryTreeView() {}
+directoryTreeView.prototype =
+{
+ __proto__: new PROTO_TREE_VIEW(),
+
+ hasRemoteAB: false,
+
+ init: function dtv_init(aTree, aJSONFile)
+ {
+ if (aJSONFile) {
+ // Parse our persistent-open-state json file
+ let data = IOUtils.loadFileToString(aJSONFile);
+ if (data) {
+ this._persistOpenMap = JSON.parse(data);
+ }
+ }
+
+ this._rebuild();
+ aTree.view = this;
+ },
+
+ shutdown: function dtv_shutdown(aJSONFile)
+ {
+ // Write out the persistOpenMap to our JSON file.
+ if (aJSONFile)
+ {
+ // Write out our json file...
+ let data = JSON.stringify(this._persistOpenMap);
+ IOUtils.saveStringToFile(aJSONFile, data);
+ }
+ },
+
+ // Override the dnd methods for those functions in abDragDrop.js
+ canDrop: function dtv_canDrop(aIndex, aOrientation, dataTransfer)
+ {
+ return abDirTreeObserver.canDrop(aIndex, aOrientation, dataTransfer);
+ },
+
+ drop: function dtv_drop(aRow, aOrientation, dataTransfer)
+ {
+ abDirTreeObserver.onDrop(aRow, aOrientation, dataTransfer);
+ },
+
+ getDirectoryAtIndex: function dtv_getDirForIndex(aIndex)
+ {
+ return this._rowMap[aIndex]._directory;
+ },
+
+ getIndexOfDirectory: function dtv_getIndexOfDir(aItem)
+ {
+ for (var i = 0; i < this._rowMap.length; i++)
+ if (this._rowMap[i]._directory == aItem)
+ return i;
+
+ return -1;
+ },
+
+ // Override jsTreeView's isContainer, since we want to be able
+ // to react to drag-drop events for all items in the directory
+ // tree.
+ isContainer: function dtv_isContainer(aIndex)
+ {
+ return true;
+ },
+
+ /**
+ * NOTE: This function will result in indeterminate rows being selected.
+ * Callers should take care to re-select a desired row after calling
+ * this function.
+ */
+ _rebuild: function dtv__rebuild() {
+ this._rowMap = [];
+
+ // Make an entry for All Address Books.
+ let rootAB = MailServices.ab.getDirectory(kAllDirectoryRoot + "?");
+ rootAB.dirName = gAddressBookBundle.getString("allAddressBooks");
+ this._rowMap.push(new abDirTreeItem(rootAB));
+
+ // Sort our addressbooks now.
+ this._rowMap.sort(abSort);
+
+ this._restoreOpenStates();
+ },
+
+ getIndexForId: function(aId) {
+ for (let i = 0; i < this._rowMap.length; i++) {
+ if (this._rowMap[i].id == aId)
+ return i;
+ }
+
+ return -1;
+ },
+
+ // nsIAbListener interfaces
+ onItemAdded: function dtv_onItemAdded(aParent, aItem)
+ {
+ if (!(aItem instanceof Ci.nsIAbDirectory))
+ return;
+
+ var oldCount = this._rowMap.length;
+ var tree = this._tree;
+ this._tree = null;
+ this._rebuild();
+ if (!tree)
+ return;
+
+ this._tree = tree;
+ var itemIndex = this.getIndexOfDirectory(aItem);
+ tree.rowCountChanged(itemIndex, this._rowMap.length - oldCount);
+ var parentIndex = this.getIndexOfDirectory(aParent);
+ if (parentIndex > -1)
+ tree.invalidateRow(parentIndex);
+ },
+
+ onItemRemoved: function dtv_onItemRemoved(aParent, aItem)
+ {
+ if (!(aItem instanceof Ci.nsIAbDirectory))
+ return;
+
+ var itemIndex = this.getIndexOfDirectory(aItem);
+ var oldCount = this._rowMap.length;
+ var tree = this._tree;
+ this._tree = null;
+ this._rebuild();
+ if (!tree)
+ return;
+
+ this._tree = tree;
+ tree.rowCountChanged(itemIndex, this._rowMap.length - oldCount);
+
+ // This does not currently work, see Bug 1323563.
+ // If we're deleting a top-level address-book, just select the first book.
+ // if (aParent.URI == kAllDirectoryRoot ||
+ // aParent.URI == kAllDirectoryRoot + "?") {
+ // this.selection.select(0);
+ // return;
+ // }
+
+ var parentIndex = this.getIndexOfDirectory(aParent);
+ if (parentIndex > -1)
+ tree.invalidateRow(parentIndex);
+
+ if (!this.selection.count)
+ {
+ // The previously selected item was a member of the deleted subtree.
+ // Select the parent of the subtree.
+ // If there is no parent, select the next item.
+ // If there is no next item, select the first item.
+ var newIndex = parentIndex;
+ if (newIndex < 0)
+ newIndex = itemIndex;
+ if (newIndex >= this._rowMap.length)
+ newIndex = 0;
+
+ this.selection.select(newIndex);
+ }
+ },
+
+ onItemPropertyChanged: function dtv_onItemProp(aItem, aProp, aOld, aNew)
+ {
+ if (!(aItem instanceof Ci.nsIAbDirectory))
+ return;
+
+ var index = this.getIndexOfDirectory(aItem);
+ var current = this.getDirectoryAtIndex(this.selection.currentIndex);
+ var tree = this._tree;
+ this._tree = null;
+ this._rebuild();
+ this._tree = tree;
+ this.selection.select(this.getIndexOfDirectory(current));
+
+ if (index > -1) {
+ var newIndex = this.getIndexOfDirectory(aItem);
+ if (newIndex >= index)
+ this._tree.invalidateRange(index, newIndex);
+ else
+ this._tree.invalidateRange(newIndex, index);
+ }
+ }
+};
+
+var gDirectoryTreeView = new directoryTreeView();
diff --git a/comm/suite/mailnews/components/addrbook/content/addressbook-panel.js b/comm/suite/mailnews/components/addrbook/content/addressbook-panel.js
new file mode 100644
index 0000000000..9fa0a125e0
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/addressbook-panel.js
@@ -0,0 +1,119 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gIsMsgCompose = false;
+
+function GetAbViewListener()
+{
+ // the ab panel doesn't care if the total changes, or if the selection changes
+ return null;
+}
+
+var mutationObs = null;
+
+function AbPanelLoad()
+{
+ InitCommonJS();
+
+ UpgradeAddressBookResultsPaneUI("mailnews.ui.addressbook_panel_results.version");
+
+ var abPopup = document.getElementById('addressbookList');
+
+ // Reselect the persisted address book if possible, if not just select the
+ // first in the list.
+ var temp = abPopup.value;
+ abPopup.selectedItem = null;
+ abPopup.value = temp;
+ if (!abPopup.selectedItem)
+ abPopup.selectedIndex = 0;
+
+ ChangeDirectoryByURI(abPopup.value);
+
+ mutationObs = new MutationObserver(function(aMutations) {
+ aMutations.forEach(function(mutation) {
+ if (getSelectedDirectoryURI() == (kAllDirectoryRoot + "?") &&
+ mutation.type == "attributes" &&
+ mutation.attributeName == "hidden") {
+ let curState = document.getElementById("addrbook").hidden;
+ gShowAbColumnInComposeSidebar = !curState;
+ }
+ });
+ });
+
+ document.getElementById("addrbook").hidden = !gShowAbColumnInComposeSidebar;
+
+ mutationObs.observe(document.getElementById("addrbook"),
+ { attributes: true, childList: true });
+
+ gSearchInput = document.getElementById("searchInput");
+
+ // for the compose window we want to show To, Cc, Bcc and a separator
+ // for all other windows we want to show Compose Mail To
+ var popup = document.getElementById("composeMail");
+ gIsMsgCompose = parent.document
+ .documentElement
+ .getAttribute("windowtype") == "msgcompose";
+ for (var i = 0; i < 4; i++)
+ popup.childNodes[i].hidden = !gIsMsgCompose;
+ popup.childNodes[4].hidden = gIsMsgCompose;
+}
+
+function AbPanelUnload()
+{
+ mutationObs.disconnect();
+
+ CloseAbView();
+}
+
+function AbPanelAdd(addrtype)
+{
+ var cards = GetSelectedAbCards();
+ var count = cards.length;
+
+ for (var i = 0; i < count; i++) {
+ // turn each card into a properly formatted address
+ var address = GenerateAddressFromCard(cards[i]);
+ if (address)
+ top.awAddRecipient(addrtype, address);
+ else
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("emptyEmailAddCardTitle"),
+ gAddressBookBundle.getString("emptyEmailAddCard"));
+ }
+}
+
+function AbPanelNewCard()
+{
+ goNewCardDialog(abList.value);
+}
+
+function AbPanelNewList()
+{
+ goNewListDialog(abList.value);
+}
+
+function ResultsPaneSelectionChanged()
+{
+ // do nothing for ab panel
+}
+
+function OnClickedCard()
+{
+ // do nothing for ab panel
+}
+
+function AbResultsPaneDoubleClick(card)
+{
+ // double click for ab panel means "send mail to this person / list"
+ if (gIsMsgCompose)
+ AbPanelAdd('addr_to');
+ else
+ AbNewMessage();
+}
+
+function UpdateCardView()
+{
+ // do nothing for ab panel
+}
diff --git a/comm/suite/mailnews/components/addrbook/content/addressbook-panel.xul b/comm/suite/mailnews/components/addrbook/content/addressbook-panel.xul
new file mode 100644
index 0000000000..72f650ffdb
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/addressbook-panel.xul
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: xml; indent-tabs-mode: nil; -*-
+ - This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/sidebarPanel.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/addressPanes.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/abResultsPane.css" type="text/css"?>
+
+<!DOCTYPE page [
+<!ENTITY % abSelectAddressesDialogDTD SYSTEM "chrome://messenger/locale/addressbook/abSelectAddressesDialog.dtd" >
+%abSelectAddressesDialogDTD;
+<!ENTITY % abResultsPaneOverlayDTD SYSTEM "chrome://messenger/locale/addressbook/abResultsPaneOverlay.dtd" >
+%abResultsPaneOverlayDTD;
+]>
+
+<page id="addressbook-panel"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="AbPanelLoad();"
+ onunload="AbPanelUnload();"
+ title="&selectAddressWindow.title;"
+ selectedaddresses="true">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://global/content/globalOverlay.js"/>
+ <script src="chrome://global/content/nsDragAndDrop.js"/>
+ <script src="chrome://messenger/content/addressbook/addressbook.js"/>
+ <script src="chrome://messenger/content/addressbook/abCommon.js"/>
+ <script src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+ <script src="chrome://messenger/content/addressbook/abResultsPane.js"/>
+ <script src="chrome://messenger/content/addressbook/abSelectAddressesDialog.js"/>
+ <script src="chrome://messenger/content/addressbook/addressbook-panel.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+
+ <commandset id="addressbook-panel-commandset">
+ <command id="cmd_newlist" oncommand="AbNewList();"/>
+ <command id="cmd_properties" oncommand="goDoCommand('cmd_properties');"/>
+ </commandset>
+
+ <menupopup id="composeMail" onpopupshowing="CommandUpdate_AddressBook();">
+ <menuitem label="&toButton.label;" accesskey="&toButton.accesskey;" oncommand="AbPanelAdd('addr_to');" default="true"/>
+ <menuitem label="&ccButton.label;" accesskey="&ccButton.accesskey;" oncommand="AbPanelAdd('addr_cc');"/>
+ <menuitem label="&bccButton.label;" accesskey="&bccButton.accesskey;" oncommand="AbPanelAdd('addr_bcc');"/>
+ <menuseparator/>
+ <menuitem label="&composeEmail.label;" accesskey="&composeEmail.accesskey;" oncommand="AbNewMessage();" default="true"/>
+ <menuitem label="&copyAddress.label;" accesskey="&copyAddress.accesskey;" oncommand="AbCopyAddress();"/>
+ <menuitem label="&deleteAddrBookCard.label;" accesskey="&deleteAddrBookCard.accesskey;" oncommand="AbDelete();"/>
+ <menuseparator/>
+ <menuitem label="&newAddrBookCard.label;" accesskey="&newAddrBookCard.accesskey;" oncommand="AbPanelNewCard();"/>
+ <menuitem label="&newAddrBookMailingList.label;" accesskey="&newAddrBookMailingList.accesskey;" command="cmd_newlist"/>
+ <menuseparator/>
+ <menuitem label="&addrBookCardProperties.label;" accesskey="&addrBookCardProperties.accesskey;" command="cmd_properties"/>
+ </menupopup>
+ <vbox id="results_box" flex="1">
+ <hbox id="panel-bar" class="toolbar" align="center">
+ <label value="&lookIn.label;" control="addressbookList" id="lookInLabel"/>
+ <menulist id="addressbookList"
+ oncommand="DirPaneSelectionChangeMenulist();" flex="1"
+ persist="value">
+ <menupopup id="addressbookList-menupopup" class="addrbooksPopup"/>
+ </menulist>
+ </hbox>
+ <hbox align="center">
+ <label value="&for.label;" id="forLabel" control="searchInput"/>
+ <textbox id="searchInput" flex="1" type="search"
+ aria-labelledby="lookInLabel addressbookList forLabel"
+ aria-controls="abResultsTree"
+ placeholder="&for.placeholder;"
+ oncommand="onEnterInSearchBar();" clickSelectsAll="true"/>
+ </hbox>
+
+ <tree id="abResultsTree" flex="1" context="composeMail" onclick="AbResultsPaneOnClick(event);" class="plain"
+ sortCol="GeneratedName" persist="sortCol">
+ <treecols>
+ <!-- these column ids must match up to the mork column names, see nsIAddrDatabase.idl -->
+ <treecol id="GeneratedName"
+ persist="hidden ordinal width sortDirection" flex="1" label="&GeneratedName.label;" primary="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="addrbook"
+ persist="hidden ordinal width sortDirection" hidden="true"
+ flex="1" label="&Addrbook.label;"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="PrimaryEmail"
+ persist="hidden ordinal width sortDirection"
+ hiddenbydefault="true"
+ flex="1" label="&PrimaryEmail.label;"/>
+ </treecols>
+ <treechildren ondragstart="nsDragAndDrop.startDrag(event, abResultsPaneObserver);"/>
+</tree>
+
+ </vbox>
+</page>
diff --git a/comm/suite/mailnews/components/addrbook/content/addressbook.js b/comm/suite/mailnews/components/addrbook/content/addressbook.js
new file mode 100644
index 0000000000..e1bce6a868
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/addressbook.js
@@ -0,0 +1,581 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {encodeABTermValue, getModelQuery} = ChromeUtils.import("resource:///modules/ABQueryUtils.jsm");
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const nsIAbListener = Ci.nsIAbListener;
+const kPrefMailAddrBookLastNameFirst = "mail.addr_book.lastnamefirst";
+const kPersistCollapseMapStorage = "directoryTree.json";
+
+var gSearchTimer = null;
+var gStatusText = null;
+var gQueryURIFormat = null;
+var gSearchInput;
+var gSearchBox;
+var gCardViewBox;
+var gCardViewBoxEmail1;
+
+var msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Ci.nsIMsgWindow);
+
+// Constants that correspond to choices
+// in Address Book->View -->Show Name as
+const kDisplayName = 0;
+const kLastNameFirst = 1;
+const kFirstNameFirst = 2;
+const kLDAPDirectory = 0; // defined in nsDirPrefs.h
+const kPABDirectory = 2; // defined in nsDirPrefs.h
+
+function OnUnloadAddressBook()
+{
+ MailServices.ab.removeAddressBookListener(gDirectoryTreeView);
+
+ // Shutdown the tree view - this will also save the open/collapsed
+ // state of the tree view to a JSON file.
+ gDirectoryTreeView.shutdown(kPersistCollapseMapStorage);
+
+ MailServices.mailSession.RemoveMsgWindow(msgWindow);
+
+ CloseAbView();
+}
+
+var gAddressBookAbViewListener = {
+ onSelectionChanged: function() {
+ ResultsPaneSelectionChanged();
+ },
+ onCountChanged: function(total) {
+ SetStatusText(total);
+ }
+};
+
+function GetAbViewListener()
+{
+ return gAddressBookAbViewListener;
+}
+
+function OnLoadAddressBook()
+{
+ gSearchInput = document.getElementById("searchInput");
+
+ verifyAccounts(null, false); // this will do migration, if we need to.
+
+ InitCommonJS();
+
+ UpgradeAddressBookResultsPaneUI("mailnews.ui.addressbook_results.version");
+
+ GetCurrentPrefs();
+
+ // FIX ME - later we will be able to use onload from the overlay
+ OnLoadCardView();
+
+ // Before and after callbacks for the customizeToolbar code
+ var abToolbox = getAbToolbox();
+ abToolbox.customizeInit = AbToolboxCustomizeInit;
+ abToolbox.customizeDone = AbToolboxCustomizeDone;
+ abToolbox.customizeChange = AbToolboxCustomizeChange;
+
+ // Initialize the Address Book tree view
+ gDirectoryTreeView.init(gDirTree, kPersistCollapseMapStorage);
+
+ SelectFirstAddressBook();
+
+ // if the pref is locked disable the menuitem New->LDAP directory
+ if (Services.prefs.prefIsLocked("ldap_2.disable_button_add"))
+ document.getElementById("addLDAP").setAttribute("disabled", "true");
+
+ // Add a listener, so we can switch directories if the current directory is
+ // deleted. This listener cares when a directory (= address book), or a
+ // directory item is/are removed. In the case of directory items, we are
+ // only really interested in mailing list changes and not cards but we have
+ // to have both.
+ MailServices.ab.addAddressBookListener(gDirectoryTreeView, nsIAbListener.all);
+
+ gDirTree.controllers.appendController(DirPaneController);
+
+ // Ensure we don't load xul error pages into the main window
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .useErrorPages = false;
+
+ MailServices.mailSession.AddMsgWindow(msgWindow);
+}
+
+function GetCurrentPrefs()
+{
+ // check "Show Name As" menu item based on pref
+ var menuitemID;
+ switch (Services.prefs.getIntPref(kPrefMailAddrBookLastNameFirst))
+ {
+ case kFirstNameFirst:
+ menuitemID = 'firstLastCmd';
+ break;
+ case kLastNameFirst:
+ menuitemID = 'lastFirstCmd';
+ break;
+ case kDisplayName:
+ default:
+ menuitemID = 'displayNameCmd';
+ break;
+ }
+
+ var menuitem = top.document.getElementById(menuitemID);
+ if ( menuitem )
+ menuitem.setAttribute('checked', 'true');
+
+ // show phonetic fields if indicated by the pref
+ if (GetLocalizedStringPref("mail.addr_book.show_phonetic_fields") == "true")
+ document.getElementById("cmd_SortBy_PhoneticName")
+ .setAttribute("hidden", "false");
+}
+
+function SetNameColumn(cmd)
+{
+ var prefValue;
+
+ switch (cmd)
+ {
+ case 'firstLastCmd':
+ prefValue = kFirstNameFirst;
+ break;
+ case 'lastFirstCmd':
+ prefValue = kLastNameFirst;
+ break;
+ case 'displayNameCmd':
+ prefValue = kDisplayName;
+ break;
+ }
+
+ Services.prefs.setIntPref(kPrefMailAddrBookLastNameFirst, prefValue);
+}
+
+function CommandUpdate_AddressBook()
+{
+ goUpdateCommand('cmd_delete');
+ goUpdateCommand('button_delete');
+ goUpdateCommand('cmd_printcardpreview');
+ goUpdateCommand('cmd_printcard');
+ goUpdateCommand('cmd_print');
+ goUpdateCommand('cmd_properties');
+ goUpdateCommand('cmd_newlist');
+}
+
+function ResultsPaneSelectionChanged()
+{
+ UpdateCardView();
+}
+
+function UpdateCardView()
+{
+ var cards = GetSelectedAbCards();
+
+ // display the selected card, if exactly one card is selected.
+ // either no cards, or more than one card is selected, clear the pane.
+ if (cards.length == 1)
+ OnClickedCard(cards[0])
+ else
+ ClearCardViewPane();
+}
+
+function OnClickedCard(card)
+{
+ if (card)
+ DisplayCardViewPane(card);
+ else
+ ClearCardViewPane();
+}
+
+function AbClose()
+{
+ top.close();
+}
+
+function AbPrintCardInternal(doPrintPreview, msgType)
+{
+ var selectedItems = GetSelectedAbCards();
+ var numSelected = selectedItems.length;
+
+ if (!numSelected)
+ return;
+
+ let statusFeedback;
+ statusFeedback = Cc["@mozilla.org/messenger/statusfeedback;1"].createInstance();
+ statusFeedback = statusFeedback.QueryInterface(Ci.nsIMsgStatusFeedback);
+
+ let selectionArray = [];
+
+ for (let i = 0; i < numSelected; i++) {
+ let card = selectedItems[i];
+ let printCardUrl = CreatePrintCardUrl(card);
+ if (printCardUrl) {
+ selectionArray.push(printCardUrl);
+ }
+ }
+
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul",
+ "",
+ "chrome,dialog=no,all",
+ selectionArray.length, selectionArray,
+ statusFeedback, doPrintPreview, msgType);
+
+ return;
+}
+
+function AbPrintCard()
+{
+ AbPrintCardInternal(false, Ci.nsIMsgPrintEngine.MNAB_PRINT_AB_CARD);
+}
+
+function AbPrintPreviewCard()
+{
+ AbPrintCardInternal(true, Ci.nsIMsgPrintEngine.MNAB_PRINTPREVIEW_AB_CARD);
+}
+
+function CreatePrintCardUrl(card)
+{
+ return "data:application/xml;base64," + card.translateTo("base64xml");
+}
+
+function AbPrintAddressBookInternal(doPrintPreview, msgType)
+{
+ let uri = getSelectedDirectoryURI();
+ if (!uri)
+ return;
+
+ var statusFeedback;
+ statusFeedback = Cc["@mozilla.org/messenger/statusfeedback;1"].createInstance();
+ statusFeedback = statusFeedback.QueryInterface(Ci.nsIMsgStatusFeedback);
+
+ /*
+ turn "moz-abmdbdirectory://abook.mab" into
+ "addbook://moz-abmdbdirectory/abook.mab?action=print"
+ */
+
+ var abURIArr = uri.split("://");
+ var printUrl = "addbook://" + abURIArr[0] + "/" + abURIArr[1] + "?action=print"
+
+ printEngineWindow = window.openDialog("chrome://messenger/content/msgPrintEngine.xul",
+ "",
+ "chrome,dialog=no,all",
+ 1, [printUrl],
+ statusFeedback, doPrintPreview, msgType);
+
+ return;
+}
+
+function AbPrintAddressBook()
+{
+ AbPrintAddressBookInternal(false, Ci.nsIMsgPrintEngine.MNAB_PRINT_ADDRBOOK);
+}
+
+function AbPrintPreviewAddressBook()
+{
+ AbPrintAddressBookInternal(true, Ci.nsIMsgPrintEngine.MNAB_PRINTPREVIEW_ADDRBOOK);
+}
+
+/**
+ * Export the currently selected addressbook.
+ */
+function AbExportSelection() {
+ let selectedDirURI = getSelectedDirectoryURI();
+ if (!selectedDirURI)
+ return;
+
+ if (selectedDirURI == (kAllDirectoryRoot + "?"))
+ AbExportAll();
+ else
+ AbExport(selectedDirURI);
+}
+
+/**
+ * Export all found addressbooks, each in a separate file.
+ */
+function AbExportAll()
+{
+ let directories = MailServices.ab.directories;
+
+ while (directories.hasMoreElements()) {
+ let directory = directories.getNext();
+ // Do not export LDAP ABs.
+ if (directory.URI.startsWith(kLdapUrlPrefix))
+ continue;
+
+ AbExport(directory.URI);
+ }
+}
+
+/**
+ * Export the specified addressbook to a file.
+ *
+ * @param aSelectedDirURI The URI of the addressbook to export.
+ */
+function AbExport(aSelectedDirURI)
+{
+ if (!aSelectedDirURI)
+ return;
+
+ try {
+ let directory = GetDirectoryFromURI(aSelectedDirURI);
+ MailServices.ab.exportAddressBook(window, directory);
+ }
+ catch (ex) {
+ var message;
+ switch (ex.result) {
+ case Cr.NS_ERROR_FILE_ACCESS_DENIED:
+ message = gAddressBookBundle.getString("failedToExportMessageFileAccessDenied");
+ break;
+ case Cr.NS_ERROR_FILE_NO_DEVICE_SPACE:
+ message = gAddressBookBundle.getString("failedToExportMessageNoDeviceSpace");
+ break;
+ default:
+ message = ex.message;
+ break;
+ }
+
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("failedToExportTitle"),
+ message);
+ }
+}
+
+function SetStatusText(total)
+{
+ if (!gStatusText)
+ gStatusText = document.getElementById('statusText');
+
+ try {
+ let statusText;
+
+ if (gSearchInput.value) {
+ if (total == 0) {
+ statusText = gAddressBookBundle.getString("noMatchFound");
+ } else {
+ statusText = PluralForm
+ .get(total, gAddressBookBundle.getString("matchesFound1"))
+ .replace("#1", total);
+ }
+ }
+ else
+ statusText =
+ gAddressBookBundle.getFormattedString(
+ "totalContactStatus",
+ [getSelectedDirectory().dirName, total]);
+
+ gStatusText.setAttribute("label", statusText);
+ }
+ catch(ex) {
+ dump("failed to set status text: " + ex + "\n");
+ }
+}
+
+function AbResultsPaneDoubleClick(card)
+{
+ AbEditCard(card);
+}
+
+function onAdvancedAbSearch()
+{
+ let selectedDirURI = getSelectedDirectoryURI();
+ if (!selectedDirURI)
+ return;
+
+ var existingSearchWindow = Services.wm.getMostRecentWindow("mailnews:absearch");
+ if (existingSearchWindow)
+ existingSearchWindow.focus();
+ else
+ window.openDialog("chrome://messenger/content/ABSearchDialog.xul", "",
+ "chrome,resizable,status,centerscreen,dialog=no",
+ {directory: selectedDirURI});
+}
+
+function onEnterInSearchBar()
+{
+ ClearCardViewPane();
+ if (!gQueryURIFormat) {
+ // Get model query from pref. We don't want the query starting with "?"
+ // as we have to prefix "?and" to this format.
+ gQueryURIFormat = getModelQuery("mail.addr_book.quicksearchquery.format");
+ }
+
+ let searchURI = getSelectedDirectoryURI();
+ if (!searchURI) return;
+
+ /*
+ XXX todo, handle the case where the LDAP url
+ already has a query, like
+ moz-abldapdirectory://nsdirectory.netscape.com:389/ou=People,dc=netscape,dc=com?(or(Department,=,Applications))
+ */
+ // Use helper method to split up search query to multi-word search
+ // query against multiple fields.
+ let searchWords = getSearchTokens(gSearchInput.value);
+ searchURI += generateQueryURI(gQueryURIFormat, searchWords);
+
+ if (searchURI == kAllDirectoryRoot)
+ searchURI += "?";
+
+ document.getElementById("localResultsOnlyMessage")
+ .setAttribute("hidden",
+ !gDirectoryTreeView.hasRemoteAB ||
+ searchURI != kAllDirectoryRoot + "?");
+
+ SetAbView(searchURI);
+
+ // XXX todo
+ // this works for synchronous searches of local addressbooks,
+ // but not for LDAP searches
+ SelectFirstCard();
+}
+
+function SwitchPaneFocus(event)
+{
+ var focusedElement = WhichPaneHasFocus();
+ var cardViewBox = GetCardViewBox();
+ var cardViewBoxEmail1 = GetCardViewBoxEmail1();
+ var searchBox = GetSearchBox();
+ var dirTree = GetDirTree();
+
+ if (event && event.shiftKey)
+ {
+ if (focusedElement == gAbResultsTree && searchBox.getAttribute('hidden') != 'true')
+ searchInput.focus();
+ else if ((focusedElement == gAbResultsTree || focusedElement == searchBox) && !IsDirPaneCollapsed())
+ dirTree.focus();
+ else if (focusedElement != cardViewBox && !IsCardViewAndAbResultsPaneSplitterCollapsed())
+ {
+ if(cardViewBoxEmail1)
+ cardViewBoxEmail1.focus();
+ else
+ cardViewBox.focus();
+ }
+ else
+ gAbResultsTree.focus();
+ }
+ else
+ {
+ if (focusedElement == searchBox)
+ gAbResultsTree.focus();
+ else if (focusedElement == gAbResultsTree && !IsCardViewAndAbResultsPaneSplitterCollapsed())
+ {
+ if(cardViewBoxEmail1)
+ cardViewBoxEmail1.focus();
+ else
+ cardViewBox.focus();
+ }
+ else if (focusedElement != dirTree && !IsDirPaneCollapsed())
+ dirTree.focus();
+ else if (searchBox.getAttribute('hidden') != 'true')
+ gSearchInput.focus();
+ else
+ gAbResultsTree.focus();
+ }
+}
+
+function WhichPaneHasFocus()
+{
+ var cardViewBox = GetCardViewBox();
+ var searchBox = GetSearchBox();
+ var dirTree = GetDirTree();
+
+ var currentNode = top.document.commandDispatcher.focusedElement;
+ while (currentNode)
+ {
+ var nodeId = currentNode.getAttribute('id');
+
+ if(currentNode == gAbResultsTree ||
+ currentNode == cardViewBox ||
+ currentNode == searchBox ||
+ currentNode == dirTree)
+ return currentNode;
+
+ currentNode = currentNode.parentNode;
+ }
+
+ return null;
+}
+
+function GetDirTree()
+{
+ if (!gDirTree)
+ gDirTree = document.getElementById('dirTree');
+ return gDirTree;
+}
+
+function GetSearchBox()
+{
+ if (!gSearchBox)
+ gSearchBox = document.getElementById('searchBox');
+ return gSearchBox;
+}
+
+function GetCardViewBox()
+{
+ if (!gCardViewBox)
+ gCardViewBox = document.getElementById('CardViewBox');
+ return gCardViewBox;
+}
+
+function GetCardViewBoxEmail1()
+{
+ if (!gCardViewBoxEmail1)
+ {
+ try {
+ gCardViewBoxEmail1 = document.getElementById('cvEmail1');
+ }
+ catch (ex) {
+ gCardViewBoxEmail1 = null;
+ }
+ }
+ return gCardViewBoxEmail1;
+}
+
+function IsDirPaneCollapsed()
+{
+ var dirPaneBox = GetDirTree().parentNode;
+ return dirPaneBox.getAttribute("collapsed") == "true" ||
+ dirPaneBox.getAttribute("hidden") == "true";
+}
+
+function IsCardViewAndAbResultsPaneSplitterCollapsed()
+{
+ var cardViewBox = document.getElementById('CardViewOuterBox');
+ try {
+ return (cardViewBox.getAttribute("collapsed") == "true");
+ }
+ catch (ex) {
+ return false;
+ }
+}
+
+function LaunchUrl(url)
+{
+ // Doesn't matter if this bit fails, window.location contains its own prompts
+ try {
+ window.location = url;
+ }
+ catch (ex) {}
+}
+
+function getAbToolbox()
+{
+ return document.getElementById("ab-toolbox");
+}
+
+function AbToolboxCustomizeInit()
+{
+ toolboxCustomizeInit("ab-menubar");
+}
+
+function AbToolboxCustomizeDone(aToolboxChanged)
+{
+ toolboxCustomizeDone("ab-menubar", getAbToolbox(), aToolboxChanged);
+}
+
+function AbToolboxCustomizeChange(event)
+{
+ toolboxCustomizeChange(getAbToolbox(), event);
+}
diff --git a/comm/suite/mailnews/components/addrbook/content/addressbook.xul b/comm/suite/mailnews/components/addrbook/content/addressbook.xul
new file mode 100644
index 0000000000..698fbe62c5
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/addressbook.xul
@@ -0,0 +1,722 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/addressbook.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressbook/addressPanes.css"
+ type="text/css"?>
+
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/addressbook/abResultsPaneOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % abMainWindowDTD SYSTEM "chrome://messenger/locale/addressbook/abMainWindow.dtd" >
+%abMainWindowDTD;
+<!ENTITY % abResultsPaneOverlayDTD SYSTEM "chrome://messenger/locale/addressbook/abResultsPaneOverlay.dtd" >
+%abResultsPaneOverlayDTD;
+<!ENTITY % mailOverlayDTD SYSTEM "chrome://messenger/locale/mailOverlay.dtd">
+%mailOverlayDTD;
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="addressbookWindow"
+ height="450"
+ width="750"
+ title="&addressbookWindow.title;"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="mail:addressbook"
+ macanimationtype="document"
+ drawtitle="true"
+ persist="width height screenX screenY sizemode"
+ toggletoolbar="true"
+ onload="OnLoadAddressBook()"
+ onunload="OnUnloadAddressBook()">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+
+<script src="chrome://messenger/content/jsTreeView.js"/>
+<script src="chrome://messenger/content/addressbook/abTrees.js"/>
+<script src="chrome://messenger/content/accountUtils.js"/>
+<script src="chrome://messenger/content/addressbook/addressbook.js"/>
+<script src="chrome://messenger/content/addressbook/abCommon.js"/>
+<script src="chrome://communicator/content/contentAreaClick.js"/>
+<script src="chrome://global/content/printUtils.js"/>
+<script src="chrome://messenger/content/msgPrintEngine.js"/>
+<script src="chrome://messenger/content/addressbook/abCardViewOverlay.js"/>
+
+ <commandset id="addressBook">
+ <commandset id="CommandUpdate_AddressBook"
+ commandupdater="true"
+ events="focus,addrbook-select"
+ oncommandupdate="CommandUpdate_AddressBook()"/>
+ <commandset id="selectEditMenuItems"/>
+ <commandset id="undoEditMenuItems"/>
+ <commandset id="globalEditMenuItems"/>
+ <command id="cmd_newNavigator"/>
+ <command id="cmd_newPrivateWindow"/>
+ <command id="cmd_newEditor"/>
+ <command id="cmd_newlist" oncommand="AbNewList();"/>
+ <command id="cmd_newCard" oncommand="goDoCommand('cmd_newCard');"/>
+ <command id="cmd_newMessage" oncommand="AbNewMessage();"/>
+ <command id="cmd_printSetup" oncommand="PrintUtils.showPageSetup()"/>
+ <command id="cmd_print" oncommand="AbPrintCard();"/>
+ <command id="cmd_printpreview" oncommand="AbPrintPreviewCard();"/>
+ <command id="cmd_printcard" oncommand="AbPrintCard()"/>
+ <command id="cmd_printcardpreview" oncommand="AbPrintPreviewCard()"/>
+ <command id="cmd_printAddressBook" oncommand="AbPrintAddressBook()"/>
+ <command id="cmd_printPreviewAddressBook" oncommand="AbPrintPreviewAddressBook()"/>
+ <command id="cmd_close" oncommand="AbClose()"/>
+ <command id="cmd_properties" oncommand="goDoCommand('cmd_properties');"/>
+ <command id="cmd_undo"/>
+ <command id="cmd_redo"/>
+ <command id="cmd_copy"/>
+ <command id="cmd_paste"/>
+ <command id="cmd_cut"/>
+ <command id="cmd_delete"
+ valueAddressBook="&deleteAbCmd.label;"
+ valueCard="&deleteContactCmd.label;"
+ valueCards="&deleteContactsCmd.label;"
+ valueList="&deleteListCmd.label;"
+ valueLists="&deleteListsCmd.label;"
+ valueItems="&deleteItemsCmd.label;"/>
+ <command id="cmd_selectAll"/>
+ <command id="button_delete" oncommand="goDoCommand('button_delete');"/>
+ <command id="cmd_swapFirstNameLastName" oncommand="AbSwapFirstNameLastName()"/>
+ <commandset id="tasksCommands"/>
+ </commandset>
+
+<broadcasterset id="abBroadcasters">
+ <broadcaster id="Communicator:WorkMode"/>
+</broadcasterset>
+
+<broadcasterset id="mainBroadcasterSet"/>
+
+<keyset id="tasksKeys">
+ <!-- File Menu -->
+ <key id="key_newNavigator"/>
+ <key id="key_newPrivateWindow"/>
+ <key id="key_newBlankPage"/>
+#ifdef XP_MACOSX
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel,shift" command="cmd_newMessage"/>
+#else
+ <key id="key_newMessage" key="&newMessageCmd.key;"
+ modifiers="accel" command="cmd_newMessage"/>
+#endif
+ <key id="key_newCard" key="&newContact.key;" modifiers="accel"
+ command="cmd_newCard"/>
+ <key id="key_printCard" key="&printContactViewCmd.key;"
+ command="cmd_printcard" modifiers="accel"/>
+ <key id="key_close"/>
+ <!-- Edit Menu -->
+ <key id="key_delete"/>
+ <key id="key_delete2"/> <!-- secondary delete key -->
+ <key id="key_undo"/>
+ <key id="key_redo"/>
+ <key id="key_cut"/>
+ <key id="key_copy"/>
+ <key id="key_paste"/>
+ <key id="key_selectAll"/>
+ <key id="key_properties" key="&propertiesCmd.key;" command="cmd_properties" modifiers="accel"/>
+
+ <!-- View Menu -->
+#ifndef XP_MACOSX
+ <key id="key_toggleDirectoryPane"
+ keycode="VK_F9"
+ oncommand="togglePaneSplitter('dirTree-splitter');"/>
+#else
+ <key id="key_toggleDirectoryPane"
+ key="&toggleDirectoryPaneCmd.key;"
+ modifiers="accel,alt"
+ oncommand="togglePaneSplitter('dirTree-splitter');"/>
+#endif
+
+ <!-- Tab/F6 Keys -->
+ <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_TAB" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);" modifiers="shift"/>
+ <key keycode="VK_F6" oncommand="SwitchPaneFocus(event);"/>
+
+ <!-- Search field -->
+ <key key="&focusSearchInput.key;"
+ modifiers="accel"
+ oncommand="focusElement(document.getElementById('searchInput'));"/>
+
+</keyset>
+
+<menupopup id="dirTreeContext">
+ <menuitem id="dirTreeContext-properties"
+ label="&editItemButton.label;"
+ accesskey="&editItemButton.accesskey;"
+ command="cmd_properties"/>
+ <menuseparator/>
+ <menuitem id="dirTreeContext-newcard"
+ label="&newContactButton.label;"
+ accesskey="&newContactButton.accesskey;"
+ command="cmd_newCard"/>
+ <menuitem id="dirTreeContext-newlist"
+ label="&newlistButton.label;"
+ accesskey="&newlistButton.accesskey;"
+ command="cmd_newlist"/>
+ <menuseparator/>
+ <menuitem id="dirTreeContext-print"
+ label="&printButton.label;"
+ accesskey="&printButton.accesskey;"
+ command="cmd_printAddressBook"/>
+ <menuitem id="dirTreeContext-delete"
+ label="&deleteItemButton.label;"
+ accesskey="&deleteItemButton.accesskey;"
+ command="button_delete"/>
+</menupopup>
+
+<menupopup id="abResultsTreeContext">
+ <menuitem id="abResultsTreeContext-properties"
+ label="&editItemButton.label;"
+ accesskey="&editItemButton.accesskey;"
+ command="cmd_properties"/>
+ <menuseparator/>
+ <menuitem id="abResultsTreeContext-newmessage"
+ label="&newmsgButton.label;"
+ accesskey="&newmsgButton.accesskey;"
+ command="cmd_newMessage"/>
+ <menuseparator/>
+ <menuitem id="abResultsTreeContext-print"
+ label="&printButton.label;"
+ accesskey="&printButton.accesskey;"
+ command="cmd_printcard"/>
+ <menuitem id="abResultsTreeContext-delete"
+ label="&deleteItemButton.label;"
+ accesskey="&deleteItemButton.accesskey;"
+ command="button_delete"/>
+</menupopup>
+
+<menupopup id="toolbar-context-menu"/>
+
+<vbox id="titlebar"/>
+
+<toolbox id="ab-toolbox"
+ mode="full"
+ defaultmode="full"
+ class="toolbox-top">
+ <toolbar type="menubar"
+ id="addrbook-toolbar-menubar2"
+ class="chromeclass-menubar"
+ persist="collapsed"
+ grippytooltiptext="&menuBar.tooltip;"
+ customizable="true"
+ defaultset="menubar-items"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items"
+ class="menubar-items"
+ align="center">
+ <menubar id="ab-menubar">
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup">
+ <menu id="menu_New">
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newContact"
+ label="&newContact.label;"
+ accesskey="&newContact.accesskey;"
+ key="key_newCard"
+ command="cmd_newCard"/>
+ <menuitem id="menu_newList"
+ label="&newListCmd.label;"
+ accesskey="&newListCmd.accesskey;"
+ command="cmd_newlist"/>
+ <menuitem id="menu_newAddrbook"
+ label="&newAddressBookCmd.label;"
+ accesskey="&newAddressBookCmd.accesskey;"
+ oncommand="AbNewAddressBook();"/>
+ <menuitem id="addLDAP"
+ label="&newLDAPDirectoryCmd.label;"
+ accesskey="&newLDAPDirectoryCmd.accesskey;"
+ oncommand="AbNewLDAPDirectory();"/>
+ <menuseparator/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ <menuitem id="menu_newMessage"
+ label="&newMessageCmd.label;"
+ accesskey="&newMessageCmd.accesskey;"
+ key="key_newMessage"
+ command="cmd_newMessage"/>
+ <menuitem id="menu_newEditor"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_close"/>
+ <menuseparator/>
+#ifndef XP_MACOSX
+ <menuitem id="printPreviewMenuItem"
+ label="&printPreviewContactViewCmd.label;"
+ accesskey="&printPreviewContactViewCmd.accesskey;"
+ command="cmd_printcardpreview"/>
+#endif
+ <menuitem id="printMenuItem" label="&printContactViewCmd.label;"
+ accesskey="&printContactViewCmd.accesskey;"
+ key="key_printCard"
+ command="cmd_printcard"/>
+ <menuseparator id="menu_PageSetupSeparator"/>
+ <menuitem id="menu_printSetup"/>
+ <menuseparator id="menu_PrintAddrbookSeparator"/>
+#ifndef XP_MACOSX
+ <menuitem id="printPreviewAddressBook"
+ label="&printPreviewAddressBook.label;"
+ accesskey="&printPreviewAddressBook.accesskey;"
+ command="cmd_printPreviewAddressBook"/>
+#endif
+ <menuitem id="printAddressBook"
+ label="&printAddressBook.label;"
+ accesskey="&printAddressBook.accesskey;"
+ command="cmd_printAddressBook"/>
+ </menupopup>
+ </menu>
+
+ <menu id="menu_Edit">
+ <menupopup id="menu_EditPopup">
+ <menuitem id="menu_undo"/>
+ <menuitem id="menu_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"/>
+ <menuitem id="menu_copy"/>
+ <menuitem id="menu_paste"/>
+ <menuitem id="menu_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"/>
+ <menuseparator/>
+ <!-- LOCALIZATION NOTE: set "hideSwapFnLnUI" to false in .dtd to enable the UI -->
+ <menuitem label="&swapFirstNameLastNameCmd.label;"
+ accesskey="&swapFirstNameLastNameCmd.accesskey;"
+ hidden="&hideSwapFnLnUI;"
+ command="cmd_swapFirstNameLastName"/>
+ <menuitem label="&propertiesCmd.label;"
+ accesskey="&propertiesCmd.accesskey;"
+ key="key_properties"
+ command="cmd_properties"/>
+ <menuitem id="menu_preferences"
+ oncommand="goPreferences('addressing_pane')"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_View">
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event);"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Layout"
+ label="&layoutMenu.label;"
+ accesskey="&layoutMenu.accesskey;">
+ <menupopup id="view_layout_popup"
+ onpopupshowing="InitViewLayoutMenuPopup(event);">
+ <menuitem id="menu_showDirectoryPane"
+ label="&showDirectoryPane.label;"
+ key="key_toggleDirectoryPane"
+ accesskey="&showDirectoryPane.accesskey;"
+ oncommand="togglePaneSplitter('dirTree-splitter');"
+ checked="true"
+ type="checkbox"/>
+ <menuitem id="menu_showCardPane"
+ label="&showContactPane2.label;"
+ accesskey="&showContactPane2.accesskey;"
+ oncommand="togglePaneSplitter('results-splitter');"
+ checked="true"
+ type="checkbox"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewMenuAfterLayoutSeparator"/>
+ <menu id="menu_ShowNameAs" label="&menu_ShowNameAs.label;" accesskey="&menu_ShowNameAs.accesskey;">
+ <menupopup id="menuShowNameAsPopup">
+ <menuitem type="radio" name="shownameas"
+ id="firstLastCmd"
+ label="&firstLastCmd.label;"
+ accesskey="&firstLastCmd.accesskey;"
+ oncommand="SetNameColumn('firstLastCmd')"/>
+ <menuitem type="radio" name="shownameas"
+ id="lastFirstCmd"
+ label="&lastFirstCmd.label;"
+ accesskey="&lastFirstCmd.accesskey;"
+ oncommand="SetNameColumn('lastFirstCmd')"/>
+ <menuitem type="radio" name="shownameas"
+ id="displayNameCmd"
+ label="&displayNameCmd.label;"
+ accesskey="&displayNameCmd.accesskey;"
+ oncommand="SetNameColumn('displayNameCmd')"/>
+ </menupopup>
+ </menu>
+ <menu id="sortMenu" label="&sortMenu.label;" accesskey="&sortMenu.accesskey;">
+ <menupopup id="sortMenuPopup" onpopupshowing="InitViewSortByMenu()">
+ <menuitem label="&GeneratedName.label;"
+ id="cmd_SortByGeneratedName"
+ accesskey="&GeneratedName.accesskey;"
+ oncommand="SortResultPane('GeneratedName');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&PrimaryEmail.label;"
+ id="cmd_SortByPrimaryEmail"
+ accesskey="&PrimaryEmail.accesskey;"
+ oncommand="SortResultPane('PrimaryEmail');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&ChatName.label;"
+ id="cmd_SortByChatName"
+ accesskey="&ChatName.accesskey;"
+ oncommand="SortResultPane('ChatName');"
+ name="sortas"
+ type="radio"
+ checked="true"/>
+ <menuitem label="&Company.label;"
+ id="cmd_SortByCompany"
+ accesskey="&Company.accesskey;"
+ oncommand="SortResultPane('Company');" name="sortas" type="radio" checked="true"/>
+ <!-- LOCALIZATION NOTE:
+ Fields for phonetic are disabled as default and can be enabled by
+ editing "mail.addr_book.show_phonetic_fields"
+ -->
+ <menuitem label="&_PhoneticName.label;"
+ id="cmd_SortBy_PhoneticName"
+ hidden="true"
+ accesskey="&_PhoneticName.accesskey;"
+ oncommand="SortResultPane('_PhoneticName');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&NickName.label;"
+ id="cmd_SortByNickName"
+ accesskey="&NickName.accesskey;"
+ oncommand="SortResultPane('NickName');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&SecondEmail.label;"
+ id="cmd_SortBySecondEmail"
+ accesskey="&SecondEmail.accesskey;"
+ oncommand="SortResultPane('SecondEmail');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&Department.label;"
+ id="cmd_SortByDepartment"
+ accesskey="&Department.accesskey;"
+ oncommand="SortResultPane('Department');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&JobTitle.label;"
+ id="cmd_SortByJobTitle"
+ accesskey="&JobTitle.accesskey;"
+ oncommand="SortResultPane('JobTitle');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&CellularNumber.label;"
+ id="cmd_SortByCellularNumber"
+ accesskey="&CellularNumber.accesskey;"
+ oncommand="SortResultPane('CellularNumber');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&PagerNumber.label;"
+ id="cmd_SortByPagerNumber"
+ accesskey="&PagerNumber.accesskey;"
+ oncommand="SortResultPane('PagerNumber');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&FaxNumber.label;"
+ id="cmd_SortByFaxNumber"
+ accesskey="&FaxNumber.accesskey;"
+ oncommand="SortResultPane('FaxNumber');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&HomePhone.label;"
+ id="cmd_SortByHomePhone"
+ accesskey="&HomePhone.accesskey;"
+ oncommand="SortResultPane('HomePhone');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&WorkPhone.label;"
+ id="cmd_SortByWorkPhone"
+ accesskey="&WorkPhone.accesskey;"
+ oncommand="SortResultPane('WorkPhone');" name="sortas" type="radio" checked="true"/>
+ <menuitem label="&Addrbook.label;"
+ id="cmd_SortByaddrbook"
+ accesskey="&Addrbook.accesskey;"
+ oncommand="SortResultPane('addrbook');"
+ name="sortas"
+ type="radio"
+ checked="true"/>
+ <menuseparator/>
+ <menuitem id="sortAscending" type="radio" name="sortdirection" label="&sortAscending.label;" accesskey="&sortAscending.accesskey;" oncommand="AbSortAscending()"/>
+ <menuitem id="sortDescending" type="radio" name="sortdirection" label="&sortDescending.label;" accesskey="&sortDescending.accesskey;" oncommand="AbSortDescending()"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+ </menu>
+ <menu id="tasksMenu">
+ <menupopup id="taskPopup">
+ <menuitem label="&searchAddressesCmd.label;"
+ accesskey="&searchAddressesCmd.accesskey;"
+ id="menu_search_addresses"
+ oncommand="onAdvancedAbSearch();"/>
+ <menuitem label="&importCmd.label;" accesskey="&importCmd.accesskey;" oncommand="toImport()"/>
+ <menuitem label="&exportCmd.label;"
+ accesskey="&exportCmd.accesskey;"
+ oncommand="AbExportSelection();"/>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+
+ <menu id="windowMenu"/>
+ <menu id="menu_Help"/>
+ <spacer flex="1"/>
+ </menubar>
+ </toolbaritem>
+ </toolbar>
+ <toolbar class="chromeclass-toolbar toolbar-primary"
+ id="ab-bar2"
+ persist="collapsed"
+ customizable="true"
+ grippytooltiptext="&addressbookToolbar.tooltip;"
+ toolbarname="&showAbToolbarCmd.label;"
+ accesskey="&showAbToolbarCmd.accesskey;"
+ defaultset="button-newcard,button-newlist,separator,button-editcard,button-newmessage,button-abdelete,spring,searchBox,throbber-box"
+ context="toolbar-context-menu">
+ <toolbarbutton id="button-newcard"
+ class="toolbarbutton-1"
+ label="&newContactButton.label;"
+ tooltiptext="&newContactButton.tooltip;"
+ removable="true"
+ command="cmd_newCard"/>
+ <toolbarbutton id="button-newlist"
+ class="toolbarbutton-1"
+ label="&newlistButton.label;"
+ tooltiptext="&newlistButton.tooltip;"
+ removable="true"
+ command="cmd_newlist"/>
+ <toolbarbutton id="button-editcard"
+ class="toolbarbutton-1"
+ label="&editItemButton.label;"
+ tooltiptext="&editItemButton.tooltip;"
+ removable="true"
+ command="cmd_properties"/>
+ <toolbarbutton id="button-newmessage"
+ class="toolbarbutton-1"
+ label="&newmsgButton.label;"
+ tooltiptext="&newmsgButton.tooltip;"
+ removable="true"
+ oncommand="AbNewMessage();"/>
+ <toolbarbutton id="button-abdelete"
+ class="toolbarbutton-1"
+ label="&deleteItemButton.label;"
+ tooltiptext="&deleteItemButton.tooltip;"
+ removable="true"
+ oncommand="goDoCommand('button_delete');"/>
+ <toolbarbutton id="print-button"
+ label="&printButton.label;"
+ tooltiptext="&printButton.tooltip;"
+ removable="true"/>
+ <toolbaritem id="searchBox"
+ flex="1"
+ title="&searchBox.title;"
+ removable="true"
+ align="center"
+ class="toolbaritem-noline chromeclass-toolbar-additional">
+ <textbox id="searchInput"
+ flex="1"
+ type="search"
+ aria-controls="abResultsTree"
+ placeholder="&searchNameAndEmail.placeholder;"
+ clickSelectsAll="true"
+ oncommand="onEnterInSearchBar();"
+ onkeypress="if (event.keyCode == KeyEvent.DOM_VK_RETURN) this.select();"/>
+ </toolbaritem>
+
+ <toolbaritem id="throbber-box"/>
+ </toolbar>
+ <toolbarpalette id="AbToolbarPalette"/>
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+</toolbox>
+
+ <!-- The main address book three pane -->
+ <hbox flex="1">
+ <vbox id="dirTreeBox" persist="width collapsed">
+ <tree id="dirTree"
+ class="abDirectory plain"
+ seltype="single"
+ style="min-width: 150px;"
+ flex="1"
+ persist="width"
+ hidecolumnpicker="true"
+ context="dirTreeContext"
+ onselect="DirPaneSelectionChange(); document.commandDispatcher.updateCommands('addrbook-select');"
+ ondblclick="DirPaneDoubleClick(event);"
+ onclick="DirPaneClick(event);"
+ onblur="goOnEvent(this, 'blur');">
+ <treecols>
+ <treecol id="DirCol"
+ flex="1"
+ primary="true"
+ label="&dirTreeHeader.label;"
+ crop="center"
+ persist="width"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+
+ <splitter id="dirTree-splitter" collapse="before" persist="state">
+ <grippy/>
+ </splitter>
+
+ <vbox flex="1" style="min-width:100px">
+ <description id="localResultsOnlyMessage"
+ value="&localResultsOnlyMessage.label;"/>
+ <vbox id="blankResultsPaneMessageBox"
+ flex="1"
+ pack="center"
+ align="center">
+ <description id="blankResultsPaneMessage"
+ value="&blankResultsPaneMessage.label;"/>
+ </vbox>
+ <!-- results pane -->
+ <tree id="abResultsTree" context="abResultsTreeContext" flex="1" />
+
+ <splitter id="results-splitter" collapse="after" persist="state">
+ <grippy/>
+ </splitter>
+
+ <!-- card view -->
+ <hbox id="CardViewOuterBox" flex="1" persist="height">
+ <vbox id="CardViewBox"
+ flex="1"
+ style="height:170px; min-height:1px; min-width:1px">
+ <vbox id="CardViewInnerBox" collapsed="true" flex="1">
+ <description id="CardTitle"/>
+ <hbox style="width:100%" flex="1">
+ <vbox id="cvbPhoto"
+ class="cardViewGroup"
+ align="center"
+ style="min-width: 10ch; max-width: 10ch;">
+ <image id="cvPhoto" style="max-width: 10ch; max-height: 10ch;"/>
+ </vbox>
+ <hbox flex="1" equalsize="always">
+ <vbox flex="1" class="cardViewColumn">
+ <vbox id="cvbContact" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhContact">&contact.heading;</description>
+ <description class="CardViewLink" id="cvListNameBox">
+ <html:p><html:a href="" id="cvListName"/></html:p>
+ </description>
+ <description class="CardViewText" id="cvDisplayName"/>
+ <description class="CardViewText" id="cvNickname"/>
+ <description class="CardViewLink" id="cvEmail1Box">
+ <html:a href="" id="cvEmail1"/>
+ </description>
+ <description class="CardViewLink" id="cvEmail2Box">
+ <html:a href="" id="cvEmail2"/>
+ </description>
+ </vbox>
+ <vbox id="cvbHome" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhHome">&home.heading;</description>
+ <hbox>
+ <vbox flex="1">
+ <description class="CardViewText" id="cvHomeAddress"/>
+ <description class="CardViewText" id="cvHomeAddress2"/>
+ <description class="CardViewText" id="cvHomeCityStZip"/>
+ <description class="CardViewText" id="cvHomeCountry"/>
+ </vbox>
+ <vbox id="cvbHomeMapItBox" pack="end">
+ <button id="cvHomeMapIt"
+ label="&mapItButton.label;"
+ type="menu-button"
+ oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+ tooltiptext="&mapIt.tooltip;">
+ <menupopup class="map-list"/>
+ </button>
+ </vbox>
+ </hbox>
+ <description class="CardViewLink" id="cvHomeWebPageBox">
+ <html:a onclick="return openLink(event);"
+ href=""
+ id="cvHomeWebPage"/>
+ </description>
+ </vbox>
+ <vbox id="cvbOther" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhOther">&other.heading;</description>
+ <description class="CardViewText" id="cvBirthday"/>
+ <description class="CardViewText" id="cvCustom1"/>
+ <description class="CardViewText" id="cvCustom2"/>
+ <description class="CardViewText" id="cvCustom3"/>
+ <description class="CardViewText" id="cvCustom4"/>
+ <description class="CardViewText" id="cvNotes"
+ style="white-space: pre-wrap;"/>
+ <hbox>
+ <image id="cvBuddyIcon"/>
+ </hbox>
+ </vbox>
+ <vbox id="cvbChat" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhChat">&chat.heading;</description>
+ <description class="CardViewText" id="cvYahoo"/>
+ <description class="CardViewText" id="cvSkype"/>
+ <description class="CardViewText" id="cvQQ"/>
+ <description class="CardViewText" id="cvMSN"/>
+ <description class="CardViewText" id="cvICQ"/>
+ <description class="CardViewText" id="cvXMPP"/>
+ <description class="CardViewText" id="cvIRC"/>
+ </vbox>
+ <!-- the description and addresses groups are only for
+ mailing lists -->
+ <vbox id="cvbDescription" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhDescription">&description.heading;</description>
+ <description class="CardViewText" id="cvDescription"/>
+ </vbox>
+ <vbox id="cvbAddresses" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhAddresses">&addresses.heading;</description>
+ <vbox id="cvAddresses"/>
+ </vbox>
+ </vbox>
+
+ <vbox flex="1" class="cardViewColumn">
+ <vbox id="cvbPhone" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhPhone">&phone.heading;</description>
+ <description class="CardViewText" id="cvPhWork"/>
+ <description class="CardViewText" id="cvPhHome"/>
+ <description class="CardViewText" id="cvPhFax"/>
+ <description class="CardViewText" id="cvPhCellular"/>
+ <description class="CardViewText" id="cvPhPager"/>
+ </vbox>
+ <vbox id="cvbWork" class="cardViewGroup">
+ <description class="CardViewHeading" id="cvhWork">&work.heading;</description>
+ <description class="CardViewText" id="cvJobTitle"/>
+ <description class="CardViewText" id="cvDepartment"/>
+ <description class="CardViewText" id="cvCompany"/>
+ <hbox>
+ <vbox flex="1">
+ <description class="CardViewText" id="cvWorkAddress"/>
+ <description class="CardViewText" id="cvWorkAddress2"/>
+ <description class="CardViewText" id="cvWorkCityStZip"/>
+ <description class="CardViewText" id="cvWorkCountry"/>
+ </vbox>
+ <vbox id="cvbWorkMapItBox" pack="end">
+ <button id="cvWorkMapIt"
+ label="&mapItButton.label;"
+ type="menu-button"
+ oncommand="openLinkWithUrl(this.firstChild.mapURL);"
+ tooltiptext="&mapIt.tooltip;">
+ <menupopup class="map-list"/>
+ </button>
+ </vbox>
+ </hbox>
+ <description class="CardViewLink" id="cvWorkWebPageBox">
+ <html:a onclick="return openLink(event);"
+ href=""
+ id="cvWorkWebPage"/>
+ </description>
+ </vbox>
+ </vbox>
+ </hbox>
+ </hbox>
+ </vbox>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <panel id="customizeToolbarSheetPopup"/>
+ <statusbar id="status-bar" class="chromeclass-status">
+ <statusbarpanel id="component-bar"/>
+ <statusbarpanel id="statusText" flex="1" value="&statusText.label;"/>
+ <statusbarpanel id="offline-status" class="statusbarpanel-iconic"/>
+ </statusbar>
+</window>
diff --git a/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.js b/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.js
new file mode 100644
index 0000000000..bfc741c97f
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.js
@@ -0,0 +1,24 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function Startup()
+{
+ enableAutocomplete();
+}
+
+function onEditDirectories()
+{
+ window.openDialog("chrome://messenger/content/addressbook/pref-editdirectories.xul",
+ "editDirectories", "chrome,modal=yes,resizable=no", null);
+}
+
+function enableAutocomplete()
+{
+ var acLDAPValue = document.getElementById("ldap_2.autoComplete.useDirectory")
+ .value;
+
+ EnableElementById("directoriesList", acLDAPValue, false);
+ EnableElementById("editButton", acLDAPValue, false);
+}
diff --git a/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.xul b/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.xul
new file mode 100644
index 0000000000..af4bed750b
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.xul
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/addressbook/pref-addressing.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="addressing_pane" label="&pref.addressing.title;"
+ script="chrome://messenger/content/addressbook/pref-addressing.js">
+
+ <stringbundle id="bundle_addressBook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+
+ <preferences id="addressing_preferences">
+ <preference id="mail.collect_email_address_outgoing"
+ name="mail.collect_email_address_outgoing"
+ type="bool"/>
+ <preference id="mail.collect_addressbook"
+ name="mail.collect_addressbook"
+ type="string"/>
+ <preference id="mail.autoComplete.highlightNonMatches"
+ name="mail.autoComplete.highlightNonMatches"
+ type="bool"/>
+ <preference id="mail.enable_autocomplete"
+ name="mail.enable_autocomplete"
+ type="bool"/>
+ <preference id="ldap_2.autoComplete.useDirectory"
+ name="ldap_2.autoComplete.useDirectory"
+ onchange="enableAutocomplete();" type="bool"/>
+ <preference id="ldap_2.autoComplete.directoryServer"
+ name="ldap_2.autoComplete.directoryServer"
+ type="string"/>
+ <preference id="pref.ldap.disable_button.edit_directories"
+ name="pref.ldap.disable_button.edit_directories"
+ type="bool"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&emailCollectiontitle.label;"/>
+ <description>&emailCollectiontext.label;</description>
+ <hbox align="center">
+ <checkbox id="emailCollectionOutgoing"
+ label="&emailCollectionPicker.label;"
+ accesskey="&emailCollectionPicker.accesskey;"
+ preference="mail.collect_email_address_outgoing"/>
+ <menulist id="localDirectoriesList" flex="1"
+ aria-labelledby="emailCollectionOutgoing"
+ preference="mail.collect_addressbook">
+ <menupopup id="localDirectoriesPopup" class="addrbooksPopup"
+ localonly="true" writable="true"/>
+ </menulist>
+ </hbox>
+ </groupbox>
+ <groupbox id="addressAutocompletion">
+ <caption label="&addressingTitle.label;"/>
+ <hbox align="center">
+ <checkbox id="highlightNonMatches" label="&highlightNonMatches.label;"
+ preference="mail.autoComplete.highlightNonMatches"
+ accesskey="&highlightNonMatches.accesskey;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <description>&autocompleteText.label;</description>
+ <hbox align="center">
+ <checkbox id="addressingAutocomplete" label="&addressingEnable.label;"
+ preference="mail.enable_autocomplete"
+ accesskey="&addressingEnable.accesskey;"/>
+ </hbox>
+ <hbox align="center">
+ <checkbox id="autocompleteLDAP" label="&directories.label;"
+ preference="ldap_2.autoComplete.useDirectory"
+ accesskey="&directories.accesskey;"/>
+ <menulist id="directoriesList" flex="1"
+ aria-labelledby="autocompleteLDAP"
+ preference="ldap_2.autoComplete.directoryServer">
+ <menupopup id="directoriesListPopup" class="addrbooksPopup"
+ none="&directoriesNone.label;"
+ remoteonly="true" value="dirPrefId"/>
+ </menulist>
+ <button id="editButton" label="&editDirectories.label;"
+ oncommand="onEditDirectories();"
+ accesskey="&editDirectories.accesskey;"
+ preference="pref.ldap.disable_button.edit_directories"/>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/addrbook/jar.mn b/comm/suite/mailnews/components/addrbook/jar.mn
new file mode 100644
index 0000000000..cc2cec4e13
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/jar.mn
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/addressbook/addressbook.js (content/addressbook.js)
+* content/messenger/addressbook/addressbook.xul (content/addressbook.xul)
+ content/messenger/addressbook/abCommon.js (content/abCommon.js)
+ content/messenger/addressbook/abCardOverlay.js (content/abCardOverlay.js)
+ content/messenger/addressbook/abCardOverlay.xul (content/abCardOverlay.xul)
+ content/messenger/addressbook/abCardViewOverlay.js (content/abCardViewOverlay.js)
+ content/messenger/addressbook/abEditCardDialog.xul (content/abEditCardDialog.xul)
+ content/messenger/addressbook/abNewCardDialog.xul (content/abNewCardDialog.xul)
+ content/messenger/addressbook/abResultsPaneOverlay.xul (content/abResultsPaneOverlay.xul)
+ content/messenger/addressbook/abMailListDialog.xul (content/abMailListDialog.xul)
+ content/messenger/addressbook/abEditListDialog.xul (content/abEditListDialog.xul)
+ content/messenger/addressbook/abListOverlay.xul (content/abListOverlay.xul)
+ content/messenger/addressbook/abSelectAddressesDialog.js (content/abSelectAddressesDialog.js)
+ content/messenger/addressbook/abSelectAddressesDialog.xul (content/abSelectAddressesDialog.xul)
+ content/messenger/addressbook/abTrees.js (content/abTrees.js)
+ content/messenger/addressbook/addressbook-panel.xul (content/addressbook-panel.xul)
+ content/messenger/addressbook/addressbook-panel.js (content/addressbook-panel.js)
+ content/messenger/addressbook/pref-addressing.js (content/prefs/pref-addressing.js)
+ content/messenger/addressbook/pref-addressing.xul (content/prefs/pref-addressing.xul)
diff --git a/comm/suite/mailnews/components/addrbook/moz.build b/comm/suite/mailnews/components/addrbook/moz.build
new file mode 100644
index 0000000000..77788c7f3e
--- /dev/null
+++ b/comm/suite/mailnews/components/addrbook/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
+
+DEFINES["TOOLKIT_DIR"] = "%s/toolkit" % (CONFIG["topsrcdir"],)
diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-addons.xhtml b/comm/suite/mailnews/components/calendar/content/suite-overlay-addons.xhtml
new file mode 100644
index 0000000000..c1ccec4917
--- /dev/null
+++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-addons.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="suiteAddonsOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script><![CDATA[
+ var lightningPrefs = {
+ guid: "{e2fda1a4-762b-4020-b5ad-a41df1933103}",
+ handleEvent: function(aEvent) {
+ var item = gListView.getListItemForID(this.guid);
+ if (!item)
+ return;
+
+ item.showPreferences = this.showPreferences;
+ },
+ showPreferences: function() {
+ var win = Services.wm.getMostRecentWindow("mozilla:preferences");
+ if (win) {
+ win.focus();
+ var doc = win.document;
+ var pane = doc.getElementById("paneLightning");
+ doc.querySelector("dialog").syncTreeWithPane(pane, true);
+ } else {
+ openDialog("chrome://communicator/content/pref/preferences.xhtml",
+ "PrefWindow",
+ "non-private,chrome,titlebar,dialog=no,resizable",
+ "paneLightning");
+ }
+ },
+ };
+
+ window.addEventListener("ViewChanged", lightningPrefs, false);
+ ]]></script>
+
+</overlay>
diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml b/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml
new file mode 100644
index 0000000000..0e7aad33df
--- /dev/null
+++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml
@@ -0,0 +1,66 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://lightning/skin/lightning.css"?>
+
+<?xul-overlay href="chrome://calendar/content/preferences/general.xhtml"?>
+<?xul-overlay href="chrome://calendar/content/preferences/alarms.xhtml"?>
+<?xul-overlay href="chrome://calendar/content/preferences/categories.xhtml"?>
+<?xul-overlay href="chrome://calendar/content/preferences/views.xhtml"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % lightningDTD SYSTEM "chrome://lightning/locale/lightning.dtd">
+ %lightningDTD;
+ <!ENTITY % preferencesDTD SYSTEM "chrome://calendar/locale/preferences/preferences.dtd">
+ %preferencesDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <treechildren id="prefsPanelChildren">
+ <treeitem container="true"
+ id="lightningItem"
+ insertafter="mailnewsItem,navigatorItem"
+ label="&lightning.preferencesLabel;"
+ prefpane="paneLightning">
+ <treechildren id="lightningChildren">
+ <treeitem id="lightningAlarms"
+ label="&paneAlarms.title;"
+ prefpane="paneLightningAlarms"/>
+ <treeitem id="lightningCategories"
+ label="&paneCategories.title;"
+ prefpane="paneLightningCategories"/>
+ <treeitem id="lightningViews"
+ label="&paneViews.title;"
+ prefpane="paneLightningViews"/>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+
+ <prefwindow id="prefDialog">
+ <prefpane id="paneLightning"
+ label="&lightning.preferencesLabel;"
+ onpaneload="gCalendarGeneralPane.init();">
+ <vbox id="calPreferencesBoxGeneral"/>
+ </prefpane>
+ <prefpane id="paneLightningAlarms"
+ label="&paneAlarms.title;"
+ onpaneload="gAlarmsPane.init();">
+ <vbox id="calPreferencesBoxAlarms"/>
+ </prefpane>
+ <prefpane id="paneLightningCategories"
+ label="&paneCategories.title;"
+ onpaneload="gCategoriesPane.init();">
+ <vbox id="calPreferencesBoxCategories"/>
+ </prefpane>
+ <prefpane id="paneLightningViews"
+ label="&paneViews.title;"
+ onpaneload="gViewsPane.init();">
+ <vbox id="calPreferencesBoxViews"/>
+ </prefpane>
+ </prefwindow>
+
+</overlay>
diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js
new file mode 100644
index 0000000000..6c27c9c385
--- /dev/null
+++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js
@@ -0,0 +1,47 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../suite/base/content/utilityOverlay.js */
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var ltnSuiteUtils = {
+ addStartupObserver: function() {
+ Services.obs.addObserver(this.startupObserver, "lightning-startup-done");
+ Services.obs.addObserver(this.startupObserver, "calendar-taskview-startup-done");
+ },
+
+ startupObserver: {
+ observe: function(subject, topic, state) {
+ if (topic != "lightning-startup-done" && topic != "calendar-taskview-startup-done") {
+ return;
+ }
+
+ const ids = [
+ ["CustomizeTaskActionsToolbar", "task-actions-toolbox"],
+ ["CustomizeCalendarToolbar", "calendar-toolbox"],
+ ["CustomizeTaskToolbar", "task-toolbox"],
+ ];
+
+ ids.forEach(([itemID, toolboxID]) => {
+ let item = document.getElementById(itemID);
+ let toolbox = document.getElementById(toolboxID);
+ toolbox.customizeInit = function() {
+ item.setAttribute("disabled", "true");
+ toolboxCustomizeInit("mail-menubar");
+ };
+ toolbox.customizeDone = function(aToolboxChanged) {
+ item.removeAttribute("disabled");
+ toolboxCustomizeDone("mail-menubar", toolbox, aToolboxChanged);
+ };
+ toolbox.customizeChange = function(aEvent) {
+ toolboxCustomizeChange(toolbox, aEvent);
+ };
+ });
+ },
+ },
+};
+
+ltnSuiteUtils.addStartupObserver();
diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml
new file mode 100644
index 0000000000..79ae5d662a
--- /dev/null
+++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ - You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="suiteSidebarOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <script src="chrome://lightning/content/suite-overlay-sidebar.js"/>
+
+ <key id="openLightningKey" removeelement="true"/>
+ <key id="openTasksKey" removeelement="true"/>
+ <key id="calendar-new-event-key" removeelement="true"/>
+ <key id="calendar-new-todo-key" removeelement="true"/>
+
+ <menuitem id="CustomizeTaskActionsToolbar"
+ oncommand="goCustomizeToolbar(document.getElementById('task-actions-toolbox'))"/>
+
+ <toolbox id="calendar-toolbox"
+ defaultlabelalign="end"
+ xpfe="false"/>
+ <toolbox id="task-toolbox"
+ defaultlabelalign="end"
+ xpfe="false"/>
+ <toolbox id="task-actions-toolbox"
+ defaultlabelalign="end"
+ xpfe="false"/>
+
+ <toolbar id="calendar-toolbar2"
+ defaultlabelalign="end"
+ context="toolbar-context-menu"/>
+ <toolbar id="task-toolbar2"
+ defaultlabelalign="end"
+ context="toolbar-context-menu"/>
+ <toolbar id="task-actions-toolbar"
+ context="toolbar-context-menu"/>
+
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js b/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js
new file mode 100644
index 0000000000..48f7f31b1a
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/MsgComposeCommands.js
@@ -0,0 +1,3936 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
+const {FolderUtils} = ChromeUtils.import("resource:///modules/FolderUtils.jsm");
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.js");
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+
+/**
+ * interfaces
+ */
+var nsIMsgCompDeliverMode = Ci.nsIMsgCompDeliverMode;
+var nsIMsgCompSendFormat = Ci.nsIMsgCompSendFormat;
+var nsIMsgCompConvertible = Ci.nsIMsgCompConvertible;
+var nsIMsgCompType = Ci.nsIMsgCompType;
+var nsIMsgCompFormat = Ci.nsIMsgCompFormat;
+var nsIAbPreferMailFormat = Ci.nsIAbPreferMailFormat;
+var mozISpellCheckingEngine = Ci.mozISpellCheckingEngine;
+
+/**
+ * In order to distinguish clearly globals that are initialized once when js load (static globals) and those that need to be
+ * initialize every time a compose window open (globals), I (ducarroz) have decided to prefix by s... the static one and
+ * by g... the other one. Please try to continue and repect this rule in the future. Thanks.
+ */
+/**
+ * static globals, need to be initialized only once
+ */
+var sComposeMsgsBundle;
+var sBrandBundle;
+
+var sRDF = null;
+var sNameProperty = null;
+var sDictCount = 0;
+
+/**
+ * Global message window object. This is used by mail-offline.js and therefore
+ * should not be renamed. We need to avoid doing this kind of cross file global
+ * stuff in the future and instead pass this object as parameter when needed by
+ * functions in the other js file.
+ */
+var msgWindow;
+
+var gMessenger;
+
+/**
+ * Global variables, need to be re-initialized every time mostly because
+ * we need to release them when the window closes.
+ */
+var gHideMenus;
+var gMsgCompose;
+var gOriginalMsgURI;
+var gWindowLocked;
+var gSendLocked;
+var gContentChanged;
+var gAutoSaving;
+var gCurrentIdentity;
+var defaultSaveOperation;
+var gSendOrSaveOperationInProgress;
+var gCloseWindowAfterSave;
+var gSavedSendNowKey;
+var gSendFormat;
+var gLogComposePerformance;
+
+var gMsgIdentityElement;
+var gMsgAddressingWidgetElement;
+var gMsgSubjectElement;
+var gMsgAttachmentElement;
+var gMsgHeadersToolbarElement;
+var gComposeType;
+var gFormatToolbarHidden = false;
+var gBodyFromArgs;
+
+// i18n globals
+var gCharsetConvertManager;
+
+var gLastWindowToHaveFocus;
+var gReceiptOptionChanged;
+var gDSNOptionChanged;
+var gAttachVCardOptionChanged;
+
+var gAutoSaveInterval;
+var gAutoSaveTimeout;
+var gAutoSaveKickedIn;
+var gEditingDraft;
+
+var kComposeAttachDirPrefName = "mail.compose.attach.dir";
+
+function InitializeGlobalVariables()
+{
+ gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+ gMsgCompose = null;
+ gOriginalMsgURI = null;
+ gWindowLocked = false;
+ gContentChanged = false;
+ gCurrentIdentity = null;
+ defaultSaveOperation = "draft";
+ gSendOrSaveOperationInProgress = false;
+ gAutoSaving = false;
+ gCloseWindowAfterSave = false;
+ gSavedSendNowKey = null;
+ gSendFormat = nsIMsgCompSendFormat.AskUser;
+ gCharsetConvertManager = Cc['@mozilla.org/charset-converter-manager;1'].getService(Ci.nsICharsetConverterManager);
+ gHideMenus = false;
+ // We are storing the value of the bool logComposePerformance inorder to
+ // avoid logging unnecessarily.
+ gLogComposePerformance = MailServices.compose.logComposePerformance;
+
+ gLastWindowToHaveFocus = null;
+ gReceiptOptionChanged = false;
+ gDSNOptionChanged = false;
+ gAttachVCardOptionChanged = false;
+ msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"]
+ .createInstance(Ci.nsIMsgWindow);
+ MailServices.mailSession.AddMsgWindow(msgWindow);
+}
+InitializeGlobalVariables();
+
+function ReleaseGlobalVariables()
+{
+ gCurrentIdentity = null;
+ gCharsetConvertManager = null;
+ gMsgCompose = null;
+ gOriginalMsgURI = null;
+ gMessenger = null;
+ sComposeMsgsBundle = null;
+ sBrandBundle = null;
+ MailServices.mailSession.RemoveMsgWindow(msgWindow);
+ msgWindow = null;
+}
+
+function disableEditableFields()
+{
+ gMsgCompose.editor.flags |= Ci.nsIEditor.eEditorReadonlyMask;
+ var disableElements = document.getElementsByAttribute("disableonsend", "true");
+ for (let i = 0; i < disableElements.length; i++)
+ disableElements[i].setAttribute('disabled', 'true');
+
+}
+
+function enableEditableFields()
+{
+ gMsgCompose.editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask;
+ var enableElements = document.getElementsByAttribute("disableonsend", "true");
+ for (let i = 0; i < enableElements.length; i++)
+ enableElements[i].removeAttribute('disabled');
+
+}
+
+/**
+ * Small helper function to check whether the node passed in is a signature.
+ * Note that a text node is not a DOM element, hence .localName can't be used.
+ */
+function isSignature(aNode) {
+ return ["DIV","PRE"].includes(aNode.nodeName) &&
+ aNode.classList.contains("moz-signature");
+}
+
+var stateListener = {
+ NotifyComposeFieldsReady: function() {
+ ComposeFieldsReady();
+ updateSendCommands(true);
+ },
+
+ NotifyComposeBodyReady: function() {
+ this.useParagraph = gMsgCompose.composeHTML &&
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph");
+ this.editor = GetCurrentEditor();
+ this.paragraphState = document.getElementById("cmd_paragraphState");
+
+ // Look at the compose types which require action (nsIMsgComposeParams.idl):
+ switch (gComposeType) {
+
+ case Ci.nsIMsgCompType.MailToUrl:
+ gBodyFromArgs = true;
+ case Ci.nsIMsgCompType.New:
+ case Ci.nsIMsgCompType.NewsPost:
+ case Ci.nsIMsgCompType.ForwardAsAttachment:
+ this.NotifyComposeBodyReadyNew();
+ break;
+
+ case Ci.nsIMsgCompType.Reply:
+ case Ci.nsIMsgCompType.ReplyAll:
+ case Ci.nsIMsgCompType.ReplyToSender:
+ case Ci.nsIMsgCompType.ReplyToGroup:
+ case Ci.nsIMsgCompType.ReplyToSenderAndGroup:
+ case Ci.nsIMsgCompType.ReplyWithTemplate:
+ case Ci.nsIMsgCompType.ReplyToList:
+ this.NotifyComposeBodyReadyReply();
+ break;
+
+ case Ci.nsIMsgCompType.ForwardInline:
+ this.NotifyComposeBodyReadyForwardInline();
+ break;
+
+ case Ci.nsIMsgCompType.EditTemplate:
+ defaultSaveOperation = "template";
+ case Ci.nsIMsgCompType.Draft:
+ case Ci.nsIMsgCompType.Template:
+ case Ci.nsIMsgCompType.Redirect:
+ case Ci.nsIMsgCompType.EditAsNew:
+ break;
+
+ default:
+ dump("Unexpected nsIMsgCompType in NotifyComposeBodyReady (" +
+ gComposeType + ")\n");
+ }
+
+ // Set the selected item in the identity list as needed, which will cause
+ // an identity/signature switch. This can only be done once the message
+ // body has already been assembled with the signature we need to switch.
+ if (gMsgCompose.identity != gCurrentIdentity) {
+ // Since switching the signature loses the caret position, we record it
+ // and restore it later.
+ let selection = this.editor.selection;
+ let range = selection.getRangeAt(0);
+ let start = range.startOffset;
+ let startNode = range.startContainer;
+
+ this.editor.enableUndo(false);
+ let identityList = GetMsgIdentityElement();
+ identityList.selectedItem = identityList.getElementsByAttribute(
+ "identitykey", gMsgCompose.identity.key)[0];
+ LoadIdentity(false);
+
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ selection.collapse(startNode, start);
+ }
+
+ if (gMsgCompose.composeHTML)
+ loadHTMLMsgPrefs();
+ AdjustFocus();
+ },
+
+ NotifyComposeBodyReadyNew: function() {
+ let insertParagraph = this.useParagraph;
+
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ if (insertParagraph && gBodyFromArgs) {
+ // Check for "empty" body before allowing paragraph to be inserted.
+ // Non-empty bodies in a new message can occur when clicking on a
+ // mailto link or when using the command line option -compose.
+ // An "empty" body can be one of these two cases:
+ // 1) <br> and nothing follows (no next sibling)
+ // 2) <div/pre class="moz-signature">
+ // Note that <br><div/pre class="moz-signature"> doesn't happen in
+ // paragraph mode.
+ let firstChild = mailBody.firstChild;
+ if ((firstChild.nodeName != "BR" || firstChild.nextSibling) &&
+ !isSignature(firstChild))
+ insertParagraph = false;
+ }
+
+ // Control insertion of line breaks.
+ if (insertParagraph) {
+ this.editor.enableUndo(false);
+
+ this.editor.selection.collapse(mailBody, 0);
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+
+ this.paragraphState.setAttribute("state", "p");
+
+ this.editor.beginningOfDocument();
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ } else {
+ this.paragraphState.setAttribute("state", "");
+ }
+ },
+
+ NotifyComposeBodyReadyReply: function() {
+ // Control insertion of line breaks.
+ if (this.useParagraph) {
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ let selection = this.editor.selection;
+
+ // Make sure the selection isn't inside the signature.
+ if (isSignature(mailBody.firstChild))
+ selection.collapse(mailBody, 0);
+
+ let range = selection.getRangeAt(0);
+ let start = range.startOffset;
+
+ if (start != range.endOffset) {
+ // The selection is not collapsed, most likely due to the
+ // "select the quote" option. In this case we do nothing.
+ return;
+ }
+
+ if (range.startContainer != mailBody) {
+ dump("Unexpected selection in NotifyComposeBodyReadyReply\n");
+ return;
+ }
+
+ this.editor.enableUndo(false);
+
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+
+ // Position into the paragraph.
+ selection.collapse(pElement, 0);
+
+ this.paragraphState.setAttribute("state", "p");
+
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ } else {
+ this.paragraphState.setAttribute("state", "");
+ }
+ },
+
+ NotifyComposeBodyReadyForwardInline: function() {
+ let mailDoc = document.getElementById("content-frame").contentDocument;
+ let mailBody = mailDoc.querySelector("body");
+ let selection = this.editor.selection;
+
+ this.editor.enableUndo(false);
+
+ // Control insertion of line breaks.
+ selection.collapse(mailBody, 0);
+ if (this.useParagraph) {
+ let pElement = this.editor.createElementWithDefaults("p");
+ let brElement = this.editor.createElementWithDefaults("br");
+ pElement.appendChild(brElement);
+ this.editor.insertElementAtSelection(pElement, false);
+ this.paragraphState.setAttribute("state", "p");
+ } else {
+ // insertLineBreak() has been observed to insert two <br> elements
+ // instead of one before a <div>, so we'll do it ourselves here.
+ let brElement = this.editor.createElementWithDefaults("br");
+ this.editor.insertElementAtSelection(brElement, false);
+ this.paragraphState.setAttribute("state", "");
+ }
+
+ this.editor.beginningOfDocument();
+ this.editor.enableUndo(true);
+ this.editor.resetModificationCount();
+ },
+
+ ComposeProcessDone: function(aResult) {
+ gWindowLocked = false;
+ enableEditableFields();
+ updateComposeItems();
+
+ if (aResult== Cr.NS_OK)
+ {
+ if (!gAutoSaving)
+ SetContentAndBodyAsUnmodified();
+
+ if (gCloseWindowAfterSave)
+ {
+ // Notify the SendListener that Send has been aborted and Stopped
+ if (gMsgCompose)
+ gMsgCompose.onSendNotPerformed(null, Cr.NS_ERROR_ABORT);
+
+ MsgComposeCloseWindow();
+ }
+ }
+ // else if we failed to save, and we're autosaving, need to re-mark the editor
+ // as changed, so that we won't lose the changes.
+ else if (gAutoSaving)
+ {
+ gMsgCompose.bodyModified = true;
+ gContentChanged = true;
+ }
+
+ gAutoSaving = false;
+ gCloseWindowAfterSave = false;
+ },
+
+ SaveInFolderDone: function(folderURI) {
+ DisplaySaveFolderDlg(folderURI);
+ }
+};
+
+// all progress notifications are done through the nsIWebProgressListener implementation...
+var progressListener = {
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START)
+ {
+ document.getElementById('navigator-throbber').setAttribute("busy", "true");
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
+ document.getElementById("statusbar-progresspanel").collapsed = false;
+ }
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
+ {
+ gSendOrSaveOperationInProgress = false;
+ document.getElementById('navigator-throbber').removeAttribute("busy");
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "normal" );
+ document.getElementById('compose-progressmeter').setAttribute( "value", 0 );
+ document.getElementById("statusbar-progresspanel").collapsed = true;
+ document.getElementById('statusText').setAttribute('label', '');
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ // Calculate percentage.
+ var percent;
+ if ( aMaxTotalProgress > 0 )
+ {
+ percent = Math.round( (aCurTotalProgress*100)/aMaxTotalProgress );
+ if ( percent > 100 )
+ percent = 100;
+
+ document.getElementById('compose-progressmeter').removeAttribute("mode");
+
+ // Advance progress meter.
+ document.getElementById('compose-progressmeter').setAttribute( "value", percent );
+ }
+ else
+ {
+ // Progress meter should be barber-pole in this case.
+ document.getElementById('compose-progressmeter').setAttribute( "mode", "undetermined" );
+ }
+ },
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ // we can ignore this notification
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ // Looks like it's possible that we get call while the document has been already delete!
+ // therefore we need to protect ourself by using try/catch
+ try {
+ let statusText = document.getElementById("statusText");
+ if (statusText)
+ statusText.setAttribute("label", aMessage);
+ } catch (ex) {}
+ },
+
+ onSecurityChange: function(aWebProgress, aRequest, state)
+ {
+ // we can ignore this notification
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ }
+};
+
+var defaultController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile":
+ case "cmd_attachPage":
+ case "cmd_close":
+ case "cmd_save":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsDraft":
+ case "cmd_saveAsTemplate":
+ case "cmd_sendButton":
+ case "cmd_sendNow":
+ case "cmd_sendWithCheck":
+ case "cmd_sendLater":
+ case "cmd_printSetup":
+ case "cmd_printpreview":
+ case "cmd_print":
+
+ //Edit Menu
+ case "cmd_account":
+ case "cmd_preferences":
+
+ //Options Menu
+ case "cmd_selectAddress":
+ case "cmd_outputFormat":
+ case "cmd_quoteMessage":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+ isCommandEnabled: function(command)
+ {
+ var composeHTML = gMsgCompose && gMsgCompose.composeHTML;
+
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile":
+ case "cmd_attachPage":
+ case "cmd_close":
+ case "cmd_save":
+ case "cmd_saveAsFile":
+ case "cmd_saveAsDraft":
+ case "cmd_saveAsTemplate":
+ case "cmd_printSetup":
+ case "cmd_printpreview":
+ case "cmd_print":
+ return !gWindowLocked;
+ case "cmd_sendButton":
+ case "cmd_sendLater":
+ case "cmd_sendWithCheck":
+ case "cmd_sendButton":
+ return !gWindowLocked && !gSendLocked;
+ case "cmd_sendNow":
+ return !gWindowLocked && !Services.io.offline && !gSendLocked;
+
+ //Edit Menu
+ case "cmd_account":
+ case "cmd_preferences":
+ return true;
+
+ //Options Menu
+ case "cmd_selectAddress":
+ return !gWindowLocked;
+ case "cmd_outputFormat":
+ return composeHTML;
+ case "cmd_quoteMessage":
+ var selectedURIs = GetSelectedMessages();
+ if (selectedURIs && selectedURIs.length > 0)
+ return true;
+ return false;
+
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ switch (command)
+ {
+ //File Menu
+ case "cmd_attachFile" : if (defaultController.isCommandEnabled(command)) AttachFile(); break;
+ case "cmd_attachPage" : AttachPage(); break;
+ case "cmd_close" : DoCommandClose(); break;
+ case "cmd_save" : Save(); break;
+ case "cmd_saveAsFile" : SaveAsFile(true); break;
+ case "cmd_saveAsDraft" : SaveAsDraft(); break;
+ case "cmd_saveAsTemplate" : SaveAsTemplate(); break;
+ case "cmd_sendButton" :
+ if (defaultController.isCommandEnabled(command))
+ {
+ if (Services.io.offline)
+ SendMessageLater();
+ else
+ SendMessage();
+ }
+ break;
+ case "cmd_sendNow" : if (defaultController.isCommandEnabled(command)) SendMessage(); break;
+ case "cmd_sendWithCheck" : if (defaultController.isCommandEnabled(command)) SendMessageWithCheck(); break;
+ case "cmd_sendLater" : if (defaultController.isCommandEnabled(command)) SendMessageLater(); break;
+ case "cmd_printSetup" : PrintUtils.showPageSetup(); break;
+ case "cmd_printpreview" : PrintUtils.printPreview(PrintPreviewListener); break;
+ case "cmd_print" :
+ let browser = GetCurrentEditorElement();
+ PrintUtils.printWindow(browser.outerWindowID, browser);
+ break;
+
+ //Edit Menu
+ case "cmd_account" :
+ let currentAccountKey = getCurrentAccountKey();
+ let account = MailServices.accounts.getAccount(currentAccountKey);
+ MsgAccountManager(null, account.incomingServer);
+ break;
+ case "cmd_preferences" : DoCommandPreferences(); break;
+
+ //Options Menu
+ case "cmd_selectAddress" : if (defaultController.isCommandEnabled(command)) SelectAddress(); break;
+ case "cmd_quoteMessage" : if (defaultController.isCommandEnabled(command)) QuoteSelectedMessage(); break;
+ default:
+ return;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+var gAttachmentBucketController =
+{
+ supportsCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ case "cmd_renameAttachment":
+ case "cmd_selectAll":
+ case "cmd_openAttachment":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ return MessageGetNumSelectedAttachments() > 0;
+ case "cmd_renameAttachment":
+ return MessageGetNumSelectedAttachments() == 1;
+ case "cmd_selectAll":
+ return MessageHasAttachments();
+ case "cmd_openAttachment":
+ return MessageGetNumSelectedAttachments() == 1;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(aCommand)
+ {
+ switch (aCommand)
+ {
+ case "cmd_delete":
+ if (MessageGetNumSelectedAttachments() > 0)
+ RemoveSelectedAttachment();
+ break;
+ case "cmd_renameAttachment":
+ if (MessageGetNumSelectedAttachments() == 1)
+ RenameSelectedAttachment();
+ break;
+ case "cmd_selectAll":
+ if (MessageHasAttachments())
+ SelectAllAttachments();
+ break;
+ case "cmd_openAttachment":
+ if (MessageGetNumSelectedAttachments() == 1)
+ OpenSelectedAttachment();
+ break;
+ default:
+ return;
+ }
+ },
+
+ onEvent: function(event)
+ {
+ }
+};
+
+function QuoteSelectedMessage()
+{
+ var selectedURIs = GetSelectedMessages();
+ if (selectedURIs)
+ for (let i = 0; i < selectedURIs.length; i++)
+ gMsgCompose.quoteMessage(selectedURIs[i]);
+}
+
+function GetSelectedMessages()
+{
+ var mailWindow = gMsgCompose && Services.wm.getMostRecentWindow("mail:3pane");
+ return mailWindow && mailWindow.gFolderDisplay.selectedMessageUris;
+}
+
+function SetupCommandUpdateHandlers()
+{
+ top.controllers.appendController(defaultController);
+
+ let attachmentBucket = document.getElementById("attachmentBucket");
+ attachmentBucket.controllers.appendController(gAttachmentBucketController);
+
+ document.getElementById("optionsMenuPopup")
+ .addEventListener("popupshowing", updateOptionItems, true);
+}
+
+function UnloadCommandUpdateHandlers()
+{
+ document.getElementById("optionsMenuPopup")
+ .removeEventListener("popupshowing", updateOptionItems, true);
+
+ top.controllers.removeController(defaultController);
+
+ let attachmentBucket = document.getElementById("attachmentBucket");
+ attachmentBucket.controllers.removeController(gAttachmentBucketController);
+}
+
+function CommandUpdate_MsgCompose()
+{
+ var focusedWindow = top.document.commandDispatcher.focusedWindow;
+
+ // we're just setting focus to where it was before
+ if (focusedWindow == gLastWindowToHaveFocus) {
+ return;
+ }
+
+ gLastWindowToHaveFocus = focusedWindow;
+
+ updateComposeItems();
+}
+
+function updateComposeItems()
+{
+ try {
+ // Edit Menu
+ goUpdateCommand("cmd_rewrap");
+
+ // Insert Menu
+ if (gMsgCompose && gMsgCompose.composeHTML)
+ {
+ goUpdateCommand("cmd_renderedHTMLEnabler");
+ goUpdateCommand("cmd_decreaseFontStep");
+ goUpdateCommand("cmd_increaseFontStep");
+ goUpdateCommand("cmd_bold");
+ goUpdateCommand("cmd_italic");
+ goUpdateCommand("cmd_underline");
+ goUpdateCommand("cmd_ul");
+ goUpdateCommand("cmd_ol");
+ goUpdateCommand("cmd_indent");
+ goUpdateCommand("cmd_outdent");
+ goUpdateCommand("cmd_align");
+ goUpdateCommand("cmd_smiley");
+ }
+
+ // Options Menu
+ goUpdateCommand("cmd_spelling");
+ } catch(e) {}
+}
+
+function openEditorContextMenu(popup)
+{
+ gContextMenu = new nsContextMenu(popup);
+ if (gContextMenu.shouldDisplay)
+ {
+ // If message body context menu then focused element should be content.
+ var showPasteExtra =
+ top.document.commandDispatcher.focusedWindow == content;
+ gContextMenu.showItem("context-pasteNoFormatting", showPasteExtra);
+ gContextMenu.showItem("context-pasteQuote", showPasteExtra);
+ if (showPasteExtra)
+ {
+ goUpdateCommand("cmd_pasteNoFormatting");
+ goUpdateCommand("cmd_pasteQuote");
+ }
+ return true;
+ }
+ return false;
+}
+
+function updateEditItems()
+{
+ goUpdateCommand("cmd_pasteNoFormatting");
+ goUpdateCommand("cmd_pasteQuote");
+ goUpdateCommand("cmd_delete");
+ goUpdateCommand("cmd_renameAttachment");
+ goUpdateCommand("cmd_selectAll");
+ goUpdateCommand("cmd_openAttachment");
+ goUpdateCommand("cmd_findReplace");
+ goUpdateCommand("cmd_find");
+ goUpdateCommand("cmd_findNext");
+ goUpdateCommand("cmd_findPrev");
+}
+
+function updateOptionItems()
+{
+ goUpdateCommand("cmd_quoteMessage");
+}
+
+/**
+ * Update all the commands for sending a message to reflect their current state.
+ */
+function updateSendCommands(aHaveController) {
+ updateSendLock();
+ if (aHaveController) {
+ goUpdateCommand("cmd_sendButton");
+ goUpdateCommand("cmd_sendNow");
+ goUpdateCommand("cmd_sendLater");
+ goUpdateCommand("cmd_sendWithCheck");
+ } else {
+ goSetCommandEnabled("cmd_sendButton",
+ defaultController.isCommandEnabled("cmd_sendButton"));
+ goSetCommandEnabled("cmd_sendNow",
+ defaultController.isCommandEnabled("cmd_sendNow"));
+ goSetCommandEnabled("cmd_sendLater",
+ defaultController.isCommandEnabled("cmd_sendLater"));
+ goSetCommandEnabled("cmd_sendWithCheck",
+ defaultController.isCommandEnabled("cmd_sendWithCheck"));
+ }
+}
+
+var messageComposeOfflineQuitObserver = {
+ observe: function(aSubject, aTopic, aState) {
+ // sanity checks
+ if (aTopic == "network:offline-status-changed")
+ {
+ MessageComposeOfflineStateChanged(aState == "offline");
+ }
+ // check whether to veto the quit request (unless another observer already
+ // did)
+ else if (aTopic == "quit-application-requested" &&
+ aSubject instanceof Ci.nsISupportsPRBool &&
+ !aSubject.data)
+ aSubject.data = !ComposeCanClose();
+ }
+}
+
+function AddMessageComposeOfflineQuitObserver()
+{
+ Services.obs.addObserver(messageComposeOfflineQuitObserver,
+ "network:offline-status-changed");
+ Services.obs.addObserver(messageComposeOfflineQuitObserver,
+ "quit-application-requested");
+
+ // set the initial state of the send button
+ MessageComposeOfflineStateChanged(Services.io.offline);
+}
+
+function RemoveMessageComposeOfflineQuitObserver()
+{
+ Services.obs.removeObserver(messageComposeOfflineQuitObserver,
+ "network:offline-status-changed");
+ Services.obs.removeObserver(messageComposeOfflineQuitObserver,
+ "quit-application-requested");
+}
+
+function MessageComposeOfflineStateChanged(goingOffline)
+{
+ try {
+ var sendButton = document.getElementById("button-send");
+ var sendNowMenuItem = document.getElementById("menu_sendNow");
+
+ if (!gSavedSendNowKey) {
+ gSavedSendNowKey = sendNowMenuItem.getAttribute('key');
+ }
+
+ // don't use goUpdateCommand here ... the defaultController might not be installed yet
+ updateSendCommands(false);
+
+ if (goingOffline)
+ {
+ sendButton.label = sendButton.getAttribute('later_label');
+ sendButton.setAttribute('tooltiptext', sendButton.getAttribute('later_tooltiptext'));
+ sendNowMenuItem.removeAttribute('key');
+ }
+ else
+ {
+ sendButton.label = sendButton.getAttribute('now_label');
+ sendButton.setAttribute('tooltiptext', sendButton.getAttribute('now_tooltiptext'));
+ if (gSavedSendNowKey) {
+ sendNowMenuItem.setAttribute('key', gSavedSendNowKey);
+ }
+ }
+
+ } catch(e) {}
+}
+
+function DoCommandClose()
+{
+ if (ComposeCanClose()) {
+ // Notify the SendListener that Send has been aborted and Stopped
+ if (gMsgCompose)
+ gMsgCompose.onSendNotPerformed(null, Cr.NS_ERROR_ABORT);
+
+ // note: if we're not caching this window, this destroys it for us
+ MsgComposeCloseWindow();
+ }
+
+ return false;
+}
+
+function DoCommandPreferences()
+{
+ goPreferences('composing_messages_pane');
+}
+
+function toggleAffectedChrome(aHide)
+{
+ // chrome to toggle includes:
+ // (*) menubar
+ // (*) toolbox
+ // (*) sidebar
+ // (*) statusbar
+
+ if (!gChromeState)
+ gChromeState = {};
+
+ var statusbar = document.getElementById("status-bar");
+
+ // sidebar states map as follows:
+ // hidden => hide/show nothing
+ // collapsed => hide/show only the splitter
+ // shown => hide/show the splitter and the box
+ if (aHide)
+ {
+ // going into print preview mode
+ gChromeState.sidebar = SidebarGetState();
+ SidebarSetState("hidden");
+
+ // deal with the Status Bar
+ gChromeState.statusbarWasHidden = statusbar.hidden;
+ statusbar.hidden = true;
+ }
+ else
+ {
+ // restoring normal mode (i.e., leaving print preview mode)
+ SidebarSetState(gChromeState.sidebar);
+
+ // restore the Status Bar
+ statusbar.hidden = gChromeState.statusbarWasHidden;
+ }
+
+ // if we are unhiding and sidebar used to be there rebuild it
+ if (!aHide && gChromeState.sidebar == "visible")
+ SidebarRebuild();
+
+ getMailToolbox().hidden = aHide;
+ document.getElementById("appcontent").collapsed = aHide;
+}
+
+var PrintPreviewListener = {
+ getPrintPreviewBrowser()
+ {
+ var browser = document.getElementById("ppBrowser");
+ if (!browser)
+ {
+ browser = document.createElement("browser");
+ browser.setAttribute("id", "ppBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("disablehistory", "true");
+ browser.setAttribute("disablesecurity", "true");
+ browser.setAttribute("type", "content");
+ document.getElementById("sidebar-parent")
+ .insertBefore(browser, document.getElementById("appcontent"));
+ }
+ return browser;
+ },
+ getSourceBrowser()
+ {
+ return GetCurrentEditorElement();
+ },
+ getNavToolbox()
+ {
+ return getMailToolbox();
+ },
+ onEnter()
+ {
+ toggleAffectedChrome(true);
+ },
+ onExit()
+ {
+ document.getElementById("ppBrowser").collapsed = true;
+ toggleAffectedChrome(false);
+ }
+}
+
+function ToggleWindowLock()
+{
+ gWindowLocked = !gWindowLocked;
+ updateComposeItems();
+}
+
+/* This function will go away soon as now arguments are passed to the window using a object of type nsMsgComposeParams instead of a string */
+function GetArgs(originalData)
+{
+ var args = new Object();
+
+ if (originalData == "")
+ return null;
+
+ var data = "";
+ var separator = String.fromCharCode(1);
+
+ var quoteChar = "";
+ var prevChar = "";
+ var nextChar = "";
+ for (let i = 0; i < originalData.length; i++, prevChar = aChar)
+ {
+ var aChar = originalData.charAt(i)
+ var aCharCode = originalData.charCodeAt(i)
+ if ( i < originalData.length - 1)
+ nextChar = originalData.charAt(i + 1);
+ else
+ nextChar = "";
+
+ if (aChar == quoteChar && (nextChar == "," || nextChar == ""))
+ {
+ quoteChar = "";
+ data += aChar;
+ }
+ else if ((aCharCode == 39 || aCharCode == 34) && prevChar == "=") //quote or double quote
+ {
+ if (quoteChar == "")
+ quoteChar = aChar;
+ data += aChar;
+ }
+ else if (aChar == ",")
+ {
+ if (quoteChar == "")
+ data += separator;
+ else
+ data += aChar
+ }
+ else
+ data += aChar
+ }
+
+ var pairs = data.split(separator);
+
+ for (let i = pairs.length - 1; i >= 0; i--)
+ {
+ var pos = pairs[i].indexOf('=');
+ if (pos == -1)
+ continue;
+ var argname = pairs[i].substring(0, pos);
+ var argvalue = pairs[i].substring(pos + 1);
+ if (argvalue.charAt(0) == "'" && argvalue.charAt(argvalue.length - 1) == "'")
+ args[argname] = argvalue.substring(1, argvalue.length - 1);
+ else
+ try {
+ args[argname] = decodeURIComponent(argvalue);
+ } catch (e) {args[argname] = argvalue;}
+ // dump("[" + argname + "=" + args[argname] + "]\n");
+ }
+ return args;
+}
+
+function ComposeFieldsReady()
+{
+ //If we are in plain text, we need to set the wrap column
+ if (! gMsgCompose.composeHTML) {
+ try {
+ gMsgCompose.editor.wrapWidth = gMsgCompose.wrapLength;
+ }
+ catch (e) {
+ dump("### textEditor.wrapWidth exception text: " + e + " - failed\n");
+ }
+ }
+ CompFields2Recipients(gMsgCompose.compFields);
+ SetComposeWindowTitle();
+ enableEditableFields();
+}
+
+// checks if the passed in string is a mailto url, if it is, generates nsIMsgComposeParams
+// for the url and returns them.
+function handleMailtoArgs(mailtoUrl)
+{
+ // see if the string is a mailto url....do this by checking the first 7 characters of the string
+ if (/^mailto:/i.test(mailtoUrl))
+ {
+ // if it is a mailto url, turn the mailto url into a MsgComposeParams object....
+ var uri = Services.io.newURI(mailtoUrl);
+
+ if (uri)
+ return MailServices.compose.getParamsForMailto(uri);
+ }
+
+ return null;
+}
+/**
+ * Handle ESC keypress from composition window for
+ * notifications with close button in the
+ * attachmentNotificationBox.
+ */
+function handleEsc()
+{
+ let activeElement = document.activeElement;
+
+ // If findbar is visible and the focus is in the message body,
+ // hide it. (Focus on the findbar is handled by findbar itself).
+ let findbar = document.getElementById("FindToolbar");
+ if (findbar && !findbar.hidden && activeElement.id == "content-frame") {
+ findbar.close();
+ return;
+ }
+
+ // If there is a notification in the attachmentNotificationBox
+ // AND focus is in message body, subject field or on the notification,
+ // hide it.
+ let notification = document.getElementById("attachmentNotificationBox")
+ .currentNotification;
+ if (notification && (activeElement.id == "content-frame" ||
+ activeElement.parentNode.parentNode.id == "msgSubject" ||
+ notification.contains(activeElement) ||
+ activeElement.classList.contains("messageCloseButton"))) {
+ notification.close();
+ }
+}
+
+/**
+ * On paste or drop, we may want to modify the content before inserting it into
+ * the editor, replacing file URLs with data URLs when appropriate.
+ */
+function onPasteOrDrop(e) {
+ // For paste use e.clipboardData, for drop use e.dataTransfer.
+ let dataTransfer = ("clipboardData" in e) ? e.clipboardData : e.dataTransfer;
+
+ if (!dataTransfer.types.includes("text/html")) {
+ return;
+ }
+
+ if (!gMsgCompose.composeHTML) {
+ // We're in the plain text editor. Nothing to do here.
+ return;
+ }
+
+ let html = dataTransfer.getData("text/html");
+ let doc = (new DOMParser()).parseFromString(html, "text/html");
+ let tmpD = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ let pendingConversions = 0;
+ let needToPreventDefault = true;
+ for (let img of doc.images) {
+ if (!/^file:/i.test(img.src)) {
+ // Doesn't start with file:. Nothing to do here.
+ continue;
+ }
+
+ // This may throw if the URL is invalid for the OS.
+ let nsFile;
+ try {
+ nsFile = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler)
+ .getFileFromURLSpec(img.src);
+ } catch (ex) {
+ continue;
+ }
+
+ if (!nsFile.exists()) {
+ continue;
+ }
+
+ if (!tmpD.contains(nsFile)) {
+ // Not anywhere under the temp dir.
+ continue;
+ }
+
+ let contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromFile(nsFile);
+ if (!contentType.startsWith("image/")) {
+ continue;
+ }
+
+ // If we ever get here, we need to prevent the default paste or drop since
+ // the code below will do its own insertion.
+ if (needToPreventDefault) {
+ e.preventDefault();
+ needToPreventDefault = false;
+ }
+
+ File.createFromNsIFile(nsFile).then(function(file) {
+ if (file.lastModified < (Date.now() - 60000)) {
+ // Not put in temp in the last minute. May be something other than
+ // a copy-paste. Let's not allow that.
+ return;
+ }
+
+ let doTheInsert = function() {
+ // Now run it through sanitation to make sure there wasn't any
+ // unwanted things in the content.
+ let ParserUtils = Cc["@mozilla.org/parserutils;1"]
+ .getService(Ci.nsIParserUtils);
+ let html2 = ParserUtils.sanitize(doc.documentElement.innerHTML,
+ ParserUtils.SanitizerAllowStyle);
+ getBrowser().contentDocument.execCommand("insertHTML", false, html2);
+ }
+
+ // Everything checks out. Convert file to data URL.
+ let reader = new FileReader();
+ reader.addEventListener("load", function() {
+ let dataURL = reader.result;
+ pendingConversions--;
+ img.src = dataURL;
+ if (pendingConversions == 0) {
+ doTheInsert();
+ }
+ });
+
+ reader.addEventListener("error", function() {
+ pendingConversions--;
+ if (pendingConversions == 0) {
+ doTheInsert();
+ }
+ });
+
+ pendingConversions++;
+ reader.readAsDataURL(file);
+ });
+ }
+}
+
+function ComposeStartup(aParams)
+{
+ var params = null; // New way to pass parameters to the compose window as a nsIMsgComposeParameters object
+ var args = null; // old way, parameters are passed as a string
+ gBodyFromArgs = false;
+
+ if (aParams)
+ params = aParams;
+ else if (window.arguments && window.arguments[0]) {
+ try {
+ if (window.arguments[0] instanceof Ci.nsIMsgComposeParams)
+ params = window.arguments[0];
+ else
+ params = handleMailtoArgs(window.arguments[0]);
+ }
+ catch(ex) { dump("ERROR with parameters: " + ex + "\n"); }
+
+ // if still no dice, try and see if the params is an old fashioned list of string attributes
+ // XXX can we get rid of this yet?
+ if (!params)
+ {
+ args = GetArgs(window.arguments[0]);
+ }
+ }
+
+ // Set the document language to the preference as early as possible.
+ document.documentElement
+ .setAttribute("lang", Services.prefs.getCharPref("spellchecker.dictionary"));
+
+ var identityList = GetMsgIdentityElement();
+
+ document.addEventListener("paste", onPasteOrDrop);
+ document.addEventListener("drop", onPasteOrDrop);
+
+ if (identityList)
+ FillIdentityList(identityList);
+
+ if (!params) {
+ // This code will go away soon as now arguments are passed to the window
+ // using a object of type nsMsgComposeParams instead of a string.
+ params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+
+ if (args) { //Convert old fashion arguments into params
+ var composeFields = params.composeFields;
+ if (args.bodyislink && args.bodyislink == "true")
+ params.bodyIsLink = true;
+ if (args.type)
+ params.type = args.type;
+ if (args.format) {
+ // Only use valid values.
+ if (args.format == Ci.nsIMsgCompFormat.PlainText ||
+ args.format == Ci.nsIMsgCompFormat.HTML ||
+ args.format == Ci.nsIMsgCompFormat.OppositeOfDefault)
+ params.format = args.format;
+ else if (args.format.toLowerCase().trim() == "html")
+ params.format = Ci.nsIMsgCompFormat.HTML;
+ else if (args.format.toLowerCase().trim() == "text")
+ params.format = Ci.nsIMsgCompFormat.PlainText;
+ }
+ if (args.originalMsgURI)
+ params.originalMsgURI = args.originalMsgURI;
+ if (args.preselectid)
+ params.identity = getIdentityForKey(args.preselectid);
+ if (args.from)
+ composeFields.from = args.from;
+ if (args.to)
+ composeFields.to = args.to;
+ if (args.cc)
+ composeFields.cc = args.cc;
+ if (args.bcc)
+ composeFields.bcc = args.bcc;
+ if (args.newsgroups)
+ composeFields.newsgroups = args.newsgroups;
+ if (args.subject)
+ composeFields.subject = args.subject;
+ if (args.attachment)
+ {
+ var attachmentList = args.attachment.split(",");
+ var commandLine = Cc["@mozilla.org/toolkit/command-line;1"]
+ .createInstance();
+ for (let i = 0; i < attachmentList.length; i++)
+ {
+ let attachmentStr = attachmentList[i];
+ let uri = commandLine.resolveURI(attachmentStr);
+ let attachment = Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+
+ if (uri instanceof Ci.nsIFileURL)
+ {
+ if (uri.file.exists())
+ attachment.size = uri.file.fileSize;
+ else
+ attachment = null;
+ }
+
+ // Only want to attach if a file that exists or it is not a file.
+ if (attachment)
+ {
+ attachment.url = uri.spec;
+ composeFields.addAttachment(attachment);
+ }
+ else
+ {
+ let title = sComposeMsgsBundle.getString("errorFileAttachTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorFileAttachMessage",
+ [attachmentStr]);
+ Services.prompt.alert(null, title, msg);
+ }
+ }
+ }
+ if (args.newshost)
+ composeFields.newshost = args.newshost;
+ if (args.message) {
+ let msgFile = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsIFile);
+ if (OS.Path.dirname(args.message) == ".") {
+ let workingDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ args.message = OS.Path.join(workingDir.path, OS.Path.basename(args.message));
+ }
+ msgFile.initWithPath(args.message);
+
+ if (!msgFile.exists()) {
+ let title = sComposeMsgsBundle.getString("errorFileMessageTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorFileMessageMessage",
+ [args.message]);
+ Services.prompt.alert(null, title, msg);
+ } else {
+ let data = "";
+ let fstream = null;
+ let cstream = null;
+
+ try {
+ fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ fstream.init(msgFile, -1, 0, 0); // Open file in default/read-only mode.
+ cstream.init(fstream, "UTF-8", 0, 0);
+
+ let str = {};
+ let read = 0;
+
+ do {
+ // Read as much as we can and put it in str.value.
+ read = cstream.readString(0xffffffff, str);
+ data += str.value;
+ } while (read != 0);
+ } catch (e) {
+ let title = sComposeMsgsBundle.getString("errorFileMessageTitle");
+ let msg = sComposeMsgsBundle.getFormattedString("errorLoadFileMessageMessage",
+ [args.message]);
+ Services.prompt.alert(null, title, msg);
+
+ } finally {
+ if (cstream)
+ cstream.close();
+ if (fstream)
+ fstream.close();
+ }
+
+ if (data) {
+ let pos = data.search(/\S/); // Find first non-whitespace character.
+
+ if (params.format != Ci.nsIMsgCompFormat.PlainText &&
+ (args.message.endsWith(".htm") ||
+ args.message.endsWith(".html") ||
+ data.substr(pos, 14).toLowerCase() == "<!doctype html" ||
+ data.substr(pos, 5).toLowerCase() == "<html")) {
+ // We replace line breaks because otherwise they'll be converted
+ // to <br> in nsMsgCompose::BuildBodyMessageAndSignature().
+ // Don't do the conversion if the user asked explicitly for plain
+ // text.
+ data = data.replace(/\r?\n/g, " ");
+ }
+ gBodyFromArgs = true;
+ composeFields.body = data;
+ }
+ }
+ } else if (args.body) {
+ gBodyFromArgs = true;
+ composeFields.body = args.body;
+ }
+ }
+ }
+
+ gComposeType = params.type;
+
+ // Detect correct identity when missing or mismatched.
+ // An identity with no email is likely not valid.
+ // When editing a draft, 'params.identity' is pre-populated with the identity
+ // that created the draft or the identity owning the draft folder for a
+ // "foreign", draft, see ComposeMessage() in mailCommands.js. We don't want
+ // the latter, so use the creator identity which could be null.
+ if (gComposeType == Ci.nsIMsgCompType.Draft) {
+ let creatorKey = params.composeFields.creatorIdentityKey;
+ params.identity = creatorKey ? getIdentityForKey(creatorKey) : null;
+ }
+ let from = [];
+ if (params.composeFields.from)
+ from = MailServices.headerParser
+ .parseEncodedHeader(params.composeFields.from, null);
+ from = (from.length && from[0] && from[0].email) ?
+ from[0].email.toLowerCase().trim() : null;
+ if (!params.identity || !params.identity.email ||
+ (from && !emailSimilar(from, params.identity.email))) {
+ let identities = MailServices.accounts.allIdentities;
+ let suitableCount = 0;
+
+ // Search for a matching identity.
+ if (from) {
+ for (let ident of identities) {
+ if (ident.email && from == ident.email.toLowerCase()) {
+ if (suitableCount == 0)
+ params.identity = ident;
+ suitableCount++;
+ if (suitableCount > 1)
+ break; // No need to find more, it's already not unique.
+ }
+ }
+ }
+
+ if (!params.identity || !params.identity.email) {
+ let identity = null;
+ // No preset identity and no match, so use the default account.
+ let defaultAccount = MailServices.accounts.defaultAccount;
+ if (defaultAccount) {
+ identity = defaultAccount.defaultIdentity;
+ }
+ if (!identity) {
+ // Get the first identity we have in the list.
+ let identitykey = identityList.getItemAtIndex(0).getAttribute("identitykey");
+ identity = MailServices.accounts.getIdentity(identitykey);
+ }
+ params.identity = identity;
+ }
+
+ // Warn if no or more than one match was found.
+ // But don't warn for +suffix additions (a+b@c.com).
+ if (from && (suitableCount > 1 ||
+ (suitableCount == 0 && !emailSimilar(from, params.identity.email))))
+ gComposeNotificationBar.setIdentityWarning(params.identity.identityName);
+ }
+
+ identityList.selectedItem =
+ identityList.getElementsByAttribute("identitykey", params.identity.key)[0];
+ if (params.composeFields.from)
+ identityList.value = MailServices.headerParser.parseDecodedHeader(params.composeFields.from)[0].toString();
+ LoadIdentity(true);
+
+ // Get the <editor> element to startup an editor
+ var editorElement = GetCurrentEditorElement();
+
+ // Remember the original message URI. When editing a draft which is a reply
+ // or forwarded message, this gets overwritten by the ancestor's message URI
+ // so the disposition flags ("replied" or "forwarded") can be set on the
+ // ancestor.
+ // For our purposes we need the URI of the message being processed, not its
+ // original ancestor.
+ gOriginalMsgURI = params.originalMsgURI;
+ gMsgCompose = MailServices.compose.initCompose(params, window,
+ editorElement.docShell);
+
+ document.getElementById("returnReceiptMenu")
+ .setAttribute("checked", gMsgCompose.compFields.returnReceipt);
+ document.getElementById("dsnMenu")
+ .setAttribute('checked', gMsgCompose.compFields.DSN);
+ document.getElementById("cmd_attachVCard")
+ .setAttribute("checked", gMsgCompose.compFields.attachVCard);
+ document.getElementById("menu_inlineSpellCheck")
+ .setAttribute("checked",
+ Services.prefs.getBoolPref("mail.spellcheck.inline"));
+
+ let editortype = gMsgCompose.composeHTML ? "htmlmail" : "textmail";
+ editorElement.makeEditable(editortype, true);
+
+ // setEditorType MUST be call before setContentWindow
+ if (gMsgCompose.composeHTML) {
+ initLocalFontFaceMenu(document.getElementById("FontFacePopup"));
+ } else {
+ //Remove HTML toolbar, format and insert menus as we are editing in plain
+ //text mode.
+ let toolbar = document.getElementById("FormatToolbar");
+ toolbar.hidden = true;
+ toolbar.setAttribute("hideinmenu", "true");
+ document.getElementById("outputFormatMenu").setAttribute("hidden", true);
+ document.getElementById("formatMenu").setAttribute("hidden", true);
+ document.getElementById("insertMenu").setAttribute("hidden", true);
+ }
+
+ // Do setup common to Message Composer and Web Composer.
+ EditorSharedStartup();
+
+ if (params.bodyIsLink) {
+ let body = gMsgCompose.compFields.body;
+ if (gMsgCompose.composeHTML) {
+ let cleanBody;
+ try {
+ cleanBody = decodeURI(body);
+ } catch(e) {
+ cleanBody = body;
+ }
+
+ body = body.replace(/&/g, "&amp;");
+ gMsgCompose.compFields.body =
+ "<br /><a href=\"" + body + "\">" + cleanBody + "</a><br />";
+ } else {
+ gMsgCompose.compFields.body = "\n<" + body + ">\n";
+ }
+ }
+
+ GetMsgSubjectElement().value = gMsgCompose.compFields.subject;
+
+ var attachments = gMsgCompose.compFields.attachments;
+ while (attachments.hasMoreElements()) {
+ AddAttachment(attachments.getNext().QueryInterface(Ci.nsIMsgAttachment));
+ }
+
+ var event = document.createEvent('Events');
+ event.initEvent('compose-window-init', false, true);
+ document.getElementById("msgcomposeWindow").dispatchEvent(event);
+
+ gMsgCompose.RegisterStateListener(stateListener);
+
+ // Add an observer to be called when document is done loading,
+ // which creates the editor.
+ try {
+ GetCurrentCommandManager().addCommandObserver(gMsgEditorCreationObserver,
+ "obs_documentCreated");
+
+ // Load empty page to create the editor
+ editorElement.webNavigation.loadURI("about:blank",
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, // referrer
+ null, // post-data stream
+ null, // HTTP headers
+ Services.scriptSecurityManager.getSystemPrincipal());
+ } catch (e) {
+ dump(" Failed to startup editor: "+e+"\n");
+ }
+
+ // create URI of the folder from draftId
+ var draftId = gMsgCompose.compFields.draftId;
+ var folderURI = draftId.substring(0, draftId.indexOf("#")).replace("-message", "");
+
+ try {
+ var folder = sRDF.GetResource(folderURI);
+
+ gEditingDraft = (folder instanceof Ci.nsIMsgFolder) &&
+ (folder.flags & Ci.nsMsgFolderFlags.Drafts);
+ }
+ catch (ex) {
+ gEditingDraft = false;
+ }
+
+ gAutoSaveKickedIn = false;
+
+ gAutoSaveInterval = Services.prefs.getBoolPref("mail.compose.autosave")
+ ? Services.prefs.getIntPref("mail.compose.autosaveinterval") * 60000
+ : 0;
+
+ if (gAutoSaveInterval)
+ gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
+}
+
+function splitEmailAddress(aEmail) {
+ let at = aEmail.lastIndexOf("@");
+ return (at != -1) ? [aEmail.slice(0, at), aEmail.slice(at + 1)]
+ : [aEmail, ""];
+}
+
+// Emails are equal ignoring +suffixes (email+suffix@example.com).
+function emailSimilar(a, b) {
+ if (!a || !b)
+ return a == b;
+ a = splitEmailAddress(a.toLowerCase());
+ b = splitEmailAddress(b.toLowerCase());
+ return a[1] == b[1] && a[0].split("+", 1)[0] == b[0].split("+", 1)[0];
+}
+
+// The new, nice, simple way of getting notified when a new editor has been created
+var gMsgEditorCreationObserver =
+{
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (aTopic == "obs_documentCreated")
+ {
+ var editor = GetCurrentEditor();
+ var commandManager = GetCurrentCommandManager();
+ if (editor && commandManager == aSubject) {
+ let editorStyle = editor.QueryInterface(Ci.nsIEditorStyleSheets);
+ // We use addOverrideStyleSheet rather than addStyleSheet so that we get
+ // a synchronous load, rather than having a late-finishing async load
+ // mark our editor as modified when the user hasn't typed anything yet,
+ // but that means the sheet must not @import slow things, especially
+ // not over the network.
+ editorStyle.addOverrideStyleSheet("chrome://messenger/skin/messageQuotes.css");
+ InitEditor(editor);
+ }
+ // Now that we know this document is an editor, update commands now if
+ // the document has focus, or next time it receives focus via
+ // CommandUpdate_MsgCompose()
+ if (gLastWindowToHaveFocus == document.commandDispatcher.focusedWindow)
+ updateComposeItems();
+ else
+ gLastWindowToHaveFocus = null;
+ }
+ }
+}
+
+function WizCallback(state)
+{
+ if (state){
+ ComposeStartup(null);
+ }
+ else
+ {
+ // The account wizard is still closing so we can't close just yet
+ setTimeout(MsgComposeCloseWindow, 0);
+ }
+}
+
+function ComposeLoad()
+{
+ sComposeMsgsBundle = document.getElementById("bundle_composeMsgs");
+ sBrandBundle = document.getElementById("brandBundle");
+
+ var otherHeaders = Services.prefs.getCharPref("mail.compose.other.header");
+
+ sRDF = Cc['@mozilla.org/rdf/rdf-service;1']
+ .getService(Ci.nsIRDFService);
+ sNameProperty = sRDF.GetResource("http://home.netscape.com/NC-rdf#Name?sort=true");
+
+ AddMessageComposeOfflineQuitObserver();
+
+ if (gLogComposePerformance)
+ MailServices.compose.TimeStamp("Start initializing the compose window (ComposeLoad)", false);
+
+ msgWindow.notificationCallbacks = new nsMsgBadCertHandler();
+
+ try {
+ SetupCommandUpdateHandlers();
+ // This will do migration, or create a new account if we need to.
+ // We also want to open the account wizard if no identities are found
+ var state = verifyAccounts(WizCallback, true);
+
+ if (otherHeaders) {
+ var selectNode = document.getElementById('addressCol1#1');
+ var otherHeaders_Array = otherHeaders.split(",");
+ for (let i = 0; i < otherHeaders_Array.length; i++)
+ selectNode.appendItem(otherHeaders_Array[i] + ":", "addr_other");
+ }
+ if (state)
+ ComposeStartup(null);
+ }
+ catch (ex) {
+ Cu.reportError(ex);
+ var errorTitle = sComposeMsgsBundle.getString("initErrorDlogTitle");
+ var errorMsg = sComposeMsgsBundle.getString("initErrorDlgMessage");
+ Services.prompt.alert(window, errorTitle, errorMsg);
+
+ MsgComposeCloseWindow();
+ return;
+ }
+ if (gLogComposePerformance)
+ MailServices.compose.TimeStamp("Done with the initialization (ComposeLoad). Waiting on editor to load about:blank", false);
+
+ // Before and after callbacks for the customizeToolbar code
+ var mailToolbox = getMailToolbox();
+ mailToolbox.customizeInit = MailToolboxCustomizeInit;
+ mailToolbox.customizeDone = MailToolboxCustomizeDone;
+ mailToolbox.customizeChange = MailToolboxCustomizeChange;
+}
+
+function ComposeUnload()
+{
+ // Send notification that the window is going away completely.
+ document.getElementById("msgcomposeWindow").dispatchEvent(
+ new Event("compose-window-unload", { bubbles: false, cancelable: false }));
+
+ GetCurrentCommandManager().removeCommandObserver(gMsgEditorCreationObserver,
+ "obs_documentCreated");
+ UnloadCommandUpdateHandlers();
+
+ // Stop InlineSpellCheckerUI so personal dictionary is saved
+ EnableInlineSpellCheck(false);
+
+ EditorCleanup();
+
+ RemoveMessageComposeOfflineQuitObserver();
+
+ if (gMsgCompose)
+ gMsgCompose.UnregisterStateListener(stateListener);
+ if (gAutoSaveTimeout)
+ clearTimeout(gAutoSaveTimeout);
+ if (msgWindow) {
+ msgWindow.closeWindow();
+ msgWindow.notificationCallbacks = null;
+ }
+
+ ReleaseGlobalVariables();
+}
+
+function ComposeSetCharacterSet(aEvent)
+{
+ if (gMsgCompose)
+ SetDocumentCharacterSet(aEvent.target.getAttribute("charset"));
+ else
+ dump("Compose has not been created!\n");
+}
+
+function SetDocumentCharacterSet(aCharset)
+{
+ // Replace generic Japanese with ISO-2022-JP.
+ if (aCharset == "Japanese") {
+ aCharset = "ISO-2022-JP";
+ }
+ gMsgCompose.SetDocumentCharset(aCharset);
+ SetComposeWindowTitle();
+}
+
+function GetCharsetUIString()
+{
+ // The charset here is already the canonical charset (not an alias).
+ let charset = gMsgCompose.compFields.characterSet;
+ if (!charset)
+ return "";
+
+ if (charset.toLowerCase() != gMsgCompose.compFields.defaultCharacterSet.toLowerCase()) {
+ try {
+ return " - " + gCharsetConvertManager.getCharsetTitle(charset);
+ }
+ catch(e) { // Not a canonical charset after all...
+ Cu.reportError("Not charset title for charset=" + charset);
+ return " - " + charset;
+ }
+ }
+ return "";
+}
+
+// Add-ons can override this to customize the behavior.
+function DoSpellCheckBeforeSend()
+{
+ return Services.prefs.getBoolPref("mail.SpellCheckBeforeSend");
+}
+
+/**
+ * Handles message sending operations.
+ * @param msgType nsIMsgCompDeliverMode of the operation.
+ */
+function GenericSendMessage(msgType) {
+ var msgCompFields = gMsgCompose.compFields;
+
+ Recipients2CompFields(msgCompFields);
+ var address = GetMsgIdentityElement().value;
+ address = MailServices.headerParser.makeFromDisplayAddress(address);
+ msgCompFields.from = MailServices.headerParser.makeMimeHeader([address[0]]);
+ var subject = GetMsgSubjectElement().value;
+ msgCompFields.subject = subject;
+ Attachments2CompFields(msgCompFields);
+
+ if (msgType == Ci.nsIMsgCompDeliverMode.Now ||
+ msgType == Ci.nsIMsgCompDeliverMode.Later ||
+ msgType == Ci.nsIMsgCompDeliverMode.Background) {
+ //Do we need to check the spelling?
+ if (DoSpellCheckBeforeSend()) {
+ // We disable spellcheck for the following -subject line, attachment
+ // pane, identity and addressing widget therefore we need to explicitly
+ // focus on the mail body when we have to do a spellcheck.
+ SetMsgBodyFrameFocus();
+ window.cancelSendMessage = false;
+ window.openDialog("chrome://editor/content/EdSpellCheck.xul", "_blank",
+ "dialog,close,titlebar,modal,resizable",
+ true, true, false);
+ if (window.cancelSendMessage)
+ return;
+ }
+
+ // Strip trailing spaces and long consecutive WSP sequences from the
+ // subject line to prevent getting only WSP chars on a folded line.
+ var fixedSubject = subject.replace(/\s{74,}/g, " ")
+ .replace(/\s*$/, "");
+ if (fixedSubject != subject) {
+ subject = fixedSubject;
+ msgCompFields.subject = fixedSubject;
+ GetMsgSubjectElement().value = fixedSubject;
+ }
+
+ // Remind the person if there isn't a subject.
+ if (subject == "") {
+ if (Services.prompt.confirmEx(
+ window,
+ sComposeMsgsBundle.getString("subjectEmptyTitle"),
+ sComposeMsgsBundle.getString("subjectEmptyMessage"),
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING *
+ Services.prompt.BUTTON_POS_1),
+ sComposeMsgsBundle.getString("sendWithEmptySubjectButton"),
+ sComposeMsgsBundle.getString("cancelSendingButton"),
+ null, null, {value:0}) == 1) {
+ GetMsgSubjectElement().focus();
+ return;
+ }
+ }
+
+ // Check if the user tries to send a message to a newsgroup through a mail
+ // account.
+ var currentAccountKey = getCurrentAccountKey();
+ var account = MailServices.accounts.getAccount(currentAccountKey);
+ if (!account) {
+ throw "UNEXPECTED: currentAccountKey '" + currentAccountKey +
+ "' has no matching account!";
+ }
+
+ if (account.incomingServer.type != "nntp" &&
+ msgCompFields.newsgroups != "") {
+ const kDontAskAgainPref = "mail.compose.dontWarnMail2Newsgroup";
+ // Default to ask user if the pref is not set.
+ var dontAskAgain = Services.prefs.getBoolPref(kDontAskAgainPref);
+ if (!dontAskAgain) {
+ var checkbox = {value:false};
+ var okToProceed = Services.prompt.confirmCheck(
+ window,
+ sComposeMsgsBundle.getString("noNewsgroupSupportTitle"),
+ sComposeMsgsBundle.getString("recipientDlogMessage"),
+ sComposeMsgsBundle.getString("CheckMsg"),
+ checkbox);
+
+ if (!okToProceed)
+ return;
+ }
+ if (checkbox.value)
+ Services.prefs.setBoolPref(kDontAskAgainPref, true);
+
+ // Remove newsgroups to prevent news_p to be set
+ // in nsMsgComposeAndSend::DeliverMessage()
+ msgCompFields.newsgroups = "";
+ }
+
+ // Before sending the message, check what to do with HTML message,
+ // eventually abort.
+ var convert = DetermineConvertibility();
+ var action = DetermineHTMLAction(convert);
+ // Check if e-mail addresses are complete, in case user has turned off
+ // autocomplete to local domain.
+ if (!CheckValidEmailAddress(msgCompFields.to, msgCompFields.cc, msgCompFields.bcc))
+ return;
+
+ if (action == Ci.nsIMsgCompSendFormat.AskUser) {
+ var recommAction = (convert == Ci.nsIMsgCompConvertible.No)
+ ? Ci.nsIMsgCompSendFormat.AskUser
+ : Ci.nsIMsgCompSendFormat.PlainText;
+ var result2 = {action:recommAction, convertible:convert, abort:false};
+ window.openDialog("chrome://messenger/content/messengercompose/askSendFormat.xul",
+ "askSendFormatDialog", "chrome,modal,titlebar,centerscreen",
+ result2);
+ if (result2.abort)
+ return;
+ action = result2.action;
+ }
+
+ // We will remember the users "send format" decision in the address
+ // collector code (see nsAbAddressCollector::CollectAddress())
+ // by using msgCompFields.forcePlainText and
+ // msgCompFields.useMultipartAlternative to determine the
+ // nsIAbPreferMailFormat (unknown, plaintext, or html).
+ // If the user sends both, we remember html.
+ switch (action) {
+ case Ci.nsIMsgCompSendFormat.PlainText:
+ msgCompFields.forcePlainText = true;
+ msgCompFields.useMultipartAlternative = false;
+ break;
+ case Ci.nsIMsgCompSendFormat.HTML:
+ msgCompFields.forcePlainText = false;
+ msgCompFields.useMultipartAlternative = false;
+ break;
+ case Ci.nsIMsgCompSendFormat.Both:
+ msgCompFields.forcePlainText = false;
+ msgCompFields.useMultipartAlternative = true;
+ break;
+ default:
+ throw new Error("Invalid nsIMsgCompSendFormat action; action=" + action);
+ }
+ }
+
+ // Hook for extra compose pre-processing.
+ Services.obs.notifyObservers(window, "mail:composeOnSend");
+
+ var originalCharset = gMsgCompose.compFields.characterSet;
+ // Check if the headers of composing mail can be converted to a mail charset.
+ if (msgType == Ci.nsIMsgCompDeliverMode.Now ||
+ msgType == Ci.nsIMsgCompDeliverMode.Later ||
+ msgType == Ci.nsIMsgCompDeliverMode.Background ||
+ msgType == Ci.nsIMsgCompDeliverMode.Save ||
+ msgType == Ci.nsIMsgCompDeliverMode.SaveAsDraft ||
+ msgType == Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft ||
+ msgType == Ci.nsIMsgCompDeliverMode.SaveAsTemplate) {
+ var fallbackCharset = new Object;
+ // Check encoding, switch to UTF-8 if the default encoding doesn't fit
+ // and disable_fallback_to_utf8 isn't set for this encoding.
+ if (!gMsgCompose.checkCharsetConversion(getCurrentIdentity(),
+ fallbackCharset)) {
+ let disableFallback = Services.prefs
+ .getBoolPref("mailnews.disable_fallback_to_utf8." + originalCharset, false);
+ if (disableFallback)
+ msgCompFields.needToCheckCharset = false;
+ else
+ fallbackCharset.value = "UTF-8";
+ }
+
+ if (fallbackCharset &&
+ fallbackCharset.value && fallbackCharset.value != "")
+ gMsgCompose.SetDocumentCharset(fallbackCharset.value);
+ }
+ try {
+ // Just before we try to send the message, fire off the
+ // compose-send-message event for listeners such as smime so they can do
+ // any pre-security work such as fetching certificates before sending.
+ var event = document.createEvent('UIEvents');
+ event.initEvent('compose-send-message', false, true);
+ var msgcomposeWindow = document.getElementById("msgcomposeWindow");
+ msgcomposeWindow.setAttribute("msgtype", msgType);
+ msgcomposeWindow.dispatchEvent(event);
+ if (event.defaultPrevented)
+ throw Cr.NS_ERROR_ABORT;
+
+ gAutoSaving = (msgType == Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ if (!gAutoSaving) {
+ // Disable the ui if we're not auto-saving.
+ gWindowLocked = true;
+ disableEditableFields();
+ updateComposeItems();
+ } else {
+ // If we're auto saving, mark the body as not changed here, and not
+ // when the save is done, because the user might change it between now
+ // and when the save is done.
+ SetContentAndBodyAsUnmodified();
+ }
+
+ var progress = Cc["@mozilla.org/messenger/progress;1"]
+ .createInstance(Ci.nsIMsgProgress);
+ if (progress) {
+ progress.registerListener(progressListener);
+ gSendOrSaveOperationInProgress = true;
+ }
+ msgWindow.domWindow = window;
+ msgWindow.rootDocShell.allowAuth = true;
+ gMsgCompose.SendMsg(msgType, getCurrentIdentity(), getCurrentAccountKey(),
+ msgWindow, progress);
+ }
+ catch (ex) {
+ Cu.reportError("GenericSendMessage FAILED: " + ex);
+ gWindowLocked = false;
+ enableEditableFields();
+ updateComposeItems();
+ }
+ if (gMsgCompose && originalCharset != gMsgCompose.compFields.characterSet)
+ SetDocumentCharacterSet(gMsgCompose.compFields.characterSet);
+}
+
+/**
+ * Check if the given address is valid (contains a @).
+ *
+ * @param aAddress The address string to check.
+ */
+function isValidAddress(aAddress) {
+ return (aAddress.includes("@", 1) && !aAddress.endsWith("@"));
+}
+
+/**
+ * Keep the Send buttons disabled until any recipient is entered.
+ */
+function updateSendLock() {
+ gSendLocked = true;
+ if (!gMsgCompose)
+ return;
+
+ // Helper function to check for a valid list name.
+ function isValidListName(aInput) {
+ let listNames = MimeParser.parseHeaderField(aInput,
+ MimeParser.HEADER_ADDRESS);
+ return listNames.length > 0 &&
+ MailServices.ab.mailListNameExists(listNames[0].name);
+ }
+
+ const mailTypes = [ "addr_to", "addr_cc", "addr_bcc" ];
+
+ // Enable the send buttons if anything usable was entered into at least one
+ // recipient field.
+ for (let row = 1; row <= top.MAX_RECIPIENTS; row ++) {
+ let popupValue = awGetPopupElement(row).value;
+ let inputValue = awGetInputElement(row).value.trim();
+ // Check for a valid looking email address or a valid mailing list name
+ // from one of our addressbooks.
+ if ((mailTypes.includes(popupValue) &&
+ (isValidAddress(inputValue) || isValidListName(inputValue))) ||
+ ((popupValue == "addr_newsgroups") && (inputValue != ""))) {
+ gSendLocked = false;
+ break;
+ }
+ }
+}
+
+function CheckValidEmailAddress(aTo, aCC, aBCC)
+{
+ var invalidStr = null;
+ // crude check that the to, cc, and bcc fields contain at least one '@'.
+ // We could parse each address, but that might be overkill.
+ if (aTo.length > 0 && (aTo.indexOf("@") <= 0 && aTo.toLowerCase() != "postmaster" || aTo.indexOf("@") == aTo.length - 1))
+ invalidStr = aTo;
+ else if (aCC.length > 0 && (aCC.indexOf("@") <= 0 && aCC.toLowerCase() != "postmaster" || aCC.indexOf("@") == aCC.length - 1))
+ invalidStr = aCC;
+ else if (aBCC.length > 0 && (aBCC.indexOf("@") <= 0 && aBCC.toLowerCase() != "postmaster" || aBCC.indexOf("@") == aBCC.length - 1))
+ invalidStr = aBCC;
+ if (invalidStr)
+ {
+ var errorTitle = sComposeMsgsBundle.getString("addressInvalidTitle");
+ var errorMsg = sComposeMsgsBundle.getFormattedString("addressInvalid", [invalidStr], 1);
+ Services.prompt.alert(window, errorTitle, errorMsg);
+ return false;
+ }
+ return true;
+}
+
+function SendMessage()
+{
+ let sendInBackground = Services.prefs.getBoolPref("mailnews.sendInBackground");
+ if (sendInBackground && AppConstants.platform != "macosx") {
+ let enumerator = Services.wm.getEnumerator(null);
+ let count = 0;
+ while (enumerator.hasMoreElements() && count < 2)
+ {
+ enumerator.getNext();
+ count++;
+ }
+ if (count == 1)
+ sendInBackground = false;
+ }
+ GenericSendMessage(sendInBackground ? nsIMsgCompDeliverMode.Background
+ : nsIMsgCompDeliverMode.Now);
+}
+
+function SendMessageWithCheck()
+{
+ var warn = Services.prefs.getBoolPref("mail.warn_on_send_accel_key");
+
+ if (warn) {
+ var checkValue = {value:false};
+ var buttonPressed = Services.prompt.confirmEx(window,
+ sComposeMsgsBundle.getString('sendMessageCheckWindowTitle'),
+ sComposeMsgsBundle.getString('sendMessageCheckLabel'),
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
+ sComposeMsgsBundle.getString('sendMessageCheckSendButtonLabel'),
+ null, null,
+ sComposeMsgsBundle.getString('CheckMsg'),
+ checkValue);
+ if (buttonPressed != 0) {
+ return;
+ }
+ if (checkValue.value) {
+ Services.prefs.setBoolPref("mail.warn_on_send_accel_key", false);
+ }
+ }
+
+ if (Services.io.offline)
+ SendMessageLater();
+ else
+ SendMessage();
+}
+
+function SendMessageLater()
+{
+ GenericSendMessage(nsIMsgCompDeliverMode.Later);
+}
+
+function Save()
+{
+ switch (defaultSaveOperation)
+ {
+ case "file" : SaveAsFile(false); break;
+ case "template" : SaveAsTemplate(false); break;
+ default : SaveAsDraft(false); break;
+ }
+}
+
+function SaveAsFile(saveAs)
+{
+ var subject = GetMsgSubjectElement().value;
+ GetCurrentEditorElement().contentDocument.title = subject;
+
+ if (gMsgCompose.bodyConvertible() == nsIMsgCompConvertible.Plain)
+ SaveDocument(saveAs, false, "text/plain");
+ else
+ SaveDocument(saveAs, false, "text/html");
+ defaultSaveOperation = "file";
+}
+
+function SaveAsDraft()
+{
+ GenericSendMessage(nsIMsgCompDeliverMode.SaveAsDraft);
+ defaultSaveOperation = "draft";
+
+ gAutoSaveKickedIn = false;
+ gEditingDraft = true;
+}
+
+function SaveAsTemplate()
+{
+ let savedReferences = null;
+ if (gMsgCompose && gMsgCompose.compFields) {
+ // Clear References header. When we use the template, we don't want that
+ // header, yet, "edit as new message" maintains it. So we need to clear
+ // it when saving the template.
+ // Note: The In-Reply-To header is the last entry in the references header,
+ // so it will get cleared as well.
+ savedReferences = gMsgCompose.compFields.references;
+ gMsgCompose.compFields.references = null;
+ }
+
+ GenericSendMessage(nsIMsgCompDeliverMode.SaveAsTemplate);
+ defaultSaveOperation = "template";
+
+ if (savedReferences)
+ gMsgCompose.compFields.references = savedReferences;
+
+ gAutoSaveKickedIn = false;
+ gEditingDraft = false;
+}
+
+// Sets the additional FCC, in addition to the default FCC.
+function MessageFcc(aFolder) {
+ if (!gMsgCompose)
+ return;
+
+ var msgCompFields = gMsgCompose.compFields;
+ if (!msgCompFields)
+ return;
+
+ // Get the uri for the folder to FCC into.
+ var fccURI = aFolder.URI;
+ msgCompFields.fcc2 = (msgCompFields.fcc2 == fccURI) ? "nocopy://" : fccURI;
+}
+
+function updatePriorityMenu(priorityMenu)
+{
+ var priority = (gMsgCompose && gMsgCompose.compFields && gMsgCompose.compFields.priority) || "Normal";
+ priorityMenu.getElementsByAttribute("value", priority)[0].setAttribute("checked", "true");
+}
+
+function PriorityMenuSelect(target)
+{
+ if (gMsgCompose)
+ {
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ msgCompFields.priority = target.getAttribute("value");
+ }
+}
+
+function OutputFormatMenuSelect(target)
+{
+ if (gMsgCompose)
+ {
+ var msgCompFields = gMsgCompose.compFields;
+ var toolbar = document.getElementById("FormatToolbar");
+ var format_menubar = document.getElementById("formatMenu");
+ var insert_menubar = document.getElementById("insertMenu");
+
+ if (msgCompFields)
+ switch (target.getAttribute('id'))
+ {
+ case "format_auto": gSendFormat = nsIMsgCompSendFormat.AskUser; break;
+ case "format_plain": gSendFormat = nsIMsgCompSendFormat.PlainText; break;
+ case "format_html": gSendFormat = nsIMsgCompSendFormat.HTML; break;
+ case "format_both": gSendFormat = nsIMsgCompSendFormat.Both; break;
+ }
+ gHideMenus = (gSendFormat == nsIMsgCompSendFormat.PlainText);
+ format_menubar.hidden = gHideMenus;
+ insert_menubar.hidden = gHideMenus;
+ if (gHideMenus) {
+ gFormatToolbarHidden = toolbar.hidden;
+ toolbar.hidden = true;
+ toolbar.setAttribute("hideinmenu", "true");
+ } else {
+ toolbar.hidden = gFormatToolbarHidden;
+ toolbar.removeAttribute("hideinmenu");
+ }
+ }
+}
+
+function SelectAddress()
+{
+ var msgCompFields = gMsgCompose.compFields;
+
+ Recipients2CompFields(msgCompFields);
+
+ var toAddress = msgCompFields.to;
+ var ccAddress = msgCompFields.cc;
+ var bccAddress = msgCompFields.bcc;
+
+ dump("toAddress: " + toAddress + "\n");
+ window.openDialog("chrome://messenger/content/addressbook/abSelectAddressesDialog.xul",
+ "",
+ "chrome,resizable,titlebar,modal",
+ {composeWindow:top.window,
+ msgCompFields:msgCompFields,
+ toAddress:toAddress,
+ ccAddress:ccAddress,
+ bccAddress:bccAddress});
+ // We have to set focus to the addressingwidget because we seem to loose focus often
+ // after opening the SelectAddresses Dialog- bug # 89950
+ AdjustFocus();
+}
+
+// walk through the recipients list and add them to the inline spell checker ignore list
+function addRecipientsToIgnoreList(aAddressesToAdd)
+{
+ if (InlineSpellCheckerUI.enabled)
+ {
+ // break the list of potentially many recipients back into individual names
+ var emailAddresses = {};
+ var names = {};
+ var fullNames = {};
+ var numAddresses =
+ MailServices.headerParser.parseHeadersWithArray(aAddressesToAdd,
+ emailAddresses, names,
+ fullNames);
+ var tokenizedNames = [];
+
+ // each name could consist of multiple words delimited by commas and/or spaces.
+ // i.e. Green Lantern or Lantern,Green.
+ for (let i = 0; i < names.value.length; i++)
+ {
+ if (!names.value[i])
+ continue;
+ var splitNames = names.value[i].match(/[^\s,]+/g);
+ if (splitNames)
+ tokenizedNames = tokenizedNames.concat(splitNames);
+ }
+
+ if (InlineSpellCheckerUI.mInlineSpellChecker.spellCheckPending)
+ {
+ // spellchecker is enabled, but we must wait for its init to complete
+ Services.obs.addObserver(function observe(subject, topic, data) {
+ if (subject == gMsgCompose.editor)
+ {
+ Services.obs.removeObserver(observe, topic);
+ InlineSpellCheckerUI.mInlineSpellChecker.ignoreWords(tokenizedNames);
+ }
+ }, "inlineSpellChecker-spellCheck-ended");
+ }
+ else
+ {
+ InlineSpellCheckerUI.mInlineSpellChecker.ignoreWords(tokenizedNames);
+ }
+ }
+}
+
+function onAddressColCommand(aWidgetId) {
+ gContentChanged = true;
+ awSetAutoComplete(aWidgetId.slice(aWidgetId.lastIndexOf('#') + 1));
+ updateSendCommands(true);
+}
+
+/**
+ * Called if the list of recipients changed in any way.
+ *
+ * @param aAutomatic Set to true if the change of recipients was invoked
+ * programatically and should not be considered a change
+ * of message content.
+ */
+function onRecipientsChanged(aAutomatic) {
+ if (!aAutomatic) {
+ gContentChanged = true;
+ setupAutocomplete();
+ }
+ updateSendCommands(true);
+}
+
+function InitLanguageMenu()
+{
+ var languageMenuList = document.getElementById("languageMenuList");
+ if (!languageMenuList)
+ return;
+
+ var spellChecker = Cc["@mozilla.org/spellchecker/engine;1"]
+ .getService(mozISpellCheckingEngine);
+ // Get the list of dictionaries from the spellchecker.
+ var dictList = spellChecker.getDictionaryList();
+ var count = dictList.length;
+
+ // If dictionary count hasn't changed then no need to update the menu.
+ if (sDictCount == count)
+ return;
+
+ // Store current dictionary count.
+ sDictCount = count;
+
+ // Load the language string bundle that will help us map
+ // RFC 1766 strings to UI strings.
+ var languageBundle = document.getElementById("languageBundle");
+ var isoStrArray;
+ var langId;
+ var langLabel;
+
+ for (let i = 0; i < count; i++)
+ {
+ try
+ {
+ langId = dictList[i];
+ isoStrArray = dictList[i].split(/[-_]/);
+
+ if (languageBundle && isoStrArray[0])
+ langLabel = languageBundle.getString(isoStrArray[0].toLowerCase());
+
+ // the user needs to be able to distinguish between the UK English dictionary
+ // and say the United States English Dictionary. If we have a isoStr value then
+ // wrap it in parentheses and append it to the menu item string. i.e.
+ // English (US) and English (UK)
+ if (!langLabel)
+ langLabel = langId;
+ // if we have a language ID like US or UK, append it to the menu item, and any sub-variety
+ else if (isoStrArray.length > 1 && isoStrArray[1]) {
+ langLabel += ' (' + isoStrArray[1];
+ if (isoStrArray.length > 2 && isoStrArray[2])
+ langLabel += '-' + isoStrArray[2];
+ langLabel += ')';
+ }
+ }
+ catch (ex)
+ {
+ // getString throws an exception when a key is not found in the
+ // bundle. In that case, just use the original dictList string.
+ langLabel = langId;
+ }
+ dictList[i] = [langLabel, langId];
+ }
+
+ // sort by locale-aware collation
+ dictList.sort(
+ function compareFn(a, b)
+ {
+ return a[0].localeCompare(b[0]);
+ }
+ );
+
+ // Remove any languages from the list.
+ while (languageMenuList.hasChildNodes())
+ languageMenuList.lastChild.remove();
+
+ for (let i = 0; i < count; i++)
+ {
+ var item = document.createElement("menuitem");
+ item.setAttribute("label", dictList[i][0]);
+ item.setAttribute("value", dictList[i][1]);
+ item.setAttribute("type", "radio");
+ languageMenuList.appendChild(item);
+ }
+}
+
+function OnShowDictionaryMenu(aTarget)
+{
+ InitLanguageMenu();
+ var spellChecker = InlineSpellCheckerUI.mInlineSpellChecker.spellChecker;
+ var curLang = spellChecker.GetCurrentDictionary();
+ var languages = aTarget.getElementsByAttribute("value", curLang);
+ if (languages.length > 0)
+ languages[0].setAttribute("checked", true);
+}
+
+function ChangeLanguage(event)
+{
+ // We need to change the dictionary language and if we are using inline spell check,
+ // recheck the message
+ var spellChecker = InlineSpellCheckerUI.mInlineSpellChecker.spellChecker;
+ if (spellChecker.GetCurrentDictionary() != event.target.value)
+ {
+ spellChecker.SetCurrentDictionary(event.target.value);
+
+ ComposeChangeLanguage(event.target.value)
+ }
+ event.stopPropagation();
+}
+
+function ComposeChangeLanguage(aLang)
+{
+ if (document.documentElement.getAttribute("lang") != aLang) {
+
+ // Update the document language as well.
+ // This is needed to synchronize the subject.
+ document.documentElement.setAttribute("lang", aLang);
+
+ // Update spellchecker pref
+ Services.prefs.setCharPref("spellchecker.dictionary", aLang);
+
+ // Now check the document and the subject over again with the new
+ // dictionary.
+ if (InlineSpellCheckerUI.enabled) {
+ InlineSpellCheckerUI.mInlineSpellChecker.spellCheckRange(null);
+
+ // Also force a recheck of the subject. The spell checker for the subject
+ // isn't always ready yet. Usually throws unless the subject was selected
+ // at least once. So don't auto-create it, hence pass 'false'.
+ let inlineSpellChecker =
+ GetMsgSubjectElement().editor.getInlineSpellChecker(false);
+ if (inlineSpellChecker) {
+ inlineSpellChecker.spellCheckRange(null);
+ }
+ }
+ }
+}
+
+function ToggleReturnReceipt(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ {
+ msgCompFields.returnReceipt = ! msgCompFields.returnReceipt;
+ target.setAttribute('checked', msgCompFields.returnReceipt);
+ gReceiptOptionChanged = true;
+ }
+}
+
+function ToggleDSN(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+
+ if (msgCompFields)
+ {
+ msgCompFields.DSN = !msgCompFields.DSN;
+ target.setAttribute('checked', msgCompFields.DSN);
+ gDSNOptionChanged = true;
+ }
+}
+
+function ToggleAttachVCard(target)
+{
+ var msgCompFields = gMsgCompose.compFields;
+ if (msgCompFields)
+ {
+ msgCompFields.attachVCard = ! msgCompFields.attachVCard;
+ target.setAttribute('checked', msgCompFields.attachVCard);
+ gAttachVCardOptionChanged = true;
+ }
+}
+
+function FillIdentityList(menulist)
+{
+ var accounts = FolderUtils.allAccountsSorted(true);
+
+ for (let acc = 0; acc < accounts.length; acc++)
+ {
+ let account = accounts[acc];
+ let identities = account.identities;
+
+ if (identities.length == 0)
+ continue;
+
+ for (let i = 0; i < identities.length; i++)
+ {
+ let identity = identities[i];
+ let item = menulist.appendItem(identity.identityName,
+ identity.fullAddress,
+ account.incomingServer.prettyName);
+ item.setAttribute("identitykey", identity.key);
+ item.setAttribute("accountkey", account.key);
+ if (i == 0)
+ {
+ // Mark the first identity as default.
+ item.setAttribute("default", "true");
+ }
+ }
+ }
+}
+
+function getCurrentAccountKey()
+{
+ // get the accounts key
+ var identityList = GetMsgIdentityElement();
+ return identityList.selectedItem.getAttribute("accountkey");
+}
+
+function getCurrentIdentityKey()
+{
+ // get the identity key
+ var identityList = GetMsgIdentityElement();
+ return identityList.selectedItem.getAttribute("identitykey");
+}
+
+function getIdentityForKey(key)
+{
+ return MailServices.accounts.getIdentity(key);
+}
+
+function getCurrentIdentity()
+{
+ return getIdentityForKey(getCurrentIdentityKey());
+}
+
+function AdjustFocus()
+{
+ let element = awGetInputElement(awGetNumberOfRecipients());
+ if (element.value == "") {
+ awSetFocusTo(element);
+ }
+ else
+ {
+ element = GetMsgSubjectElement();
+ if (element.value == "") {
+ element.focus();
+ }
+ else {
+ SetMsgBodyFrameFocus();
+ }
+ }
+}
+
+function SetComposeWindowTitle()
+{
+ var newTitle = GetMsgSubjectElement().value;
+
+ if (newTitle == "" )
+ newTitle = sComposeMsgsBundle.getString("defaultSubject");
+
+ newTitle += GetCharsetUIString();
+ document.title = sComposeMsgsBundle.getString("windowTitlePrefix") + " " + newTitle;
+}
+
+// Check for changes to document and allow saving before closing
+// This is hooked up to the OS's window close widget (e.g., "X" for Windows)
+function ComposeCanClose()
+{
+ if (gSendOrSaveOperationInProgress)
+ {
+ var brandShortName = sBrandBundle.getString("brandShortName");
+
+ var promptTitle = sComposeMsgsBundle.getString("quitComposeWindowTitle");
+ var promptMsg = sComposeMsgsBundle.getFormattedString("quitComposeWindowMessage2",
+ [brandShortName], 1);
+ var quitButtonLabel = sComposeMsgsBundle.getString("quitComposeWindowQuitButtonLabel2");
+ var waitButtonLabel = sComposeMsgsBundle.getString("quitComposeWindowWaitButtonLabel2");
+
+ if (Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1),
+ waitButtonLabel, quitButtonLabel, null, null, {value:0}) == 1)
+ {
+ gMsgCompose.abort();
+ return true;
+ }
+ return false;
+ }
+
+ // Returns FALSE only if user cancels save action
+ if (gContentChanged || gMsgCompose.bodyModified || (gAutoSaveKickedIn && !gEditingDraft))
+ {
+ // call window.focus, since we need to pop up a dialog
+ // and therefore need to be visible (to prevent user confusion)
+ window.focus();
+ let draftFolderURI = gCurrentIdentity.draftFolder;
+ let draftFolderName = MailUtils.getFolderForURI(draftFolderURI).prettyName;
+ switch (Services.prompt.confirmEx(window,
+ sComposeMsgsBundle.getString("saveDlogTitle"),
+ sComposeMsgsBundle.getFormattedString("saveDlogMessages3", [draftFolderName]),
+ (Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1) +
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2),
+ null,
+ null,
+ sComposeMsgsBundle.getString("discardButtonLabel"),
+ null, {value:0}))
+ {
+ case 0: //Save
+ // we can close immediately if we already autosaved the draft
+ if (!gContentChanged && !gMsgCompose.bodyModified)
+ break;
+ gCloseWindowAfterSave = true;
+ GenericSendMessage(nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ return false;
+ case 1: //Cancel
+ return false;
+ case 2: //Don't Save
+ // only delete the draft if we didn't start off editing a draft
+ if (!gEditingDraft && gAutoSaveKickedIn)
+ RemoveDraft();
+ break;
+ }
+ }
+
+ return true;
+}
+
+function RemoveDraft()
+{
+ try
+ {
+ var draftId = gMsgCompose.compFields.draftId;
+ var msgKey = draftId.substr(draftId.indexOf('#') + 1);
+ var folder = sRDF.GetResource(gMsgCompose.savedFolderURI);
+ try {
+ if (folder instanceof Ci.nsIMsgFolder)
+ {
+ let msg = folder.GetMessageHeader(msgKey);
+ folder.deleteMessages([msg], null, true, false, null, false);
+ }
+ }
+ catch (ex) // couldn't find header - perhaps an imap folder.
+ {
+ if (folder instanceof Ci.nsIMsgImapMailFolder)
+ {
+ const kImapMsgDeletedFlag = 0x0008;
+ folder.storeImapFlags(kImapMsgDeletedFlag, true, [msgKey], null);
+ }
+ }
+ } catch (ex) {}
+}
+
+function SetContentAndBodyAsUnmodified()
+{
+ gMsgCompose.bodyModified = false;
+ gContentChanged = false;
+}
+
+function MsgComposeCloseWindow()
+{
+ if (gMsgCompose)
+ gMsgCompose.CloseWindow();
+ else
+ window.close();
+}
+
+// attachedLocalFile must be a nsIFile
+function SetLastAttachDirectory(attachedLocalFile)
+{
+ try {
+ var file = attachedLocalFile.QueryInterface(Ci.nsIFile);
+ var parent = file.parent.QueryInterface(Ci.nsIFile);
+
+ Services.prefs.setComplexValue(kComposeAttachDirPrefName,
+ Ci.nsIFile, parent);
+ }
+ catch (ex) {
+ dump("error: SetLastAttachDirectory failed: " + ex + "\n");
+ }
+}
+
+function AttachFile()
+{
+ //Get file using nsIFilePicker and convert to URL
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+ fp.init(window, sComposeMsgsBundle.getString("chooseFileToAttach"),
+ nsIFilePicker.modeOpenMultiple);
+ let lastDirectory = GetLocalFilePref(kComposeAttachDirPrefName);
+ if (lastDirectory)
+ fp.displayDirectory = lastDirectory;
+
+ fp.appendFilters(nsIFilePicker.filterAll);
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.files) {
+ return;
+ }
+ try {
+ let firstAttachedFile = AttachFiles(fp.files);
+ if (firstAttachedFile) {
+ SetLastAttachDirectory(firstAttachedFile);
+ }
+ }
+ catch (ex) {
+ dump("failed to get attachments: " + ex + "\n");
+ }
+ });
+}
+
+function AttachFiles(attachments)
+{
+ if (!attachments || !attachments.hasMoreElements())
+ return null;
+
+ var firstAttachedFile = null;
+
+ while (attachments.hasMoreElements()) {
+ var currentFile = attachments.getNext().QueryInterface(Ci.nsIFile);
+
+ if (!firstAttachedFile) {
+ firstAttachedFile = currentFile;
+ }
+
+ var fileHandler = Services.io.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+ var currentAttachment = fileHandler.getURLSpecFromFile(currentFile);
+
+ if (!DuplicateFileCheck(currentAttachment)) {
+ var attachment = Cc["@mozilla.org/messengercompose/attachment;1"].createInstance(Ci.nsIMsgAttachment);
+ attachment.url = currentAttachment;
+ attachment.size = currentFile.fileSize;
+ AddAttachment(attachment);
+ gContentChanged = true;
+ }
+ }
+ return firstAttachedFile;
+}
+
+function AddAttachment(attachment)
+{
+ if (attachment && attachment.url)
+ {
+ var bucket = GetMsgAttachmentElement();
+ var item = document.createElement("listitem");
+
+ if (!attachment.name)
+ attachment.name = gMsgCompose.AttachmentPrettyName(attachment.url, attachment.urlCharset);
+
+ // for security reasons, don't allow *-message:// uris to leak out
+ // we don't want to reveal the .slt path (for mailbox://), or the username or hostname
+ var messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
+ if (messagePrefix.test(attachment.name))
+ attachment.name = sComposeMsgsBundle.getString("messageAttachmentSafeName");
+ else {
+ // for security reasons, don't allow mail protocol uris to leak out
+ // we don't want to reveal the .slt path (for mailbox://), or the username or hostname
+ var mailProtocol = /^file:|^mailbox:|^imap:|^s?news:/i;
+ if (mailProtocol.test(attachment.name))
+ attachment.name = sComposeMsgsBundle.getString("partAttachmentSafeName");
+ }
+
+ var nameAndSize = attachment.name;
+ if (attachment.size != -1)
+ nameAndSize += " (" + gMessenger.formatFileSize(attachment.size) + ")";
+ item.setAttribute("label", nameAndSize); //use for display only
+ item.attachment = attachment; //full attachment object stored here
+ try {
+ item.setAttribute("tooltiptext", decodeURI(attachment.url));
+ } catch(e) {
+ item.setAttribute("tooltiptext", attachment.url);
+ }
+ item.setAttribute("class", "listitem-iconic");
+ item.setAttribute("image", "moz-icon:" + attachment.url);
+ item.setAttribute("crop", "center");
+ bucket.appendChild(item);
+ }
+}
+
+function SelectAllAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ if (bucketList)
+ bucketList.selectAll();
+}
+
+function MessageHasAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ if (bucketList) {
+ return (bucketList && bucketList.hasChildNodes() && (bucketList == top.document.commandDispatcher.focusedElement));
+ }
+ return false;
+}
+
+function MessageGetNumSelectedAttachments()
+{
+ var bucketList = GetMsgAttachmentElement();
+ return (bucketList) ? bucketList.selectedItems.length : 0;
+}
+
+function AttachPage()
+{
+ var params = { action: "5", url: null };
+ window.openDialog("chrome://communicator/content/openLocation.xul",
+ "_blank", "chrome,close,titlebar,modal", params);
+ if (params.url)
+ {
+ var attachment =
+ Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+ attachment.url = params.url;
+ AddAttachment(attachment);
+ }
+}
+
+function DuplicateFileCheck(FileUrl)
+{
+ var bucket = GetMsgAttachmentElement();
+ for (let i = 0; i < bucket.childNodes.length; i++)
+ {
+ let attachment = bucket.childNodes[i].attachment;
+ if (attachment)
+ {
+ if (FileUrl == attachment.url)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function Attachments2CompFields(compFields)
+{
+ var bucket = GetMsgAttachmentElement();
+
+ //First, we need to clear all attachment in the compose fields
+ compFields.removeAttachments();
+
+ for (let i = 0; i < bucket.childNodes.length; i++)
+ {
+ let attachment = bucket.childNodes[i].attachment;
+ if (attachment)
+ compFields.addAttachment(attachment);
+ }
+}
+
+function RemoveAllAttachments()
+{
+ var child;
+ var bucket = GetMsgAttachmentElement();
+ while (bucket.hasChildNodes())
+ {
+ child = bucket.removeChild(bucket.lastChild);
+ // Let's release the attachment object hold by the node else it won't go away until the window is destroyed
+ child.attachment = null;
+ }
+}
+
+function RemoveSelectedAttachment()
+{
+ var child;
+ var bucket = GetMsgAttachmentElement();
+ if (bucket.selectedItems.length > 0) {
+ for (let i = bucket.selectedItems.length - 1; i >= 0; i--)
+ {
+ child = bucket.removeChild(bucket.selectedItems[i]);
+ // Let's release the attachment object hold by the node else it won't go away until the window is destroyed
+ child.attachment = null;
+ }
+ gContentChanged = true;
+ }
+}
+
+function RenameSelectedAttachment()
+{
+ var bucket = GetMsgAttachmentElement();
+ if (bucket.selectedItems.length != 1)
+ return; // not one attachment selected
+
+ var item = bucket.getSelectedItem(0);
+ var attachmentName = {value: item.attachment.name};
+ if (Services.prompt.prompt(
+ window,
+ sComposeMsgsBundle.getString("renameAttachmentTitle"),
+ sComposeMsgsBundle.getString("renameAttachmentMessage"),
+ attachmentName,
+ null,
+ {value: 0}))
+ {
+ var modifiedAttachmentName = attachmentName.value;
+ if (modifiedAttachmentName == "")
+ return; // name was not filled, bail out
+
+ var nameAndSize = modifiedAttachmentName;
+ if (item.attachment.size != -1)
+ nameAndSize += " (" + gMessenger.formatFileSize(item.attachment.size) + ")";
+ item.label = nameAndSize;
+ item.attachment.name = modifiedAttachmentName;
+ gContentChanged = true;
+ }
+}
+
+function FocusOnFirstAttachment()
+{
+ var bucketList = GetMsgAttachmentElement();
+
+ if (bucketList && bucketList.hasChildNodes())
+ bucketList.selectItem(bucketList.firstChild);
+}
+
+function AttachmentElementHasItems()
+{
+ var element = GetMsgAttachmentElement();
+ return element ? element.childNodes.length : 0;
+}
+
+function OpenSelectedAttachment()
+{
+ let bucket = document.getElementById("attachmentBucket");
+ if (bucket.selectedItems.length == 1) {
+ let attachmentUrl = bucket.getSelectedItem(0).attachment.url;
+
+ let messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
+ if (messagePrefix.test(attachmentUrl)) {
+ // We must be dealing with a forwarded attachment, treat this special.
+ let msgHdr = gMessenger.msgHdrFromURI(attachmentUrl);
+ if (msgHdr) {
+ MailUtils.openMessageInNewWindow(msgHdr);
+ }
+ } else {
+ // Turn the URL into a nsIURI object then open it.
+ let uri = Services.io.newURI(attachmentUrl);
+ if (uri) {
+ let channel = Services.io.newChannelFromURI(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ if (channel) {
+ let uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
+ uriLoader.openURI(channel, true, new nsAttachmentOpener());
+ }
+ }
+ }
+ } // if one attachment selected
+}
+
+function nsAttachmentOpener()
+{
+}
+
+nsAttachmentOpener.prototype =
+{
+ QueryInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIURIContentListener) ||
+ iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ doContent: function(contentType, isContentPreferred, request, contentHandler)
+ {
+ return false;
+ },
+
+ isPreferred: function(contentType, desiredContentType)
+ {
+ return false;
+ },
+
+ canHandleContent: function(contentType, isContentPreferred, desiredContentType)
+ {
+ return false;
+ },
+
+ getInterface: function(iid)
+ {
+ if (iid.equals(Ci.nsIDOMWindow)) {
+ return window;
+ }
+
+ if (iid.equals(Ci.nsIDocShell)) {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ }
+
+ return this.QueryInterface(iid);
+ },
+
+ loadCookie: null,
+ parentContentListener: null
+}
+
+function DetermineHTMLAction(convertible)
+{
+ try {
+ gMsgCompose.expandMailingLists();
+ } catch(ex) {
+ dump("gMsgCompose.expandMailingLists failed: " + ex + "\n");
+ }
+
+ if (!gMsgCompose.composeHTML)
+ {
+ return nsIMsgCompSendFormat.PlainText;
+ }
+
+ if (gSendFormat == nsIMsgCompSendFormat.AskUser)
+ {
+ return gMsgCompose.determineHTMLAction(convertible);
+ }
+
+ return gSendFormat;
+}
+
+function DetermineConvertibility()
+{
+ if (!gMsgCompose.composeHTML)
+ return nsIMsgCompConvertible.Plain;
+
+ try {
+ return gMsgCompose.bodyConvertible();
+ } catch(ex) {}
+ return nsIMsgCompConvertible.No;
+}
+
+function LoadIdentity(startup)
+{
+ var identityElement = GetMsgIdentityElement();
+ var prevIdentity = gCurrentIdentity;
+
+ if (identityElement) {
+ identityElement.value = identityElement.selectedItem.value;
+
+ var idKey = identityElement.selectedItem.getAttribute("identitykey");
+ gCurrentIdentity = MailServices.accounts.getIdentity(idKey);
+
+ let accountKey = null;
+ if (identityElement.selectedItem)
+ accountKey = identityElement.selectedItem.getAttribute("accountkey");
+
+ let maxRecipients = awGetMaxRecipients();
+ for (let i = 1; i <= maxRecipients; i++)
+ {
+ let params = JSON.parse(awGetInputElement(i).searchParam);
+ params.idKey = idKey;
+ params.accountKey = accountKey;
+ awGetInputElement(i).searchParam = JSON.stringify(params);
+ }
+
+ if (!startup && prevIdentity && idKey != prevIdentity.key)
+ {
+ var prevReplyTo = prevIdentity.replyTo;
+ var prevCc = "";
+ var prevBcc = "";
+ var prevReceipt = prevIdentity.requestReturnReceipt;
+ var prevDSN = prevIdentity.requestDSN;
+ var prevAttachVCard = prevIdentity.attachVCard;
+
+ if (prevIdentity.doCc)
+ prevCc += prevIdentity.doCcList;
+
+ if (prevIdentity.doBcc)
+ prevBcc += prevIdentity.doBccList;
+
+ var newReplyTo = gCurrentIdentity.replyTo;
+ var newCc = "";
+ var newBcc = "";
+ var newReceipt = gCurrentIdentity.requestReturnReceipt;
+ var newDSN = gCurrentIdentity.requestDSN;
+ var newAttachVCard = gCurrentIdentity.attachVCard;
+
+ if (gCurrentIdentity.doCc)
+ newCc += gCurrentIdentity.doCcList;
+
+ if (gCurrentIdentity.doBcc)
+ newBcc += gCurrentIdentity.doBccList;
+
+ var needToCleanUp = false;
+ var msgCompFields = gMsgCompose.compFields;
+
+ if (!gReceiptOptionChanged &&
+ prevReceipt == msgCompFields.returnReceipt &&
+ prevReceipt != newReceipt)
+ {
+ msgCompFields.returnReceipt = newReceipt;
+ document.getElementById("returnReceiptMenu").setAttribute('checked',msgCompFields.returnReceipt);
+ }
+
+ if (!gDSNOptionChanged &&
+ prevDSN == msgCompFields.DSN &&
+ prevDSN != newDSN)
+ {
+ msgCompFields.DSN = newDSN;
+ document.getElementById("dsnMenu").setAttribute('checked',msgCompFields.DSN);
+ }
+
+ if (!gAttachVCardOptionChanged &&
+ prevAttachVCard == msgCompFields.attachVCard &&
+ prevAttachVCard != newAttachVCard)
+ {
+ msgCompFields.attachVCard = newAttachVCard;
+ document.getElementById("cmd_attachVCard").setAttribute('checked',msgCompFields.attachVCard);
+ }
+
+ if (newReplyTo != prevReplyTo)
+ {
+ needToCleanUp = true;
+ if (prevReplyTo != "")
+ awRemoveRecipients(msgCompFields, "addr_reply", prevReplyTo);
+ if (newReplyTo != "")
+ awAddRecipients(msgCompFields, "addr_reply", newReplyTo);
+ }
+
+ let toAddrs = new Set(msgCompFields.splitRecipients(msgCompFields.to, true));
+ let ccAddrs = new Set(msgCompFields.splitRecipients(msgCompFields.cc, true));
+
+ if (newCc != prevCc)
+ {
+ needToCleanUp = true;
+ if (prevCc)
+ awRemoveRecipients(msgCompFields, "addr_cc", prevCc);
+ if (newCc) {
+ // Ensure none of the Ccs are already in To.
+ let cc2 = msgCompFields.splitRecipients(newCc, true);
+ newCc = cc2.filter(x => !toAddrs.has(x)).join(", ");
+ awAddRecipients(msgCompFields, "addr_cc", newCc);
+ }
+ }
+
+ if (newBcc != prevBcc)
+ {
+ needToCleanUp = true;
+ if (prevBcc)
+ awRemoveRecipients(msgCompFields, "addr_bcc", prevBcc);
+ if (newBcc) {
+ // Ensure none of the Bccs are already in To or Cc.
+ let bcc2 = msgCompFields.splitRecipients(newBcc, true);
+ let toCcAddrs = new Set([...toAddrs, ...ccAddrs]);
+ newBcc = bcc2.filter(x => !toCcAddrs.has(x)).join(", ");
+ awAddRecipients(msgCompFields, "addr_bcc", newBcc);
+ }
+ }
+
+ if (needToCleanUp)
+ awCleanupRows();
+
+ try {
+ gMsgCompose.identity = gCurrentIdentity;
+ } catch (ex) { dump("### Cannot change the identity: " + ex + "\n");}
+
+ var event = document.createEvent('Events');
+ event.initEvent('compose-from-changed', false, true);
+ document.getElementById("msgcomposeWindow").dispatchEvent(event);
+
+ gComposeNotificationBar.clearIdentityWarning();
+ }
+
+ if (!startup) {
+ if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches"))
+ document.getElementById('addressCol2#1').highlightNonMatches = true;
+
+ // Only do this if we aren't starting up...
+ // It gets done as part of startup already.
+ addRecipientsToIgnoreList(gCurrentIdentity.fullAddress);
+ }
+ }
+}
+
+function setupAutocomplete()
+{
+ var autoCompleteWidget = document.getElementById("addressCol2#1");
+
+ // if the pref is set to turn on the comment column, honor it here.
+ // this element then gets cloned for subsequent rows, so they should
+ // honor it as well
+ //
+ if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches"))
+ autoCompleteWidget.highlightNonMatches = true;
+
+ if (Services.prefs.getIntPref("mail.autoComplete.commentColumn", 0) != 0)
+ autoCompleteWidget.showCommentColumn = true;
+}
+
+function subjectKeyPress(event)
+{
+ switch(event.keyCode) {
+ case KeyEvent.DOM_VK_TAB:
+ if (!event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {
+ SetMsgBodyFrameFocus();
+ event.preventDefault();
+ }
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ SetMsgBodyFrameFocus();
+ break;
+ }
+}
+
+function AttachmentBucketClicked(event)
+{
+ if (event.button != 0)
+ return;
+
+ if (event.originalTarget.localName == "listboxbody")
+ goDoCommand('cmd_attachFile');
+ else if (event.originalTarget.localName == "listitem" && event.detail == 2)
+ OpenSelectedAttachment();
+}
+
+// Content types supported in the attachmentBucketObserver.
+let flavours = [ "text/x-moz-message", "application/x-moz-file",
+ "text/x-moz-url", ];
+
+var attachmentBucketObserver = {
+ onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ let dataList = [];
+ for (let i = 0; i < dt.mozItemCount; i++) {
+ let types = Array.from(dt.mozTypesAt(i));
+ for (let flavour of flavours) {
+ if (types.includes(flavour)) {
+ let data = dt.mozGetDataAt(flavour, i);
+ if (data) {
+ dataList.push({ data, flavour });
+ }
+ break;
+ }
+ }
+ }
+
+ for (let { data, flavour } of dataList) {
+ let isValidAttachment = false;
+ let prettyName;
+ let size;
+
+ // We could be dropping an attachment of various flavours;
+ // check and do the right thing.
+ switch (flavour) {
+ case "application/x-moz-file": {
+ if (data instanceof Ci.nsIFile) {
+ size = data.fileSize;
+ }
+
+ try {
+ data = Services.io.getProtocolHandler("file")
+ .QueryInterface(Ci.nsIFileProtocolHandler)
+ .getURLSpecFromFile(data);
+ isValidAttachment = true;
+ } catch (e) {
+ Cu.reportError("Couldn't process the dragged file " +
+ data.leafName + ":" + e);
+ }
+ break;
+ }
+
+ case "text/x-moz-message": {
+ isValidAttachment = true;
+ let msgHdr = gMessenger.messageServiceFromURI(data)
+ .messageURIToMsgHdr(data);
+ prettyName = msgHdr.mime2DecodedSubject + ".eml";
+ size = msgHdr.messageSize;
+ break;
+ }
+
+ case "text/x-moz-url": {
+ let pieces = data.split("\n");
+ data = pieces[0];
+ if (pieces.length > 1) {
+ prettyName = pieces[1];
+ }
+ if (pieces.length > 2) {
+ size = parseInt(pieces[2]);
+ }
+
+ // If this is a URL (or selected text), check if it's a valid URL
+ // by checking if we can extract a scheme using Services.io.
+ // Don't attach invalid or mailto: URLs.
+ try {
+ let scheme = Services.io.extractScheme(data);
+ if (scheme != "mailto") {
+ isValidAttachment = true;
+ }
+ } catch (ex) {}
+ break;
+ }
+ }
+
+ if (isValidAttachment && !DuplicateFileCheck(data)) {
+ let attachment = Cc["@mozilla.org/messengercompose/attachment;1"]
+ .createInstance(Ci.nsIMsgAttachment);
+ attachment.url = data;
+ attachment.name = prettyName;
+
+ if (size !== undefined) {
+ attachment.size = size;
+ }
+
+ AddAttachment(attachment);
+ }
+ }
+
+ aEvent.stopPropagation();
+ },
+
+ onDragOver(aEvent) {
+ let dragSession = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService).getCurrentSession();
+ for (let flavour of flavours) {
+ if (dragSession.isDataFlavorSupported(flavour)) {
+ let attachmentBucket = GetMsgAttachmentElement();
+ attachmentBucket.setAttribute("dragover", "true");
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ break;
+ }
+ }
+ },
+
+ onDragExit(aEvent) {
+ let attachmentBucket = GetMsgAttachmentElement();
+ attachmentBucket.removeAttribute("dragover");
+ },
+};
+
+function DisplaySaveFolderDlg(folderURI)
+{
+ try
+ {
+ var showDialog = gCurrentIdentity.showSaveMsgDlg;
+ }
+ catch (e)
+ {
+ return;
+ }
+
+ if (showDialog){
+ let msgfolder = MailUtils.getFolderForURI(folderURI, true);
+ if (!msgfolder)
+ return;
+ var checkbox = {value:0};
+ var SaveDlgTitle = sComposeMsgsBundle.getString("SaveDialogTitle");
+ var dlgMsg = sComposeMsgsBundle.getFormattedString("SaveDialogMsg",
+ [msgfolder.name,
+ msgfolder.server.prettyName]);
+
+ var CheckMsg = sComposeMsgsBundle.getString("CheckMsg");
+ Services.prompt.alertCheck(window, SaveDlgTitle, dlgMsg, CheckMsg, checkbox);
+ try {
+ gCurrentIdentity.showSaveMsgDlg = !checkbox.value;
+ }//try
+ catch (e) {
+ return;
+ }//catch
+
+ }//if
+ return;
+}
+
+function SetMsgAddressingWidgetElementFocus()
+{
+ awSetFocusTo(awGetInputElement(awGetNumberOfRecipients()));
+}
+
+function SetMsgIdentityElementFocus()
+{
+ GetMsgIdentityElement().focus();
+}
+
+function SetMsgSubjectElementFocus()
+{
+ GetMsgSubjectElement().focus();
+}
+
+function SetMsgAttachmentElementFocus()
+{
+ GetMsgAttachmentElement().focus();
+ FocusOnFirstAttachment();
+}
+
+function SetMsgBodyFrameFocus()
+{
+ //window.content.focus(); fails to blur the currently focused element
+ document.commandDispatcher
+ .advanceFocusIntoSubtree(document.getElementById("appcontent"));
+}
+
+function GetMsgAddressingWidgetElement()
+{
+ if (!gMsgAddressingWidgetElement)
+ gMsgAddressingWidgetElement = document.getElementById("addressingWidget");
+
+ return gMsgAddressingWidgetElement;
+}
+
+function GetMsgIdentityElement()
+{
+ if (!gMsgIdentityElement)
+ gMsgIdentityElement = document.getElementById("msgIdentity");
+
+ return gMsgIdentityElement;
+}
+
+function GetMsgSubjectElement()
+{
+ if (!gMsgSubjectElement)
+ gMsgSubjectElement = document.getElementById("msgSubject");
+
+ return gMsgSubjectElement;
+}
+
+function GetMsgAttachmentElement()
+{
+ if (!gMsgAttachmentElement)
+ gMsgAttachmentElement = document.getElementById("attachmentBucket");
+
+ return gMsgAttachmentElement;
+}
+
+function GetMsgHeadersToolbarElement()
+{
+ if (!gMsgHeadersToolbarElement)
+ gMsgHeadersToolbarElement = document.getElementById("MsgHeadersToolbar");
+
+ return gMsgHeadersToolbarElement;
+}
+
+function IsMsgHeadersToolbarCollapsed()
+{
+ var element = GetMsgHeadersToolbarElement();
+ return element && element.collapsed;
+}
+
+function WhichElementHasFocus()
+{
+ var msgIdentityElement = GetMsgIdentityElement();
+ var msgAddressingWidgetElement = GetMsgAddressingWidgetElement();
+ var msgSubjectElement = GetMsgSubjectElement();
+ var msgAttachmentElement = GetMsgAttachmentElement();
+
+ if (top.document.commandDispatcher.focusedWindow == content)
+ return content;
+
+ var currentNode = top.document.commandDispatcher.focusedElement;
+ while (currentNode)
+ {
+ if (currentNode == msgIdentityElement ||
+ currentNode == msgAddressingWidgetElement ||
+ currentNode == msgSubjectElement ||
+ currentNode == msgAttachmentElement)
+ return currentNode;
+
+ currentNode = currentNode.parentNode;
+ }
+
+ return null;
+}
+
+// Function that performs the logic of switching focus from
+// one element to another in the mail compose window.
+// The default element to switch to when going in either
+// direction (shift or no shift key pressed), is the
+// AddressingWidgetElement.
+//
+// The only exception is when the MsgHeadersToolbar is
+// collapsed, then the focus will always be on the body of
+// the message.
+function SwitchElementFocus(event)
+{
+ var focusedElement = WhichElementHasFocus();
+
+ if (event && event.shiftKey)
+ {
+ if (IsMsgHeadersToolbarCollapsed())
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == gMsgAddressingWidgetElement)
+ SetMsgIdentityElementFocus();
+ else if (focusedElement == gMsgIdentityElement)
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == content)
+ {
+ // only set focus to the attachment element if there
+ // are any attachments.
+ if (AttachmentElementHasItems())
+ SetMsgAttachmentElementFocus();
+ else
+ SetMsgSubjectElementFocus();
+ }
+ else if (focusedElement == gMsgAttachmentElement)
+ SetMsgSubjectElementFocus();
+ else
+ SetMsgAddressingWidgetElementFocus();
+ }
+ else
+ {
+ if (IsMsgHeadersToolbarCollapsed())
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == gMsgAddressingWidgetElement)
+ SetMsgSubjectElementFocus();
+ else if (focusedElement == gMsgSubjectElement)
+ {
+ // only set focus to the attachment element if there
+ // are any attachments.
+ if (AttachmentElementHasItems())
+ SetMsgAttachmentElementFocus();
+ else
+ SetMsgBodyFrameFocus();
+ }
+ else if (focusedElement == gMsgAttachmentElement)
+ SetMsgBodyFrameFocus();
+ else if (focusedElement == content)
+ SetMsgIdentityElementFocus();
+ else
+ SetMsgAddressingWidgetElementFocus();
+ }
+}
+
+function loadHTMLMsgPrefs()
+{
+ var fontFace = Services.prefs.getStringPref("msgcompose.font_face", "");
+ doStatefulCommand("cmd_fontFace", fontFace);
+
+ var fontSize = Services.prefs.getCharPref("msgcompose.font_size", "");
+ if (fontSize)
+ EditorSetFontSize(fontSize);
+
+ var bodyElement = GetBodyElement();
+
+ var textColor = Services.prefs.getCharPref("msgcompose.text_color", "");
+ if (!bodyElement.hasAttribute("text") && textColor)
+ {
+ bodyElement.setAttribute("text", textColor);
+ gDefaultTextColor = textColor;
+ document.getElementById("cmd_fontColor").setAttribute("state", textColor);
+ onFontColorChange();
+ }
+
+ var bgColor = Services.prefs.getCharPref("msgcompose.background_color", "");
+ if (!bodyElement.hasAttribute("bgcolor") && bgColor)
+ {
+ bodyElement.setAttribute("bgcolor", bgColor);
+ gDefaultBackgroundColor = bgColor;
+ document.getElementById("cmd_backgroundColor").setAttribute("state", bgColor);
+ onBackgroundColorChange();
+ }
+}
+
+function AutoSave()
+{
+ if (gMsgCompose.editor && (gContentChanged || gMsgCompose.bodyModified) &&
+ !gSendOrSaveOperationInProgress)
+ {
+ GenericSendMessage(nsIMsgCompDeliverMode.AutoSaveAsDraft);
+ gAutoSaveKickedIn = true;
+ }
+ gAutoSaveTimeout = setTimeout(AutoSave, gAutoSaveInterval);
+}
+
+/**
+ * Helper function to remove a query part from a URL, so for example:
+ * ...?remove=xx&other=yy becomes ...?other=yy.
+ *
+ * @param aURL the URL from which to remove the query part
+ * @param aQuery the query part to remove
+ * @return the URL with the query part removed
+ */
+function removeQueryPart(aURL, aQuery)
+{
+ // Quick pre-check.
+ if (!aURL.includes(aQuery))
+ return aURL;
+
+ let indexQM = aURL.indexOf("?");
+ if (indexQM < 0)
+ return aURL;
+
+ let queryParts = aURL.substr(indexQM + 1).split("&");
+ let indexPart = queryParts.indexOf(aQuery);
+ if (indexPart < 0)
+ return aURL;
+ queryParts.splice(indexPart, 1);
+ return aURL.substr(0, indexQM + 1) + queryParts.join("&");
+}
+
+function InitEditor(editor)
+{
+ // Set the eEditorMailMask flag to avoid using content prefs for the spell
+ // checker, otherwise the dictionary setting in preferences is ignored and
+ // the dictionary is inconsistent between the subject and message body.
+ var eEditorMailMask = Ci.nsIEditor.eEditorMailMask;
+ editor.flags |= eEditorMailMask;
+ GetMsgSubjectElement().editor.flags |= eEditorMailMask;
+
+ // Control insertion of line breaks.
+ editor.returnInParagraphCreatesNewParagraph =
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph") ||
+ Services.prefs.getBoolPref("editor.CR_creates_new_p");
+ editor.document.execCommand("defaultparagraphseparator", false,
+ gMsgCompose.composeHTML &&
+ Services.prefs.getBoolPref("mail.compose.default_to_paragraph") ?
+ "p" : "br");
+
+ gMsgCompose.initEditor(editor, window.content);
+ InlineSpellCheckerUI.init(editor);
+ EnableInlineSpellCheck(Services.prefs.getBoolPref("mail.spellcheck.inline"));
+ document.getElementById("menu_inlineSpellCheck").setAttribute("disabled", !InlineSpellCheckerUI.canSpellCheck);
+
+ // Listen for spellchecker changes, set the document language to the
+ // dictionary picked by the user via the right-click menu in the editor.
+ document.addEventListener("spellcheck-changed", updateDocumentLanguage);
+
+ // XXX: the error event fires twice for each load. Why??
+ editor.document.body.addEventListener("error", function(event) {
+ if (event.target.localName != "img") {
+ return;
+ }
+
+ if (event.target.getAttribute("moz-do-not-send") == "true") {
+ return;
+ }
+
+ let src = event.target.src;
+
+ if (!src) {
+ return;
+ }
+
+ if (!/^file:/i.test(src)) {
+ // Check if this is a protocol that can fetch parts.
+ let protocol = src.substr(0, src.indexOf(":")).toLowerCase();
+ if (!(Services.io.getProtocolHandler(protocol) instanceof
+ Ci.nsIMsgMessageFetchPartService)) {
+ // Can't fetch parts, don't try to load.
+ return;
+ }
+ }
+
+ if (event.target.classList.contains("loading-internal")) {
+ // We're already loading this, or tried so unsuccesfully.
+ return;
+ }
+
+ if (gOriginalMsgURI) {
+ let msgSvc = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger)
+ .messageServiceFromURI(gOriginalMsgURI);
+ let originalMsgNeckoURI = msgSvc.getUrlForUri(gOriginalMsgURI);
+
+ if (src.startsWith(removeQueryPart(originalMsgNeckoURI.spec,
+ "type=application/x-message-display"))) {
+ // Reply/Forward/Edit Draft/Edit as New can contain references to
+ // images in the original message. Load those and make them data: URLs
+ // now.
+ event.target.classList.add("loading-internal");
+ try {
+ loadBlockedImage(src);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ }
+ }
+ else {
+ // Appears to reference a random message. Notify and keep blocking.
+ gComposeNotificationBar.setBlockedContent(src);
+ }
+ }
+ else {
+ // For file:, and references to parts of random messages, show the
+ // blocked content notification.
+ gComposeNotificationBar.setBlockedContent(src);
+ }
+ }, true);
+
+ // Convert mailnews URL back to data: URL.
+ let background = editor.document.body.background;
+ if (background && gOriginalMsgURI) {
+ // Check that background has the same URL as the message itself.
+ let msgSvc = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger)
+ .messageServiceFromURI(gOriginalMsgURI);
+ let originalMsgNeckoURI = msgSvc.getUrlForUri(gOriginalMsgURI);
+
+ if (background.startsWith(
+ removeQueryPart(originalMsgNeckoURI.spec,
+ "type=application/x-message-display"))) {
+ try {
+ editor.document.body.background = loadBlockedImage(background, true);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ }
+ }
+ }
+}
+
+/**
+ * The event listener for the "spellcheck-changed" event updates
+ * the document language.
+ */
+function updateDocumentLanguage(event)
+{
+ document.documentElement.setAttribute("lang", event.detail.dictionary);
+}
+
+function EnableInlineSpellCheck(aEnableInlineSpellCheck)
+{
+ InlineSpellCheckerUI.enabled = aEnableInlineSpellCheck;
+ GetMsgSubjectElement().setAttribute("spellcheck", aEnableInlineSpellCheck);
+}
+
+function getMailToolbox()
+{
+ return document.getElementById("compose-toolbox");
+}
+
+function MailToolboxCustomizeInit()
+{
+ if (document.commandDispatcher.focusedWindow == content)
+ window.focus();
+ disableEditableFields();
+ GetMsgHeadersToolbarElement().setAttribute("moz-collapsed", true);
+ document.getElementById("compose-toolbar-sizer").setAttribute("moz-collapsed", true);
+ document.getElementById("content-frame").setAttribute("moz-collapsed", true);
+ toolboxCustomizeInit("mail-menubar");
+}
+
+function MailToolboxCustomizeDone(aToolboxChanged)
+{
+ toolboxCustomizeDone("mail-menubar", getMailToolbox(), aToolboxChanged);
+ GetMsgHeadersToolbarElement().removeAttribute("moz-collapsed");
+ document.getElementById("compose-toolbar-sizer").removeAttribute("moz-collapsed");
+ document.getElementById("content-frame").removeAttribute("moz-collapsed");
+ enableEditableFields();
+ SetMsgBodyFrameFocus();
+}
+
+function MailToolboxCustomizeChange(aEvent)
+{
+ toolboxCustomizeChange(getMailToolbox(), aEvent);
+}
+
+/**
+ * Object to handle message related notifications that are showing in a
+ * notificationbox below the composed message content.
+ */
+var gComposeNotificationBar = {
+
+ get notificationBar() {
+ delete this.notificationBar;
+ return this.notificationBar = document.getElementById("attachmentNotificationBox");
+ },
+
+ setBlockedContent: function(aBlockedURI) {
+ let brandName = sBrandBundle.getString("brandShortName");
+ let buttonLabel = sComposeMsgsBundle.getString("blockedContentPrefLabel");
+ let buttonAccesskey = sComposeMsgsBundle.getString("blockedContentPrefAccesskey");
+
+ let buttons = [{
+ label: buttonLabel,
+ accessKey: buttonAccesskey,
+ popup: "blockedContentOptions",
+ callback: function(aNotification, aButton) {
+ return true; // keep notification open
+ }
+ }];
+
+ // The popup value is a space separated list of all the blocked urls.
+ let popup = document.getElementById("blockedContentOptions");
+ let urls = popup.value ? popup.value.split(" ") : [];
+ if (!urls.includes(aBlockedURI)) {
+ urls.push(aBlockedURI);
+ }
+ popup.value = urls.join(" ");
+
+ let msg = sComposeMsgsBundle.getFormattedString("blockedContentMessage",
+ [brandName, brandName]);
+ msg = PluralForm.get(urls.length, msg);
+
+ if (!this.isShowingBlockedContentNotification()) {
+ this.notificationBar
+ .appendNotification(msg, "blockedContent", null,
+ this.notificationBar.PRIORITY_WARNING_MEDIUM,
+ buttons);
+ }
+ else {
+ this.notificationBar.getNotificationWithValue("blockedContent")
+ .setAttribute("label", msg);
+ }
+ },
+
+ isShowingBlockedContentNotification: function() {
+ return !!this.notificationBar.getNotificationWithValue("blockedContent");
+ },
+
+ clearBlockedContentNotification: function() {
+ this.notificationBar.removeNotification(
+ this.notificationBar.getNotificationWithValue("blockedContent"));
+ },
+
+ clearNotifications: function(aValue) {
+ this.notificationBar.removeAllNotifications(true);
+ },
+
+ setIdentityWarning: function(aIdentityName) {
+ if (!this.notificationBar.getNotificationWithValue("identityWarning")) {
+ let text = sComposeMsgsBundle.getString("identityWarning").split("%S");
+ let label = new DocumentFragment();
+ label.appendChild(document.createTextNode(text[0]));
+ label.appendChild(document.createElement("b"));
+ label.lastChild.appendChild(document.createTextNode(aIdentityName));
+ label.appendChild(document.createTextNode(text[1]));
+ this.notificationBar.appendNotification(label, "identityWarning", null,
+ this.notificationBar.PRIORITY_WARNING_HIGH, null);
+ }
+ },
+
+ clearIdentityWarning: function() {
+ let idWarning = this.notificationBar.getNotificationWithValue("identityWarning");
+ if (idWarning)
+ this.notificationBar.removeNotification(idWarning);
+ }
+};
+
+/**
+ * Populate the menuitems of what blocked content to unblock.
+ */
+function onBlockedContentOptionsShowing(aEvent) {
+ let urls = aEvent.target.value ? aEvent.target.value.split(" ") : [];
+
+ // Out with the old...
+ let childNodes = aEvent.target.childNodes;
+ for (let i = childNodes.length - 1; i >= 0; i--) {
+ childNodes[i].remove();
+ }
+
+ // ... and in with the new.
+ for (let url of urls) {
+ let menuitem = document.createElement("menuitem");
+ let fString = sComposeMsgsBundle.getFormattedString("blockedAllowResource",
+ [url]);
+ menuitem.setAttribute("label", fString);
+ menuitem.setAttribute("crop", "center");
+ menuitem.setAttribute("value", url);
+ menuitem.setAttribute("oncommand",
+ "onUnblockResource(this.value, this.parentNode);");
+ aEvent.target.appendChild(menuitem);
+ }
+}
+
+/**
+ * Handle clicking the "Load <url>" in the blocked content notification bar.
+ * @param {String} aURL - the URL that was unblocked
+ * @param {Node} aNode - the node holding as value the URLs of the blocked
+ * resources in the message (space separated).
+ */
+function onUnblockResource(aURL, aNode) {
+ try {
+ loadBlockedImage(aURL);
+ } catch (e) {
+ // Couldn't load the referenced image.
+ Cu.reportError(e);
+ } finally {
+ // Remove it from the list on success and failure.
+ let urls = aNode.value.split(" ");
+ for (let i = 0; i < urls.length; i++) {
+ if (urls[i] == aURL) {
+ urls.splice(i, 1);
+ aNode.value = urls.join(" ");
+ if (urls.length == 0) {
+ gComposeNotificationBar.clearBlockedContentNotification();
+ }
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * Convert the blocked content to a data URL and swap the src to that for the
+ * elements that were using it.
+ *
+ * @param {String} aURL - (necko) URL to unblock
+ * @param {Bool} aReturnDataURL - return data: URL instead of processing image
+ * @return {String} the image as data: URL.
+ * @throw Error() if reading the data failed
+ */
+function loadBlockedImage(aURL, aReturnDataURL = false) {
+ let filename;
+ if (/^(file|chrome):/i.test(aURL)) {
+ filename = aURL.substr(aURL.lastIndexOf("/") + 1);
+ }
+ else {
+ let fnMatch = /[?&;]filename=([^?&]+)/.exec(aURL);
+ filename = (fnMatch && fnMatch[1]) || "";
+ }
+
+ filename = decodeURIComponent(filename);
+ let uri = Services.io.newURI(aURL);
+ let contentType;
+ if (filename) {
+ try {
+ contentType = Cc["@mozilla.org/mime;1"]
+ .getService(Ci.nsIMIMEService)
+ .getTypeFromURI(uri);
+ } catch (ex) {
+ contentType = "image/png";
+ }
+
+ if (!contentType.startsWith("image/")) {
+ // Unsafe to unblock this. It would just be garbage either way.
+ throw new Error("Won't unblock; URL=" + aURL +
+ ", contentType=" + contentType);
+ }
+ }
+ else {
+ // Assuming image/png is the best we can do.
+ contentType = "image/png";
+ }
+
+ let channel =
+ Services.io.newChannelFromURI(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+
+ let inputStream = channel.open();
+ let stream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(inputStream);
+ let streamData = "";
+ try {
+ while (stream.available() > 0) {
+ streamData += stream.readBytes(stream.available());
+ }
+ } catch(e) {
+ stream.close();
+ throw new Error("Couln't read all data from URL=" + aURL + " (" + e +")");
+ }
+ stream.close();
+
+ let encoded = btoa(streamData);
+ let dataURL = "data:" + contentType +
+ (filename ? ";filename=" + encodeURIComponent(filename) : "") +
+ ";base64," + encoded;
+
+ if (aReturnDataURL) {
+ return dataURL;
+ }
+
+ let editor = GetCurrentEditor();
+ for (let img of editor.document.images) {
+ if (img.src == aURL) {
+ img.src = dataURL; // Swap to data URL.
+ img.classList.remove("loading-internal");
+ }
+ }
+}
diff --git a/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js b/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js
new file mode 100644
index 0000000000..38cd1f5ecc
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js
@@ -0,0 +1,1167 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {MailServices} = ChromeUtils.import("resource:///modules/MailServices.jsm");
+
+top.MAX_RECIPIENTS = 1; /* for the initial listitem created in the XUL */
+
+var inputElementType = "";
+var selectElementType = "";
+var selectElementIndexTable = null;
+
+var gNumberOfCols = 0;
+
+var gDragService = Cc["@mozilla.org/widget/dragservice;1"]
+ .getService(Ci.nsIDragService);
+
+/**
+ * global variable inherited from MsgComposeCommands.js
+ *
+ var gMsgCompose;
+ */
+
+function awGetMaxRecipients()
+{
+ return top.MAX_RECIPIENTS;
+}
+
+function awGetNumberOfCols()
+{
+ if (gNumberOfCols == 0)
+ {
+ var listbox = document.getElementById('addressingWidget');
+ var listCols = listbox.getElementsByTagName('listcol');
+ gNumberOfCols = listCols.length;
+ if (!gNumberOfCols)
+ gNumberOfCols = 1; /* if no cols defined, that means we have only one! */
+ }
+
+ return gNumberOfCols;
+}
+
+function awInputElementName()
+{
+ if (inputElementType == "")
+ inputElementType = document.getElementById("addressCol2#1").localName;
+ return inputElementType;
+}
+
+function awSelectElementName()
+{
+ if (selectElementType == "")
+ selectElementType = document.getElementById("addressCol1#1").localName;
+ return selectElementType;
+}
+
+// TODO: replace awGetSelectItemIndex with recipient type index constants
+
+function awGetSelectItemIndex(itemData)
+{
+ if (selectElementIndexTable == null)
+ {
+ selectElementIndexTable = new Object();
+ var selectElem = document.getElementById("addressCol1#1");
+ for (var i = 0; i < selectElem.childNodes[0].childNodes.length; i ++)
+ {
+ var aData = selectElem.childNodes[0].childNodes[i].getAttribute("value");
+ selectElementIndexTable[aData] = i;
+ }
+ }
+ return selectElementIndexTable[itemData];
+}
+
+function Recipients2CompFields(msgCompFields)
+{
+ if (!msgCompFields) {
+ throw new Error("Message Compose Error: msgCompFields is null (ExtractRecipients)");
+ return;
+ }
+
+ var i = 1;
+ var addrTo = "";
+ var addrCc = "";
+ var addrBcc = "";
+ var addrReply = "";
+ var addrNg = "";
+ var addrFollow = "";
+ var to_Sep = "";
+ var cc_Sep = "";
+ var bcc_Sep = "";
+ var reply_Sep = "";
+ var ng_Sep = "";
+ var follow_Sep = "";
+
+ var recipientType;
+ var inputField;
+ var fieldValue;
+ var recipient;
+ while ((inputField = awGetInputElement(i)))
+ {
+ fieldValue = inputField.value;
+
+ if (fieldValue != "")
+ {
+ recipientType = awGetPopupElement(i).value;
+ recipient = null;
+
+ switch (recipientType)
+ {
+ case "addr_to" :
+ case "addr_cc" :
+ case "addr_bcc" :
+ case "addr_reply" :
+ try {
+ let headerParser = MailServices.headerParser;
+ recipient =
+ headerParser.makeFromDisplayAddress(fieldValue)
+ .map(fullValue => headerParser.makeMimeAddress(
+ fullValue.name,
+ fullValue.email))
+ .join(", ");
+ } catch (ex) {
+ recipient = fieldValue;
+ }
+ break;
+ }
+
+ switch (recipientType)
+ {
+ case "addr_to" : addrTo += to_Sep + recipient; to_Sep = ","; break;
+ case "addr_cc" : addrCc += cc_Sep + recipient; cc_Sep = ","; break;
+ case "addr_bcc" : addrBcc += bcc_Sep + recipient; bcc_Sep = ","; break;
+ case "addr_reply" : addrReply += reply_Sep + recipient; reply_Sep = ","; break;
+ case "addr_newsgroups" : addrNg += ng_Sep + fieldValue; ng_Sep = ","; break;
+ case "addr_followup" : addrFollow += follow_Sep + fieldValue; follow_Sep = ","; break;
+ case "addr_other":
+ let headerName = awGetPopupElement(i).label;
+ headerName = headerName.substring(0, headerName.indexOf(':'));
+ msgCompFields.setRawHeader(headerName, fieldValue, null);
+ break;
+ }
+ }
+ i ++;
+ }
+
+ msgCompFields.to = addrTo;
+ msgCompFields.cc = addrCc;
+ msgCompFields.bcc = addrBcc;
+ msgCompFields.replyTo = addrReply;
+ msgCompFields.newsgroups = addrNg;
+ msgCompFields.followupTo = addrFollow;
+}
+
+function CompFields2Recipients(msgCompFields)
+{
+ if (msgCompFields) {
+ var listbox = document.getElementById('addressingWidget');
+ var newListBoxNode = listbox.cloneNode(false);
+ var listBoxColsClone = listbox.firstChild.cloneNode(true);
+ newListBoxNode.appendChild(listBoxColsClone);
+ let templateNode = listbox.querySelector("listitem");
+ // dump("replacing child in comp fields 2 recips \n");
+ listbox.parentNode.replaceChild(newListBoxNode, listbox);
+
+ top.MAX_RECIPIENTS = 0;
+ var msgReplyTo = msgCompFields.replyTo;
+ var msgTo = msgCompFields.to;
+ var msgCC = msgCompFields.cc;
+ var msgBCC = msgCompFields.bcc;
+ var msgNewsgroups = msgCompFields.newsgroups;
+ var msgFollowupTo = msgCompFields.followupTo;
+ var havePrimaryRecipient = false;
+ if (msgReplyTo)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgReplyTo, false),
+ "addr_reply", newListBoxNode, templateNode);
+ if (msgTo) {
+ var rcp = msgCompFields.splitRecipients(msgTo, false);
+ if (rcp.length)
+ {
+ awSetInputAndPopupFromArray(rcp, "addr_to", newListBoxNode, templateNode);
+ havePrimaryRecipient = true;
+ }
+ }
+ if (msgCC)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgCC, false),
+ "addr_cc", newListBoxNode, templateNode);
+ if (msgBCC)
+ awSetInputAndPopupFromArray(msgCompFields.splitRecipients(msgBCC, false),
+ "addr_bcc", newListBoxNode, templateNode);
+ if (msgNewsgroups) {
+ awSetInputAndPopup(msgNewsgroups, "addr_newsgroups", newListBoxNode, templateNode);
+ havePrimaryRecipient = true;
+ }
+ if(msgFollowupTo)
+ awSetInputAndPopup(msgFollowupTo, "addr_followup", newListBoxNode, templateNode);
+
+ // If it's a new message, we need to add an extra empty recipient.
+ if (!havePrimaryRecipient)
+ _awSetInputAndPopup("", "addr_to", newListBoxNode, templateNode);
+ awFitDummyRows(2);
+
+ // CompFields2Recipients is called whenever a user replies or edits an existing message.
+ // We want to add all of the recipients for this message to the ignore list for spell check
+ let currentAddress = gCurrentIdentity ? gCurrentIdentity.fullAddress : "";
+ addRecipientsToIgnoreList([currentAddress,msgTo,msgCC,msgBCC].filter(adr => adr).join(", "));
+ }
+}
+
+function awSetInputAndPopupId(inputElem, popupElem, rowNumber)
+{
+ popupElem.id = "addressCol1#" + rowNumber;
+ inputElem.id = "addressCol2#" + rowNumber;
+ inputElem.setAttribute("aria-labelledby", popupElem.id);
+}
+
+function awSetInputAndPopupValue(inputElem, inputValue, popupElem, popupValue, rowNumber)
+{
+ inputElem.value = inputValue.trimLeft();
+
+ popupElem.selectedItem = popupElem.childNodes[0].childNodes[awGetSelectItemIndex(popupValue)];
+
+ if (rowNumber >= 0)
+ awSetInputAndPopupId(inputElem, popupElem, rowNumber);
+
+ _awSetAutoComplete(popupElem, inputElem);
+
+ onRecipientsChanged(true);
+}
+
+function _awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
+{
+ top.MAX_RECIPIENTS++;
+
+ var newNode = templateNode.cloneNode(true);
+ parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ var select = newNode.getElementsByTagName(awSelectElementName());
+
+ if (input && input.length == 1 && select && select.length == 1)
+ awSetInputAndPopupValue(input[0], inputValue, select[0], popupValue, top.MAX_RECIPIENTS)
+}
+
+function awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode)
+{
+ if ( inputValue && popupValue )
+ {
+ var addressArray = inputValue.split(",");
+
+ for ( var index = 0; index < addressArray.length; index++ )
+ _awSetInputAndPopup(addressArray[index], popupValue, parentNode, templateNode);
+ }
+}
+
+function awSetInputAndPopupFromArray(inputArray, popupValue, parentNode, templateNode)
+{
+ if (popupValue)
+ {
+ for (let recipient of inputArray)
+ _awSetInputAndPopup(recipient, popupValue, parentNode, templateNode);
+ }
+}
+
+function awRemoveRecipients(msgCompFields, recipientType, recipientsList)
+{
+ if (!msgCompFields)
+ return;
+
+ var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
+
+ for (var index = 0; index < recipientArray.length; index++)
+ for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
+ {
+ var popup = awGetPopupElement(row);
+ if (popup.value == recipientType) {
+ var input = awGetInputElement(row);
+ if (input.value == recipientArray[index])
+ {
+ awSetInputAndPopupValue(input, "", popup, "addr_to", -1);
+ break;
+ }
+ }
+ }
+}
+
+function awAddRecipients(msgCompFields, recipientType, recipientsList)
+{
+ if (!msgCompFields)
+ return;
+
+ var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
+
+ for (var index = 0; index < recipientArray.length; index++)
+ awAddRecipient(recipientType, recipientArray[index]);
+}
+
+// this was broken out of awAddRecipients so it can be re-used...adds a new row matching recipientType and
+// drops in the single address.
+function awAddRecipient(recipientType, address)
+{
+ for (var row = 1; row <= top.MAX_RECIPIENTS; row ++)
+ {
+ if (awGetInputElement(row).value == "")
+ break;
+ }
+
+ if (row > top.MAX_RECIPIENTS)
+ awAppendNewRow(false);
+
+ awSetInputAndPopupValue(awGetInputElement(row), address, awGetPopupElement(row), recipientType, row);
+
+ /* be sure we still have an empty row left at the end */
+ if (row == top.MAX_RECIPIENTS)
+ {
+ awAppendNewRow(true);
+ awSetInputAndPopupValue(awGetInputElement(top.MAX_RECIPIENTS), "", awGetPopupElement(top.MAX_RECIPIENTS), recipientType, top.MAX_RECIPIENTS);
+ }
+
+ // add the recipient to our spell check ignore list
+ addRecipientsToIgnoreList(address);
+}
+
+function awTestRowSequence()
+{
+ /*
+ This function is for debug and testing purpose only, normal users should not run it!
+
+ Everytime we insert or delete a row, we must be sure we didn't break the ID sequence of
+ the addressing widget rows. This function will run a quick test to see if the sequence still ok
+
+ You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
+ */
+
+ var test_sequence;
+ if (Services.prefs.getPrefType("mail.debug.test_addresses_sequence") == Ci.nsIPrefBranch.PREF_BOOL)
+ test_sequence = Services.prefs.getBoolPref("mail.debug.test_addresses_sequence");
+ if (!test_sequence)
+ return true;
+
+ /* debug code to verify the sequence still good */
+
+ var listbox = document.getElementById('addressingWidget');
+ var listitems = listbox.getElementsByTagName('listitem');
+ if (listitems.length >= top.MAX_RECIPIENTS )
+ {
+ for (var i = 1; i <= listitems.length; i ++)
+ {
+ var item = listitems [i - 1];
+ let inputID = item.querySelector(awInputElementName()).id.split("#")[1];
+ let popupID = item.querySelector(awSelectElementName()).id.split("#")[1];
+ if (inputID != i || popupID != i)
+ {
+ dump("#ERROR: sequence broken at row " + i + ", inputID=" + inputID + ", popupID=" + popupID + "\n");
+ return false;
+ }
+ dump("---SEQUENCE OK---\n");
+ return true;
+ }
+ }
+ else
+ dump("#ERROR: listitems.length(" + listitems.length + ") < top.MAX_RECIPIENTS(" + top.MAX_RECIPIENTS + ")\n");
+
+ return false;
+}
+
+function awCleanupRows()
+{
+ var maxRecipients = top.MAX_RECIPIENTS;
+ var rowID = 1;
+
+ for (var row = 1; row <= maxRecipients; row ++)
+ {
+ var inputElem = awGetInputElement(row);
+ if (inputElem.value == "" && row < maxRecipients)
+ awRemoveRow(awGetRowByInputElement(inputElem));
+ else
+ {
+ awSetInputAndPopupId(inputElem, awGetPopupElement(row), rowID);
+ rowID ++;
+ }
+ }
+
+ awTestRowSequence();
+}
+
+function awDeleteRow(rowToDelete)
+{
+ /* When we delete a row, we must reset the id of others row in order to not break the sequence */
+ var maxRecipients = top.MAX_RECIPIENTS;
+ awRemoveRow(rowToDelete);
+
+ // assume 2 column update (input and popup)
+ for (var row = rowToDelete + 1; row <= maxRecipients; row ++)
+ awSetInputAndPopupId(awGetInputElement(row), awGetPopupElement(row), (row-1));
+
+ awTestRowSequence();
+}
+
+function awClickEmptySpace(target, setFocus)
+{
+ if (target == null ||
+ (target.localName != "listboxbody" &&
+ target.localName != "listcell" &&
+ target.localName != "listitem"))
+ return;
+
+ let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+
+ if ( lastInput && lastInput.value )
+ awAppendNewRow(setFocus);
+ else if (setFocus)
+ awSetFocusTo(lastInput);
+}
+
+function awReturnHit(inputElement)
+{
+ let row = awGetRowByInputElement(inputElement);
+ let nextInput = awGetInputElement(row+1);
+
+ if ( !nextInput )
+ {
+ if ( inputElement.value )
+ awAppendNewRow(true);
+ else // No address entered, switch to Subject field
+ {
+ var subjectField = document.getElementById( 'msgSubject' );
+ subjectField.select();
+ subjectField.focus();
+ }
+ }
+ else
+ {
+ nextInput.select();
+ awSetFocusTo(nextInput);
+ }
+
+ // be sure to add the recipient to our ignore list
+ // when the user hits enter in an autocomplete widget...
+ addRecipientsToIgnoreList(inputElement.value);
+}
+
+function awDeleteHit(inputElement)
+{
+ let row = awGetRowByInputElement(inputElement);
+
+ /* 1. don't delete the row if it's the last one remaining, just reset it! */
+ if (top.MAX_RECIPIENTS <= 1)
+ {
+ inputElement.value = "";
+ return;
+ }
+
+ /* 2. Set the focus to the previous field if possible */
+ // Note: awSetFocusTo() is asynchronous, i.e. we'll focus after row removal.
+ if (row > 1)
+ awSetFocusTo(awGetInputElement(row - 1))
+ else
+ awSetFocusTo(awGetInputElement(2))
+
+ /* 3. Delete the row */
+ awDeleteRow(row);
+}
+
+function awAppendNewRow(setFocus)
+{
+ var listbox = document.getElementById('addressingWidget');
+ var listitem1 = awGetListItem(1);
+
+ if ( listbox && listitem1 )
+ {
+ var lastRecipientType = awGetPopupElement(top.MAX_RECIPIENTS).value;
+
+ var nextDummy = awGetNextDummyRow();
+ var newNode = listitem1.cloneNode(true);
+ if (nextDummy)
+ listbox.replaceChild(newNode, nextDummy);
+ else
+ listbox.appendChild(newNode);
+
+ top.MAX_RECIPIENTS++;
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if ( input && input.length == 1 )
+ {
+ input[0].value = "";
+
+ // We always clone the first row. The problem is that the first row
+ // could be focused. When we clone that row, we end up with a cloned
+ // XUL textbox that has a focused attribute set. Therefore we think
+ // we're focused and don't properly refocus. The best solution to this
+ // would be to clone a template row that didn't really have any presentation,
+ // rather than using the real visible first row of the listbox.
+ //
+ // For now we'll just put in a hack that ensures the focused attribute
+ // is never copied when the node is cloned.
+ if (input[0].getAttribute('focused') != '')
+ input[0].removeAttribute('focused');
+ }
+ var select = newNode.getElementsByTagName(awSelectElementName());
+ if ( select && select.length == 1 )
+ {
+ // It only makes sense to clone some field types; others
+ // should not be cloned, since it just makes the user have
+ // to go to the trouble of selecting something else. In such
+ // cases let's default to 'To' (a reasonable default since
+ // we already default to 'To' on the first dummy field of
+ // a new message).
+ switch (lastRecipientType)
+ {
+ case "addr_reply":
+ case "addr_other":
+ select[0].selectedIndex = awGetSelectItemIndex("addr_to");
+ break;
+ case "addr_followup":
+ select[0].selectedIndex = awGetSelectItemIndex("addr_newsgroups");
+ break;
+ default:
+ // e.g. "addr_to","addr_cc","addr_bcc","addr_newsgroups":
+ select[0].selectedIndex = awGetSelectItemIndex(lastRecipientType);
+ }
+
+ awSetInputAndPopupId(input[0], select[0], top.MAX_RECIPIENTS);
+
+ if (input)
+ _awSetAutoComplete(select[0], input[0]);
+ }
+
+ // Focus the new input widget.
+ if (setFocus && input[0] )
+ awSetFocusTo(input[0]);
+ }
+}
+
+// functions for accessing the elements in the addressing widget
+
+/**
+ * Returns the recipient type popup for a row.
+ *
+ * @param row Index of the recipient row to return. Starts at 1.
+ * @return This returns the menulist (not its child menupopup), despite the
+ * function name.
+ */
+function awGetPopupElement(row)
+{
+ return document.getElementById("addressCol1#" + row);
+}
+
+/**
+ * Returns the recipient inputbox for a row.
+ *
+ * @param row Index of the recipient row to return. Starts at 1.
+ * @return This returns the textbox element.
+ */
+function awGetInputElement(row)
+{
+ return document.getElementById("addressCol2#" + row);
+}
+
+function awGetElementByCol(row, col)
+{
+ var colID = "addressCol" + col + "#" + row;
+ return document.getElementById(colID);
+}
+
+function awGetListItem(row)
+{
+ var listbox = document.getElementById('addressingWidget');
+
+ if ( listbox && row > 0)
+ {
+ var listitems = listbox.getElementsByTagName('listitem');
+ if ( listitems && listitems.length >= row )
+ return listitems[row-1];
+ }
+ return 0;
+}
+
+function awGetRowByInputElement(inputElement)
+{
+ var row = 0;
+ if (inputElement) {
+ var listitem = inputElement.parentNode.parentNode;
+ while (listitem) {
+ if (listitem.localName == "listitem")
+ ++row;
+ listitem = listitem.previousSibling;
+ }
+ }
+ return row;
+}
+
+
+// Copy Node - copy this node and insert ahead of the (before) node. Append to end if before=0
+function awCopyNode(node, parentNode, beforeNode)
+{
+ var newNode = node.cloneNode(true);
+
+ if ( beforeNode )
+ parentNode.insertBefore(newNode, beforeNode);
+ else
+ parentNode.appendChild(newNode);
+
+ return newNode;
+}
+
+// remove row
+
+function awRemoveRow(row)
+{
+ awGetListItem(row).remove();
+ awFitDummyRows();
+
+ top.MAX_RECIPIENTS --;
+}
+
+/**
+ * Set focus to the specified element, typically a recipient input element.
+ * We do this asynchronusly to allow other processes like adding or removing rows
+ * to complete before shifting focus.
+ *
+ * @param element the element to receive focus asynchronously
+ */
+function awSetFocusTo(element) {
+ // Remember the (input) element to focus for asynchronous focusing, so that we
+ // play safe if this gets called again and the original element gets removed
+ // before we can focus it.
+ top.awInputToFocus = element;
+ setTimeout(_awSetFocusTo, 0);
+}
+
+function _awSetFocusTo() {
+ top.awInputToFocus.focus();
+}
+
+// Deprecated - use awSetFocusTo() instead.
+// ### TODO: This function should be removed if we're sure addons aren't using it.
+function awSetFocus(row, inputElement) {
+ awSetFocusTo(inputElement);
+}
+
+function awTabFromRecipient(element, event) {
+ var row = awGetRowByInputElement(element);
+ if (!event.shiftKey && row < top.MAX_RECIPIENTS) {
+ var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
+ var listBox = document.getElementById("addressingWidget");
+ listBox.listBoxObject.ensureIndexIsVisible(listBoxRow + 1);
+ }
+
+ // be sure to add the recipient to our ignore list
+ // when the user tabs out of an autocomplete line...
+ addRecipientsToIgnoreList(element.value);
+}
+
+function awTabFromMenulist(element, event)
+{
+ var row = awGetRowByInputElement(element);
+ if (event.shiftKey && row > 1) {
+ var listBoxRow = row - 1; // listbox row indices are 0-based, ours are 1-based.
+ var listBox = document.getElementById("addressingWidget");
+ listBox.listBoxObject.ensureIndexIsVisible(listBoxRow - 1);
+ }
+}
+
+function awGetNumberOfRecipients()
+{
+ return top.MAX_RECIPIENTS;
+}
+
+function DropOnAddressingTarget(event, onWidget) {
+ let dragSession = gDragService.getCurrentSession();
+
+ let trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+
+ let added = false;
+ for (let i = 0; i < dragSession.numDropItems; ++i) {
+ dragSession.getData(trans, i);
+ let dataObj = {};
+ let bestFlavor = {};
+ let len = {};
+
+ // Ensure we catch any empty data that may have slipped through.
+ try {
+ trans.getAnyTransferData(bestFlavor, dataObj, len);
+ } catch(ex) {
+ continue;
+ }
+ if (dataObj) {
+ dataObj = dataObj.value.QueryInterface(Ci.nsISupportsString);
+ }
+ if (!dataObj) {
+ continue;
+ }
+
+ // Pull the address out of the data object.
+ let address = dataObj.data.substring(0, len.value);
+ if (!address) {
+ continue;
+ }
+
+ if (onWidget) {
+ // Break down and add each address.
+ parseAndAddAddresses(address,
+ awGetPopupElement(top.MAX_RECIPIENTS).value);
+ } else {
+ // Add address into the bucket.
+ DropRecipient(address);
+ }
+ added = true;
+ }
+
+ // We added at least one address during the drop.
+ // Disable the default handler and stop propagating the event
+ // to avoid data being dropped twice.
+ if (added) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+}
+
+function _awSetAutoComplete(selectElem, inputElem)
+{
+ let params = JSON.parse(inputElem.getAttribute('autocompletesearchparam'));
+ params.type = selectElem.value;
+ inputElem.setAttribute('autocompletesearchparam', JSON.stringify(params));
+}
+
+function awSetAutoComplete(rowNumber)
+{
+ var inputElem = awGetInputElement(rowNumber);
+ var selectElem = awGetPopupElement(rowNumber);
+ _awSetAutoComplete(selectElem, inputElem)
+}
+
+function awRecipientTextCommand(userAction, element)
+{
+ if (userAction == "typing" || userAction == "scrolling")
+ awReturnHit(element);
+}
+
+// Called when an autocomplete session item is selected and the status of
+// the session it was selected from is nsIAutoCompleteStatus::failureItems.
+//
+// As of this writing, the only way that can happen is when an LDAP
+// autocomplete session returns an error to be displayed to the user.
+//
+// There are hardcoded messages in here, but these are just fallbacks for
+// when string bundles have already failed us.
+//
+function awRecipientErrorCommand(errItem, element)
+{
+ // remove the angle brackets from the general error message to construct
+ // the title for the alert. someday we'll pass this info using a real
+ // exception object, and then this code can go away.
+ //
+ var generalErrString;
+ if (errItem.value != "") {
+ generalErrString = errItem.value.slice(1, errItem.value.length-1);
+ } else {
+ generalErrString = "Unknown LDAP server problem encountered";
+ }
+
+ // try and get the string of the specific error to contruct the complete
+ // err msg, otherwise fall back to something generic. This message is
+ // handed to us as an nsISupportsString in the param slot of the
+ // autocomplete error item, by agreement documented in
+ // nsILDAPAutoCompFormatter.idl
+ //
+ var specificErrString = "";
+ try {
+ var specificError = errItem.param.QueryInterface(Ci.nsISupportsString);
+ specificErrString = specificError.data;
+ } catch (ex) {
+ }
+ if (specificErrString == "") {
+ specificErrString = "Internal error";
+ }
+
+ Services.prompt.alert(window, generalErrString, specificErrString);
+}
+
+function awRecipientKeyPress(event, element)
+{
+ switch(event.key) {
+ case "ArrowUp":
+ awArrowHit(element, -1);
+ break;
+ case "ArrowDown":
+ awArrowHit(element, 1);
+ break;
+ case "Enter":
+ case "Tab":
+ // if the user text contains a comma or a line return, ignore
+ if (element.value.includes(',')) {
+ var addresses = element.value;
+ element.value = ""; // clear out the current line so we don't try to autocomplete it..
+ parseAndAddAddresses(addresses, awGetPopupElement(awGetRowByInputElement(element)).value);
+ }
+ else if (event.key == "Tab")
+ awTabFromRecipient(element, event);
+
+ break;
+ }
+}
+
+function awArrowHit(inputElement, direction)
+{
+ var row = awGetRowByInputElement(inputElement) + direction;
+ if (row) {
+ var nextInput = awGetInputElement(row);
+
+ if (nextInput)
+ awSetFocusTo(nextInput);
+ else if (inputElement.value)
+ awAppendNewRow(true);
+ }
+}
+
+function awRecipientKeyDown(event, element)
+{
+ switch(event.key) {
+ case "Delete":
+ case "Backspace":
+ /* do not query directly the value of the text field else the autocomplete widget could potentially
+ alter it value while doing some internal cleanup, instead, query the value through the first child
+ */
+ if (!element.value)
+ awDeleteHit(element);
+
+ //We need to stop the event else the listbox will receive it and the function
+ //awKeyDown will be executed!
+ event.stopPropagation();
+ break;
+ }
+}
+
+function awKeyDown(event, listboxElement)
+{
+ switch(event.key) {
+ case "Delete":
+ case "Backspace":
+ /* Warning, the listboxElement.selectedItems will change everytime we delete a row */
+ var length = listboxElement.selectedCount;
+ for (var i = 1; i <= length; i++) {
+ var inputs = listboxElement.selectedItem.getElementsByTagName(awInputElementName());
+ if (inputs && inputs.length == 1)
+ awDeleteHit(inputs[0]);
+ }
+ break;
+ }
+}
+
+function awMenulistKeyPress(event, element)
+{
+ switch(event.key) {
+ case "Tab":
+ awTabFromMenulist(element, event);
+ break;
+ }
+}
+
+/* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
+
+var gAWContentHeight = 0;
+var gAWRowHeight = 0;
+
+function awFitDummyRows()
+{
+ awCalcContentHeight();
+ awCreateOrRemoveDummyRows();
+}
+
+function awCreateOrRemoveDummyRows()
+{
+ var listbox = document.getElementById("addressingWidget");
+ var listboxHeight = listbox.boxObject.height;
+
+ // remove rows to remove scrollbar
+ let kids = listbox.querySelectorAll('[_isDummyRow]');
+ for (let i = kids.length - 1; gAWContentHeight > listboxHeight && i >= 0; --i) {
+ gAWContentHeight -= gAWRowHeight;
+ kids[i].remove();
+ }
+
+ // add rows to fill space
+ if (gAWRowHeight) {
+ while (gAWContentHeight + gAWRowHeight < listboxHeight) {
+ awCreateDummyItem(listbox);
+ gAWContentHeight += gAWRowHeight;
+ }
+ }
+}
+
+function awCalcContentHeight()
+{
+ var listbox = document.getElementById("addressingWidget");
+ var items = listbox.getElementsByTagName("listitem");
+
+ gAWContentHeight = 0;
+ if (items.length > 0) {
+ // all rows are forced to a uniform height in xul listboxes, so
+ // find the first listitem with a boxObject and use it as precedent
+ var i = 0;
+ do {
+ gAWRowHeight = items[i].boxObject.height;
+ ++i;
+ } while (i < items.length && !gAWRowHeight);
+ gAWContentHeight = gAWRowHeight*items.length;
+ }
+}
+
+function awCreateDummyItem(aParent)
+{
+ var titem = document.createElement("listitem");
+ titem.setAttribute("_isDummyRow", "true");
+ titem.setAttribute("class", "dummy-row");
+
+ for (var i = awGetNumberOfCols(); i > 0; i--)
+ awCreateDummyCell(titem);
+
+ if (aParent)
+ aParent.appendChild(titem);
+
+ return titem;
+}
+
+function awCreateDummyCell(aParent)
+{
+ var cell = document.createElement("listcell");
+ cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
+ if (aParent)
+ aParent.appendChild(cell);
+
+ return cell;
+}
+
+function awGetNextDummyRow()
+{
+ // gets the next row from the top down
+ return document.querySelector('#addressingWidget > [_isDummyRow]');
+}
+
+function awSizerListen()
+{
+ // when splitter is clicked, fill in necessary dummy rows each time the mouse is moved
+ awCalcContentHeight(); // precalculate
+ document.addEventListener("mousemove", awSizerMouseMove, true);
+ document.addEventListener("mouseup", awSizerMouseUp);
+}
+
+function awSizerMouseMove()
+{
+ awCreateOrRemoveDummyRows(2);
+}
+
+function awSizerMouseUp()
+{
+ document.removeEventListener("mousemove", awSizerMouseMove);
+ document.removeEventListener("mouseup", awSizerMouseUp);
+}
+
+function awSizerResized(aSplitter)
+{
+ // set the height on the listbox rather than on the toolbox
+ var listbox = document.getElementById("addressingWidget");
+ listbox.height = listbox.boxObject.height;
+ // remove all the heights set on the splitter's previous siblings
+ for (let sib = aSplitter.previousSibling; sib; sib = sib.previousSibling)
+ sib.removeAttribute("height");
+}
+
+function awDocumentKeyPress(event)
+{
+ try {
+ var id = event.target.id;
+ if (id.startsWith('addressCol1'))
+ awRecipientKeyPress(event, event.target);
+ } catch (e) { }
+}
+
+function awRecipientInputCommand(event, inputElement)
+{
+ gContentChanged=true;
+ setupAutocomplete();
+}
+
+// Given an arbitrary block of text like a comma delimited list of names or a names separated by spaces,
+// we will try to autocomplete each of the names and then take the FIRST match for each name, adding it the
+// addressing widget on the compose window.
+
+var gAutomatedAutoCompleteListener = null;
+
+function parseAndAddAddresses(addressText, recipientType)
+{
+ // strip any leading >> characters inserted by the autocomplete widget
+ var strippedAddresses = addressText.replace(/.* >> /, "");
+
+ var addresses = MailServices.headerParser
+ .makeFromDisplayAddress(strippedAddresses);
+
+ if (addresses.length)
+ {
+ // we need to set up our own autocomplete session and search for results
+
+ setupAutocomplete(); // be safe, make sure we are setup
+ if (!gAutomatedAutoCompleteListener)
+ gAutomatedAutoCompleteListener = new AutomatedAutoCompleteHandler();
+
+ gAutomatedAutoCompleteListener.init(addresses.map(addr => addr.toString()),
+ addresses.length, recipientType);
+ }
+}
+
+function AutomatedAutoCompleteHandler()
+{
+}
+
+// state driven self contained object which will autocomplete a block of addresses without any UI.
+// force picks the first match and adds it to the addressing widget, then goes on to the next
+// name to complete.
+
+AutomatedAutoCompleteHandler.prototype =
+{
+ param: this,
+ sessionName: null,
+ namesToComplete: {},
+ numNamesToComplete: 0,
+ indexIntoNames: 0,
+
+ numSessionsToSearch: 0,
+ numSessionsSearched: 0,
+ recipientType: null,
+ searchResults: null,
+
+ init:function(namesToComplete, numNamesToComplete, recipientType)
+ {
+ this.indexIntoNames = 0;
+ this.numNamesToComplete = numNamesToComplete;
+ this.namesToComplete = namesToComplete;
+
+ this.recipientType = recipientType;
+
+ // set up the auto complete sessions to use
+ setupAutocomplete();
+ this.autoCompleteNextAddress();
+ },
+
+ autoCompleteNextAddress:function()
+ {
+ this.numSessionsToSearch = 0;
+ this.numSessionsSearched = 0;
+ this.searchResults = new Array;
+
+ if (this.indexIntoNames < this.numNamesToComplete && this.namesToComplete[this.indexIntoNames])
+ {
+ /* XXX This is used to work, until switching to the new toolkit broke it
+ We should fix it see bug 456550.
+ if (!this.namesToComplete[this.indexIntoNames].includes('@')) // don't autocomplete if address has an @ sign in it
+ {
+ // make sure total session count is updated before we kick off ANY actual searches
+ if (gAutocompleteSession)
+ this.numSessionsToSearch++;
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory)
+ this.numSessionsToSearch++;
+
+ if (gAutocompleteSession)
+ {
+ gAutocompleteSession.onAutoComplete(this.namesToComplete[this.indexIntoNames], null, this);
+ // AB searches are actually synchronous. So by the time we get here we have already looked up results.
+
+ // if we WERE going to also do an LDAP lookup, then check to see if we have a valid match in the AB, if we do
+ // don't bother with the LDAP search too just return
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory && this.searchResults[0] && this.searchResults[0].defaultItemIndex != -1)
+ {
+ this.processAllResults();
+ return;
+ }
+ }
+
+ if (gLDAPSession && gCurrentAutocompleteDirectory)
+ gLDAPSession.onStartLookup(this.namesToComplete[this.indexIntoNames], null, this);
+ }
+ */
+
+ if (!this.numSessionsToSearch)
+ this.processAllResults(); // ldap and ab are turned off, so leave text alone
+ }
+ },
+
+ onStatus:function(aStatus)
+ {
+ return;
+ },
+
+ onAutoComplete: function(aResults, aStatus)
+ {
+ // store the results until all sessions are done and have reported in
+ if (aResults)
+ this.searchResults[this.numSessionsSearched] = aResults;
+
+ this.numSessionsSearched++; // bump our counter
+
+ if (this.numSessionsToSearch <= this.numSessionsSearched)
+ setTimeout('gAutomatedAutoCompleteListener.processAllResults()', 0); // we are all done
+ },
+
+ processAllResults: function()
+ {
+ // Take the first result and add it to the compose window
+ var addressToAdd;
+
+ // loop through the results looking for the non default case (default case is the address book with only one match, the default domain)
+ var sessionIndex;
+
+ var searchResultsForSession;
+
+ for (sessionIndex in this.searchResults)
+ {
+ searchResultsForSession = this.searchResults[sessionIndex];
+ if (searchResultsForSession && searchResultsForSession.defaultItemIndex > -1)
+ {
+ addressToAdd = searchResultsForSession.items
+ .queryElementAt(searchResultsForSession.defaultItemIndex,
+ Ci.nsIAutoCompleteItem).value;
+ break;
+ }
+ }
+
+ // still no match? loop through looking for the -1 default index
+ if (!addressToAdd)
+ {
+ for (sessionIndex in this.searchResults)
+ {
+ searchResultsForSession = this.searchResults[sessionIndex];
+ if (searchResultsForSession && searchResultsForSession.defaultItemIndex == -1)
+ {
+ addressToAdd = searchResultsForSession.items
+ .queryElementAt(0, Ci.nsIAutoCompleteItem).value;
+ break;
+ }
+ }
+ }
+
+ // no matches anywhere...just use what we were given
+ if (!addressToAdd)
+ addressToAdd = this.namesToComplete[this.indexIntoNames];
+
+ // that will automatically set the focus on a new available row, and make sure it is visible
+ awAddRecipient(this.recipientType ? this.recipientType : "addr_to", addressToAdd);
+
+ this.indexIntoNames++;
+ this.autoCompleteNextAddress();
+ },
+
+ QueryInterface : function(iid)
+ {
+ if (iid.equals(Ci.nsIAutoCompleteListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+}
diff --git a/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul b/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul
new file mode 100644
index 0000000000..efd1c6007a
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="mailComposeOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <menupopup id="menu_EditPopup" onpopupshowing="updateEditItems();">
+ <menuitem id="menu_inlineSpellCheck"
+ oncommand="EnableInlineSpellCheck(!InlineSpellCheckerUI.enabled);"/>
+ <menuitem id="menu_accountmgr"
+ insertafter="sep_preferences"
+ command="cmd_account"/>
+ </menupopup>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/messengercompose.xul b/comm/suite/mailnews/components/compose/content/messengercompose.xul
new file mode 100644
index 0000000000..89126d814d
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/messengercompose.xul
@@ -0,0 +1,720 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/messengercompose/messengercompose.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://editor/skin/editorFormatToolbar.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/addressingWidget.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSMIMEOverlay.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/bindings.css" type="text/css"?>
+
+<?xul-overlay href="chrome://communicator/content/charsetOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/tasksOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/sidebar/sidebarOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/contentAreaContextOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/messengercompose/msgComposeContextOverlay.xul"?>
+<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?>
+<?xul-overlay href="chrome://editor/content/editorOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/messengercompose/mailComposeOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/mailOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % messengercomposeDTD SYSTEM "chrome://messenger/locale/messengercompose/messengercompose.dtd" >
+%messengercomposeDTD;
+<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+%messengerDTD;
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
+%utilityDTD;
+<!ENTITY % msgCompSMIMEDTD SYSTEM "chrome://messenger-smime/locale/msgCompSMIMEOverlay.dtd">
+%msgCompSMIMEDTD;
+]>
+
+<window id="msgcomposeWindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ onunload="ComposeUnload()"
+ onload="ComposeLoad()"
+ onclose="return DoCommandClose()"
+ onfocus="EditorOnFocus()"
+ title="&msgComposeWindow.title;"
+ toggletoolbar="true"
+ lightweightthemes="true"
+ lightweightthemesfooter="status-bar"
+ windowtype="msgcompose"
+ macanimationtype="document"
+ drawtitle="true"
+ width="640" height="480"
+ persist="screenX screenY width height sizemode">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_composeMsgs" src="chrome://messenger/locale/messengercompose/composeMsgs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <stringbundle id="bundle_offlinePrompts" src="chrome://messenger/locale/offline.properties"/>
+ <stringbundle id="languageBundle" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="brandBundle" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_comp_smime" src="chrome://messenger-smime/locale/msgCompSMIMEOverlay.properties"/>
+ </stringbundleset>
+
+ <script src="chrome://communicator/content/contentAreaClick.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+ <script src="chrome://messenger/content/accountUtils.js"/>
+ <script src="chrome://messenger/content/mail-offline.js"/>
+ <script src="chrome://editor/content/editor.js"/>
+ <script src="chrome://messenger/content/messengercompose/MsgComposeCommands.js"/>
+ <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
+ <script src="chrome://messenger/content/addressbook/abDragDrop.js"/>
+ <script src="chrome://messenger-smime/content/msgCompSMIMEOverlay.js"/>
+
+ <commandset id="composeCommands">
+ <commandset id="msgComposeCommandUpdate"
+ commandupdater="true"
+ events="focus"
+ oncommandupdate="CommandUpdate_MsgCompose()"/>
+
+ <commandset id="editorCommands"/>
+ <commandset id="commonEditorMenuItems"/>
+ <commandset id="composerMenuItems"/>
+ <commandset id="composerEditMenuItems"/>
+ <commandset id="composerStyleMenuItems"/>
+ <commandset id="composerTableMenuItems"/>
+ <commandset id="composerListMenuItems"/>
+ <commandset id="tasksCommands"/>
+ <!-- File Menu -->
+ <command id="cmd_attachFile" oncommand="goDoCommand('cmd_attachFile')"/>
+ <command id="cmd_attachPage" oncommand="goDoCommand('cmd_attachPage')"/>
+ <command id="cmd_attachVCard" checked="false" oncommand="ToggleAttachVCard(event.target)"/>
+ <command id="cmd_save" oncommand="goDoCommand('cmd_save')"/>
+ <command id="cmd_saveAsFile" oncommand="goDoCommand('cmd_saveAsFile')"/>
+ <command id="cmd_saveAsDraft" oncommand="goDoCommand('cmd_saveAsDraft')"/>
+ <command id="cmd_saveAsTemplate" oncommand="goDoCommand('cmd_saveAsTemplate')"/>
+ <command id="cmd_sendButton" oncommand="goDoCommand('cmd_sendButton')"/>
+ <command id="cmd_sendNow" oncommand="goDoCommand('cmd_sendNow')"/>
+ <command id="cmd_sendWithCheck" oncommand="goDoCommand('cmd_sendWithCheck')"/>
+ <command id="cmd_sendLater" oncommand="goDoCommand('cmd_sendLater')"/>
+
+ <!-- Edit Menu -->
+ <!--command id="cmd_pasteQuote"/ DO NOT INCLUDE THOSE COMMANDS ELSE THE EDIT MENU WILL BE BROKEN! -->
+ <!--command id="cmd_find"/-->
+ <!--command id="cmd_findNext"/-->
+ <!--command id="cmd_findReplace"/-->
+ <command id="cmd_renameAttachment" oncommand="goDoCommand('cmd_renameAttachment')" disabled="true"/>
+ <command id="cmd_openAttachment" oncommand="goDoCommand('cmd_openAttachment')"/>
+ <command id="cmd_account"
+ label="&accountManagerCmd.label;"
+ accesskey="&accountManagerCmd.accesskey;"
+ oncommand="goDoCommand('cmd_account');"/>
+
+ <!-- Options Menu -->
+ <command id="cmd_selectAddress" oncommand="goDoCommand('cmd_selectAddress')"/>
+ <command id="cmd_outputFormat" oncommand="OutputFormatMenuSelect(event.target)"/>
+ <command id="cmd_quoteMessage" oncommand="goDoCommand('cmd_quoteMessage')"/>
+ <command id="cmd_viewSecurityStatus" oncommand="showMessageComposeSecurityStatus();"/>
+ </commandset>
+
+ <broadcasterset id="composeBroadcasters">
+ <broadcaster id="Communicator:WorkMode"/>
+ <broadcaster id="securityStatus" crypto="" signing=""/>
+ </broadcasterset>
+
+ <observes element="securityStatus" attribute="crypto"/>
+ <observes element="securityStatus" attribute="signing"/>
+
+ <broadcasterset id="mainBroadcasterSet"/>
+
+ <keyset id="tasksKeys">
+ <!-- File Menu -->
+ <key id="key_send" keycode="&sendCmd.keycode;" observes="cmd_sendWithCheck" modifiers="accel"/>
+ <key id="key_sendLater" keycode="&sendLaterCmd.keycode;" observes="cmd_sendLater" modifiers="accel, shift"/>
+
+ <!-- Options Menu -->
+ <!-- key id="key_selectAddresses" key="&selectAddressCmd.key;" command="cmd_selectAddress"/ -->
+
+ <key id="showHideSidebar"/>
+ <!-- Tab/F6 Keys -->
+ <key keycode="VK_TAB" oncommand="SwitchElementFocus(event);" modifiers="control"/>
+ <key keycode="VK_TAB" oncommand="SwitchElementFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="control"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="control,shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);" modifiers="shift"/>
+ <key keycode="VK_F6" oncommand="SwitchElementFocus(event);"/>
+ <key keycode="VK_ESCAPE" oncommand="handleEsc();"/>
+ </keyset>
+ <keyset id="editorKeys"/>
+ <keyset id="composeKeys">
+#ifndef XP_MACOSX
+ <key id="key_renameAttachment" keycode="VK_F2"
+ oncommand="goDoCommand('cmd_renameAttachment');"/>
+#endif
+ </keyset>
+
+ <popupset id="contentAreaContextSet"/>
+
+ <popupset id="editorPopupSet">
+ <menupopup id="sidebarPopup"/>
+
+ <menupopup id="msgComposeAttachmentContext"
+ onpopupshowing="updateEditItems();">
+ <menuitem label="&openAttachment.label;"
+ accesskey="&openAttachment.accesskey;"
+ command="cmd_openAttachment"/>
+ <menuitem accesskey="&deleteAttachment.accesskey;"
+ command="cmd_delete"/>
+ <menuitem label="&renameAttachment.label;"
+ accesskey="&renameAttachment.accesskey;"
+ command="cmd_renameAttachment"/>
+ <menuitem label="&selectAllCmd.label;"
+ accesskey="&selectAllAttachments.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem label="&attachFile.label;"
+ accesskey="&attachFile.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem label="&attachPage.label;"
+ accesskey="&attachPage.accesskey;"
+ command="cmd_attachPage"/>
+ </menupopup>
+ </popupset>
+
+ <menupopup id="blockedContentOptions" value=""
+ onpopupshowing="onBlockedContentOptionsShowing(event);">
+ </menupopup>
+
+ <vbox id="titlebar"/>
+
+ <toolbox id="compose-toolbox"
+ class="toolbox-top"
+ mode="full"
+ defaultmode="full">
+ <toolbar id="compose-toolbar-menubar2"
+ type="menubar"
+ class="chromeclass-menubar"
+ persist="collapsed"
+ grippytooltiptext="&menuBar.tooltip;"
+ customizable="true"
+ defaultset="menubar-items"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items"
+ class="menubar-items"
+ align="center">
+ <menubar id="mail-menubar">
+ <menu id="menu_File">
+ <menupopup id="menu_FilePopup">
+ <menu id="menu_New">
+ <menupopup id="menu_NewPopup">
+ <menuitem id="menu_newMessage"/>
+ <menuseparator id="menuNewPopupSeparator"/>
+ <menuitem id="menu_newCard"/>
+ <menuitem id="menu_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"/>
+ <menuitem id="menu_newEditor"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Attach"
+ label="&attachMenu.label;"
+ accesskey="&attachMenu.accesskey;">
+ <menupopup id="menu_AttachPopup">
+ <menuitem id="menu_AttachFile"
+ label="&attachFileCmd.label;"
+ accesskey="&attachFileCmd.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem id="menu_AttachPage"
+ label="&attachPageCmd.label;"
+ accesskey="&attachPageCmd.accesskey;"
+ command="cmd_attachPage"/>
+ <menuseparator id="menuAttachPageSeparator"/>
+ <menuitem id="menu_AttachPageVCard"
+ type="checkbox"
+ label="&attachVCardCmd.label;"
+ accesskey="&attachVCardCmd.accesskey;"
+ command="cmd_attachVCard"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_close"/>
+ <menuseparator id="menuFileAfterCloseSeparator"/>
+ <menuitem id="menu_SaveCmd"
+ label="&saveCmd.label;"
+ accesskey="&saveCmd.accesskey;"
+ key="key_save"
+ command="cmd_save"/>
+ <menu id="menu_SaveAsCmd"
+ label="&saveAsCmd.label;"
+ accesskey="&saveAsCmd.accesskey;">
+ <menupopup id="menu_SaveAsCmdPopup">
+ <menuitem id="menu_SaveAsFileCmd"
+ label="&saveAsFileCmd.label;"
+ accesskey="&saveAsFileCmd.accesskey;"
+ command="cmd_saveAsFile"/>
+ <menuseparator id="menuSaveAfterSaveAsSeparator"/>
+ <menuitem id="menu_SaveAsDraftCmd"
+ label="&saveAsDraftCmd.label;"
+ accesskey="&saveAsDraftCmd.accesskey;"
+ command="cmd_saveAsDraft"/>
+ <menuitem id="menu_SaveAsTemplateCmd"
+ label="&saveAsTemplateCmd.label;"
+ accesskey="&saveAsTemplateCmd.accesskey;"
+ command="cmd_saveAsTemplate"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="menuFileAfterSaveAsSeparator"/>
+ <menuitem id="menu_sendNow"
+ label="&sendNowCmd.label;"
+ accesskey="&sendNowCmd.accesskey;"
+ key="key_send" command="cmd_sendNow"/>
+ <menuitem id="menu_sendLater"
+ label="&sendLaterCmd.label;"
+ accesskey="&sendLaterCmd.accesskey;"
+ key="key_sendLater"
+ command="cmd_sendLater"/>
+ <menuseparator id="menuFileAfterSendLaterSeparator"/>
+ <menuitem id="menu_printSetup"/>
+ <menuitem id="menu_printPreview"/>
+ <menuitem id="menu_print"/>
+ </menupopup>
+ </menu>
+ <menu id="menu_Edit"/>
+ <menu id="menu_View">
+ <menupopup id="menu_View_Popup">
+ <menu id="menu_Toolbars">
+ <menupopup id="view_toolbars_popup"
+ onpopupshowing="onViewToolbarsPopupShowing(event)"
+ oncommand="onViewToolbarCommand(event);">
+ <menuitem id="menu_showTaskbar"/>
+ </menupopup>
+ </menu>
+ <menuseparator id="viewMenuBeforeSecurityStatusSeparator"/>
+ <menuitem id="menu_viewSecurityStatus"
+ label="&menu_viewSecurityStatus.label;"
+ accesskey="&menu_viewSecurityStatus.accesskey;"
+ command="cmd_viewSecurityStatus"/>
+ </menupopup>
+ </menu>
+
+ <menu id="insertMenu"
+ command="cmd_renderedHTMLEnabler"/>
+
+ <menu id="formatMenu"
+ label="&formatMenu.label;"
+ accesskey="&formatMenu.accesskey;"
+ command="cmd_renderedHTMLEnabler">
+ <menupopup id="formatMenuPopup">
+ <menu id="tableMenu"/>
+ <menuseparator id="menuFormatAfterTableSeparator"/>
+ <menuitem id="objectProperties"/>
+ <menuitem id="colorsAndBackground"/>
+ </menupopup>
+ </menu>
+
+ <menu id="optionsMenu"
+ label="&optionsMenu.label;"
+ accesskey="&optionsMenu.accesskey;">
+ <menupopup id="optionsMenuPopup"
+ onpopupshowing="setSecuritySettings(1);">
+ <menuitem id="menu_selectAddress"
+ label="&selectAddressCmd.label;"
+ accesskey="&selectAddressCmd.accesskey;"
+ command="cmd_selectAddress"/>
+ <menuitem id="menu_quoteMessage"
+ label="&quoteCmd.label;"
+ accesskey="&quoteCmd.accesskey;"
+ command="cmd_quoteMessage"/>
+ <menuseparator id="menuOptionsAfterQuoteSeparator"/>
+ <menuitem id="returnReceiptMenu"
+ type="checkbox"
+ label="&returnReceiptMenu.label;"
+ accesskey="&returnReceiptMenu.accesskey;"
+ checked="false"
+ oncommand="ToggleReturnReceipt(event.target)"/>
+ <menuitem id="dsnMenu"
+ type="checkbox"
+ label="&dsnMenu.label;"
+ accesskey="&dsnMenu.accesskey;"
+ checked="false"
+ oncommand="ToggleDSN(event.target);"/>
+ <menu id="outputFormatMenu"
+ label="&outputFormatMenu.label;"
+ accesskey="&outputFormatMenu.accesskey;"
+ command="cmd_outputFormat">
+ <menupopup id="outputFormatMenuPopup">
+ <menuitem id="format_auto" type="radio" name="output_format" label="&autoFormatCmd.label;" accesskey="&autoFormatCmd.accesskey;" checked="true"/>
+ <menuitem id="format_plain" type="radio" name="output_format" label="&plainTextFormatCmd.label;" accesskey="&plainTextFormatCmd.accesskey;"/>
+ <menuitem id="format_html" type="radio" name="output_format" label="&htmlFormatCmd.label;" accesskey="&htmlFormatCmd.accesskey;"/>
+ <menuitem id="format_both" type="radio" name="output_format" label="&bothFormatCmd.label;" accesskey="&bothFormatCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menu id="priorityMenu"
+ label="&priorityMenu.label;"
+ accesskey="&priorityMenu.accesskey;"
+ oncommand="PriorityMenuSelect(event.target);">
+ <menupopup id="priorityMenuPopup"
+ onpopupshowing="updatePriorityMenu(this);">
+ <menuitem id="priority_highest" type="radio" name="priority" label="&highestPriorityCmd.label;" accesskey="&highestPriorityCmd.accesskey;" value="Highest"/>
+ <menuitem id="priority_high" type="radio" name="priority" label="&highPriorityCmd.label;" accesskey="&highPriorityCmd.accesskey;" value="High"/>
+ <menuitem id="priority_normal" type="radio" name="priority" label="&normalPriorityCmd.label;" accesskey="&normalPriorityCmd.accesskey;" value="Normal"/>
+ <menuitem id="priority_low" type="radio" name="priority" label="&lowPriorityCmd.label;" accesskey="&lowPriorityCmd.accesskey;" value="Low"/>
+ <menuitem id="priority_lowest" type="radio" name="priority" label="&lowestPriorityCmd.label;" accesskey="&lowestPriorityCmd.accesskey;" value="Lowest"/>
+ </menupopup>
+ </menu>
+ <menu id="charsetMenu"
+ onpopupshowing="UpdateCharsetMenu(gMsgCompose.compFields.characterSet, this);"
+ oncommand="ComposeSetCharacterSet(event);">
+ <menupopup id="charsetPopup" detectors="false"/>
+ </menu>
+ <menu id="fccMenu"
+ label="&fileCarbonCopyCmd.label;"
+ accesskey="&fileCarbonCopyCmd.accesskey;"
+ oncommand="MessageFcc(event.target._folder);">
+ <menupopup id="fccMenuPopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ fileHereLabel="&fileHereMenu.label;"/>
+ </menu>
+ <menuseparator id="smimeOptionsSeparator"/>
+ <menuitem id="menu_securityEncryptRequire1"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="toggleEncryptMessage();"/>
+ <menuitem id="menu_securitySign1"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="toggleSignMessage();"/>
+ </menupopup>
+ </menu>
+ <menu id="tasksMenu"/>
+ <menu id="windowMenu"/>
+ <menu id="menu_Help"/>
+ </menubar>
+ </toolbaritem>
+ </toolbar>
+
+ <toolbar id="composeToolbar"
+ class="toolbar-primary chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&mailToolbar.tooltip;"
+ toolbarname="&showComposeToolbarCmd.label;"
+ accesskey="&showComposeToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="button-send,separator,button-address,button-attach,spellingButton,button-security,separator,button-save,spring,throbber-box"
+ context="toolbar-context-menu">
+ <toolbarbutton id="button-send"
+ class="toolbarbutton-1"
+ label="&sendButton.label;"
+ tooltiptext="&sendButton.tooltip;"
+ now_label="&sendButton.label;"
+ now_tooltiptext="&sendButton.tooltip;"
+ later_label="&sendLaterCmd.label;"
+ later_tooltiptext="&sendlaterButton.tooltip;"
+ removable="true"
+ command="cmd_sendButton">
+ <observes element="Communicator:WorkMode"
+ attribute="offline"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-address"
+ class="toolbarbutton-1"
+ label="&addressButton.label;"
+ tooltiptext="&addressButton.tooltip;"
+ removable="true"
+ command="cmd_selectAddress"/>
+
+ <toolbarbutton id="button-attach"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&attachButton.label;"
+ tooltiptext="&attachButton.tooltip;"
+ removable="true"
+ command="cmd_attachFile">
+ <menupopup id="button-attachPopup">
+ <menuitem id="button-attachFile"
+ label="&attachFileCmd.label;"
+ accesskey="&attachFileCmd.accesskey;"
+ command="cmd_attachFile"/>
+ <menuitem id="button-attachPage"
+ label="&attachPageCmd.label;"
+ accesskey="&attachPageCmd.accesskey;"
+ command="cmd_attachPage"/>
+ <menuseparator id="buttonAttachAfterPageSeparator"/>
+ <menuitem id="button-attachVCard"
+ type="checkbox"
+ label="&attachVCardCmd.label;"
+ accesskey="&attachVCardCmd.accesskey;"
+ command="cmd_attachVCard"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="spellingButton"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&spellingButton.label;"
+ removable="true"
+ command="cmd_spelling">
+ <!-- this popup gets dynamically generated -->
+ <menupopup id="languageMenuList"
+ oncommand="ChangeLanguage(event);"
+ onpopupshowing="OnShowDictionaryMenu(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="button-save"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&saveButton.label;"
+ tooltiptext="&saveButton.tooltip;"
+ removable="true"
+ command="cmd_save">
+ <menupopup id="button-savePopup">
+ <menuitem id="button-saveAsFile"
+ label="&saveAsFileCmd.label;"
+ accesskey="&saveAsFileCmd.accesskey;"
+ command="cmd_saveAsFile"/>
+ <menuseparator id="buttonSaveAfterFileSeparator"/>
+ <menuitem id="button-saveAsDraft"
+ label="&saveAsDraftCmd.label;"
+ accesskey="&saveAsDraftCmd.accesskey;"
+ command="cmd_saveAsDraft"/>
+ <menuitem id="button-saveAsTemplate"
+ label="&saveAsTemplateCmd.label;"
+ accesskey="&saveAsTemplateCmd.accesskey;"
+ command="cmd_saveAsTemplate"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbaritem id="throbber-box"/>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="MsgHeadersToolbar"
+ persist="collapsed"
+ flex="1"
+ grippytooltiptext="&addressBar.tooltip;"
+ nowindowdrag="true">
+ <hbox id="msgheaderstoolbar-box" flex="1">
+ <vbox id="addresses-box" flex="1">
+ <hbox align="center">
+ <label value="&fromAddr.label;" accesskey="&fromAddr.accesskey;" control="msgIdentity"/>
+ <menulist id="msgIdentity"
+ editable="true"
+ disableautoselect="true"
+ flex="1"
+ oncommand="LoadIdentity(false);">
+ <menupopup id="msgIdentityPopup"/>
+ </menulist>
+ </hbox>
+ <!-- Addressing Widget -->
+ <listbox id="addressingWidget" flex="1"
+ seltype="multiple" rows="4"
+ onkeydown="awKeyDown(event, this);"
+ onclick="awClickEmptySpace(event.originalTarget, true);"
+ ondragover="DragAddressOverTargetControl(event);"
+ ondrop="DropOnAddressingTarget(event, true);">
+
+ <listcols>
+ <listcol id="typecol-addressingWidget"/>
+ <listcol id="textcol-addressingWidget" flex="1"/>
+ </listcols>
+
+ <listitem class="addressingWidgetItem" allowevents="true">
+ <listcell class="addressingWidgetCell" align="stretch">
+ <menulist id="addressCol1#1" disableonsend="true"
+ class="aw-menulist menulist-compact" flex="1"
+ onkeypress="awMenulistKeyPress(event, this);"
+ oncommand="onAddressColCommand(this.id);">
+ <menupopup>
+ <menuitem value="addr_to" label="&toAddr.label;"/>
+ <menuitem value="addr_cc" label="&ccAddr.label;"/>
+ <menuitem value="addr_bcc" label="&bccAddr.label;"/>
+ <menuitem value="addr_reply" label="&replyAddr.label;"/>
+ <menuitem value="addr_newsgroups"
+ label="&newsgroupsAddr.label;"/>
+ <menuitem value="addr_followup"
+ label="&followupAddr.label;"/>
+ </menupopup>
+ </menulist>
+ </listcell>
+
+ <listcell class="addressingWidgetCell">
+ <textbox id="addressCol2#1"
+ class="plain textbox-addressingWidget uri-element"
+ aria-labelledby="addressCol1#1"
+ type="autocomplete" flex="1" maxrows="4"
+ newlines="replacewithcommas"
+ autocompletesearch="mydomain addrbook ldap news"
+ timeout="300" autocompletesearchparam="{}"
+ completedefaultindex="true" forcecomplete="true"
+ minresultsforpopup="2" ignoreblurwhilesearching="true"
+ ontextentered="awRecipientTextCommand(eventParam, this);"
+ onerrorcommand="awRecipientErrorCommand(eventParam, this);"
+ onchange="onRecipientsChanged();"
+ oninput="onRecipientsChanged();"
+ onkeypress="awRecipientKeyPress(event, this);"
+ onkeydown="awRecipientKeyDown(event, this);"
+ disableonsend="true">
+ <image class="person-icon"
+ onclick="this.parentNode.select();"/>
+ </textbox>
+ </listcell>
+ </listitem>
+ </listbox>
+ <hbox align="center">
+ <label value="&subject.label;" accesskey="&subject.accesskey;" control="msgSubject"/>
+ <textbox id="msgSubject" flex="1" class="toolbar" disableonsend="true" spellcheck="true"
+ oninput="gContentChanged=true;SetComposeWindowTitle();"
+ onkeypress="subjectKeyPress(event);" />
+ </hbox>
+ </vbox>
+ <splitter id="attachmentbucket-sizer" collapse="after"/>
+ <vbox id="attachments-box">
+ <label id="attachmentBucketText" value="&attachments.label;" crop="right"
+ accesskey="&attachments.accesskey;" control="attachmentBucket"/>
+ <listbox id="attachmentBucket"
+ seltype="multiple"
+ flex="1"
+ rows="4"
+ tabindex="-1"
+ context="msgComposeAttachmentContext"
+ disableoncustomize="true"
+ onkeypress="if (event.keyCode == 8 || event.keyCode == 46) RemoveSelectedAttachment();"
+ onclick="AttachmentBucketClicked(event);"
+ ondragover="attachmentBucketObserver.onDragOver(event);"
+ ondrop="attachmentBucketObserver.onDrop(event);"
+ ondragexit="attachmentBucketObserver.onDragExit(event);"/>
+ </vbox>
+ </hbox>
+ </toolbar>
+
+ <!-- These toolbar items get filled out from the editorOverlay -->
+ <toolbar id="FormatToolbar"
+ class="chromeclass-toolbar"
+ persist="collapsed"
+ grippytooltiptext="&formatToolbar.tooltip;"
+ toolbarname="&showFormatToolbarCmd.label;"
+ accesskey="&showFormatToolbarCmd.accesskey;"
+ customizable="true"
+ defaultset="paragraph-select-container,font-face-select-container,color-buttons-container,DecreaseFontSizeButton,IncreaseFontSizeButton,separator,boldButton,italicButton,underlineButton,separator,ulButton,olButton,outdentButton,indentButton,separator,AlignPopupButton,InsertPopupButton,smileButtonMenu"
+ mode="icons"
+ iconsize="small"
+ defaultmode="icons"
+ defaulticonsize="small"
+ context="toolbar-context-menu"
+ nowindowdrag="true">
+ <toolbaritem id="paragraph-select-container"/>
+ <toolbaritem id="font-face-select-container"/>
+ <toolbaritem id="color-buttons-container"
+ disableoncustomize="true"/>
+ <toolbarbutton id="DecreaseFontSizeButton"/>
+ <toolbarbutton id="IncreaseFontSizeButton"/>
+ <toolbarbutton id="boldButton"/>
+ <toolbarbutton id="italicButton"/>
+ <toolbarbutton id="underlineButton"/>
+ <toolbarbutton id="ulButton"/>
+ <toolbarbutton id="olButton"/>
+ <toolbarbutton id="outdentButton"/>
+ <toolbarbutton id="indentButton"/>
+ <toolbarbutton id="AlignPopupButton"/>
+ <toolbarbutton id="InsertPopupButton"/>
+ <toolbarbutton id="smileButtonMenu"/>
+ </toolbar>
+
+ <toolbarpalette id="MsgComposeToolbarPalette">
+ <toolbarbutton id="print-button"
+ label="&printButton.label;"
+ tooltiptext="&printButton.tooltip;"/>
+ <toolbarbutton id="button-security"
+ type="menu-button"
+ class="toolbarbutton-1"
+ label="&securityButton.label;"
+ tooltiptext="&securityButton.tooltip;"
+ oncommand="doSecurityButton();">
+ <menupopup onpopupshowing="setSecuritySettings(2);">
+ <menuitem id="menu_securityEncryptRequire2"
+ type="checkbox"
+ label="&menu_securityEncryptRequire.label;"
+ accesskey="&menu_securityEncryptRequire.accesskey;"
+ oncommand="setNextCommand('encryptMessage');"/>
+ <menuitem id="menu_securitySign2"
+ type="checkbox"
+ label="&menu_securitySign.label;"
+ accesskey="&menu_securitySign.accesskey;"
+ oncommand="setNextCommand('signMessage');"/>
+ <menuseparator id="smimeToolbarButtonSeparator"/>
+ <menuitem id="menu_securityStatus2"
+ label="&menu_securityStatus.label;"
+ accesskey="&menu_securityStatus.accesskey;"
+ oncommand="setNextCommand('show');"/>
+ </menupopup>
+ </toolbarbutton>
+ </toolbarpalette>
+
+ </toolbox>
+
+ <splitter id="compose-toolbar-sizer"
+ resizeafter="grow"
+ onmousedown="awSizerListen();"
+ oncommand="awSizerResized(this);">
+ <observes element="MsgHeadersToolbar" attribute="collapsed"/>
+ </splitter>
+
+ <!-- sidebar/toolbar/content/status -->
+ <hbox id="sidebar-parent" flex="1">
+ <!-- From sidebarOverlay.xul -->
+ <vbox id="sidebar-box" class="chromeclass-extrachrome" hidden="true"/>
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome" hidden="true"/>
+
+ <!-- The mail message body frame -->
+ <vbox id="appcontent" flex="1">
+ <findbar id="FindToolbar" browserid="content-frame"/>
+ <editor id="content-frame"
+ type="content"
+ primary="true"
+ src="about:blank"
+ name="browser.message.body"
+ minheight="100"
+ flex="1"
+ ondblclick="EditorDblClick(event);"
+ context="contentAreaContextMenu"/>
+ </vbox>
+ </hbox>
+
+ <hbox>
+ <notificationbox id="attachmentNotificationBox"
+ flex="1"
+ notificationside="bottom"/>
+ </hbox>
+
+ <statusbar id="status-bar"
+ class="chromeclass-status">
+ <statusbarpanel id="component-bar"/>
+ <statusbarpanel id="statusText"
+ flex="1"/>
+ <statusbarpanel id="statusbar-progresspanel"
+ class="statusbarpanel-progress"
+ collapsed="true">
+ <progressmeter id="compose-progressmeter"
+ class="progressmeter-statusbar"
+ mode="normal"
+ value="0"/>
+ </statusbarpanel>
+ <statusbarpanel id="signing-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageComposeSecurityStatus();"/>
+ <statusbarpanel id="encryption-status"
+ class="statusbarpanel-iconic"
+ collapsed="true"
+ oncommand="showMessageComposeSecurityStatus();"/>
+ <statusbarpanel id="offline-status"
+ class="statusbarpanel-iconic"
+ checkfunc="MailCheckBeforeOfflineChange();"/>
+ </statusbar>
+
+</window>
diff --git a/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul b/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul
new file mode 100644
index 0000000000..f3558873d2
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="msgComposeContextOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <menupopup id="contentAreaContextMenu"
+ onpopupshowing="return event.target != this ||
+ openEditorContextMenu(this);">
+ <!-- Hide the menuitems by default so they do not to show up
+ in the sidebar context menu. -->
+ <menuitem id="context-pasteNoFormatting"
+ insertafter="context-paste"
+ hidden="true"
+ command="cmd_pasteNoFormatting"/>
+ <menuitem id="context-pasteQuote"
+ insertafter="context-pasteNoFormatting"
+ hidden="true"
+ command="cmd_pasteQuote"/>
+ </menupopup>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js
new file mode 100644
index 0000000000..f67d919f63
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js
@@ -0,0 +1,30 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function Startup() {
+ let value = document.getElementById("mail.compose.autosave").value;
+ EnableElementById("autoSaveInterval", value, false);
+}
+
+function EnableMailComposeAutosaveInterval(aValue) {
+ let focus = (document.getElementById("autoSave") == document.commandDispatcher.focusedElement);
+ EnableElementById("autoSaveInterval", aValue, focus);
+}
+
+function PopulateFonts() {
+ var fontsList = document.getElementById("fontSelect");
+ try {
+ var enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"]
+ .getService(Ci.nsIFontEnumerator);
+ var localFonts = enumerator.EnumerateAllFonts();
+ for (let font of localFonts)
+ if (font != "serif" && font != "sans-serif" && font != "monospace")
+ fontsList.appendItem(font, font);
+ } catch (ex) { }
+
+ // Select the item after the list is completely generated.
+ document.getElementById(fontsList.getAttribute("preference"))
+ .setElementValue(fontsList);
+}
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul
new file mode 100644
index 0000000000..c6f3b4fac8
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul
@@ -0,0 +1,212 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % pref-composing_messagesDTD SYSTEM "chrome://messenger/locale/messengercompose/pref-composing_messages.dtd">
+%pref-composing_messagesDTD;
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+%editorOverlayDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="composing_messages_pane"
+ label="&pref.composing.messages.title;"
+ script="chrome://messenger/content/messengercompose/pref-composing_messages.js"
+ onpaneload="this.PopulateFonts();">
+
+ <preferences id="composing_messages_preferences">
+ <preference id="mail.forward_message_mode"
+ name="mail.forward_message_mode"
+ type="int"/>
+ <preference id="mail.reply_quote_inline"
+ name="mail.reply_quote_inline"
+ type="bool"/>
+ <preference id="mail.compose.autosave"
+ name="mail.compose.autosave"
+ type="bool"
+ onchange="EnableMailComposeAutosaveInterval(this.value);"/>
+ <preference id="mail.compose.autosaveinterval"
+ name="mail.compose.autosaveinterval"
+ type="int"/>
+ <preference id="mail.warn_on_send_accel_key"
+ name="mail.warn_on_send_accel_key"
+ type="bool"/>
+ <preference id="mailnews.wraplength"
+ name="mailnews.wraplength"
+ type="int"/>
+ <preference id="msgcompose.font_face"
+ name="msgcompose.font_face"
+ type="string"/>
+ <preference id="msgcompose.font_size"
+ name="msgcompose.font_size"
+ type="string"/>
+ <preference id="msgcompose.text_color"
+ name="msgcompose.text_color"
+ type="string"/>
+ <preference id="msgcompose.background_color"
+ name="msgcompose.background_color"
+ type="string"/>
+ <preference id="mailnews.reply_header_type"
+ name="mailnews.reply_header_type"
+ type="int"/>
+ <preference id="mail.compose.default_to_paragraph"
+ name="mail.compose.default_to_paragraph"
+ type="bool"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&generalComposing.label;"/>
+
+ <radiogroup id="forwardMessageMode"
+ orient="horizontal"
+ align="center"
+ preference="mail.forward_message_mode">
+ <label value="&forwardMsg.label;" control="forwardMessageMode"/>
+ <radio value="2"
+ label="&inline.label;"
+ accesskey="&inline.accesskey;"/>
+ <radio value="0"
+ label="&asAttachment.label;"
+ accesskey="&asAttachment.accesskey;"/>
+ </radiogroup>
+
+ <checkbox id="replyQuoteInline" label="&replyQuoteInline.label;"
+ preference="mail.reply_quote_inline"
+ accesskey="&replyQuoteInline.accesskey;"/>
+
+ <hbox align="center">
+ <checkbox id="autoSave" label="&autoSave.label;"
+ preference="mail.compose.autosave"
+ accesskey="&autoSave.accesskey;"
+ aria-labelledby="autoSave autoSaveInterval autoSaveEnd"/>
+ <textbox id="autoSaveInterval"
+ type="number"
+ min="1"
+ max="99"
+ size="2"
+ preference="mail.compose.autosaveinterval"
+ aria-labelledby="autoSave autoSaveInterval autoSaveEnd"/>
+ <label id="autoSaveEnd" value="&autoSaveEnd.label;"/>
+ </hbox>
+
+ <checkbox id="mailWarnOnSendAccelKey"
+ label="&warnOnSendAccelKey.label;"
+ accesskey="&warnOnSendAccelKey.accesskey;"
+ preference="mail.warn_on_send_accel_key"/>
+
+ <hbox align="center">
+ <label id="wrapOutLabel"
+ value="&wrapOutMsg.label;"
+ accesskey="&wrapOutMsg.accesskey;"
+ control="wrapLength"/>
+ <textbox id="wrapLength"
+ type="number"
+ min="0"
+ max="999"
+ size="3"
+ preference="mailnews.wraplength"
+ aria-labelledby="wrapOutLabel wrapLength wrapOutEnd"/>
+ <label id="wrapOutEnd" value="&char.label;"/>
+ </hbox>
+ <hbox align="center">
+ <label id="selectHeaderType"
+ value="&selectHeaderType.label;"
+ accesskey="&selectHeaderType.accesskey;"
+ control="mailNewsReplyList"/>
+ <menulist id="mailNewsReplyList"
+ preference="mailnews.reply_header_type">
+ <menupopup>
+ <menuitem value="0"
+ label="&noReplyOption.label;"/>
+ <menuitem value="1"
+ label="&authorWroteOption.label;"/>
+ <menuitem value="2"
+ label="&onDateAuthorWroteOption.label;"/>
+ <menuitem value="3"
+ label="&authorWroteOnDateOption.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <!-- Composing Mail -->
+
+ <groupbox align="start">
+ <caption label="&defaultMessagesHeader.label;"/>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <label value="&font.label;"
+ accesskey="&font.accesskey;"
+ control="fontSelect"/>
+ <menulist id="fontSelect" preference="msgcompose.font_face">
+ <menupopup>
+ <menuitem value=""
+ label="&fontVarWidth.label;"/>
+ <menuitem value="tt"
+ label="&fontFixedWidth.label;"/>
+ <menuseparator/>
+ <menuitem value="Helvetica, Arial, sans-serif"
+ label="&fontHelvetica.label;"/>
+ <menuitem value="Times New Roman, Times, serif"
+ label="&fontTimes.label;"/>
+ <menuitem value="Courier New, Courier, monospace"
+ label="&fontCourier.label;"/>
+ <menuseparator/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label value="&size.label;"
+ accesskey="&size.accesskey;"
+ control="fontSizeSelect"/>
+ <hbox align="center">
+ <menulist id="fontSizeSelect" preference="msgcompose.font_size">
+ <menupopup>
+ <menuitem value="x-small" label="&size-tinyCmd.label;"/>
+ <menuitem value="small" label="&size-smallCmd.label;"/>
+ <menuitem value="medium" label="&size-mediumCmd.label;"/>
+ <menuitem value="large" label="&size-largeCmd.label;"/>
+ <menuitem value="x-large" label="&size-extraLargeCmd.label;"/>
+ <menuitem value="xx-large" label="&size-hugeCmd.label;"/>
+ </menupopup>
+ </menulist>
+ <label value="&fontColor.label;"
+ accesskey="&fontColor.accesskey;"
+ control="msgComposeTextColor"/>
+ <colorpicker id="msgComposeTextColor"
+ type="button"
+ preference="msgcompose.text_color"/>
+ <label value="&bgColor.label;"
+ accesskey="&bgColor.accesskey;"
+ control="msgComposeBackgroundColor"/>
+ <colorpicker id="msgComposeBackgroundColor"
+ type="button"
+ preference="msgcompose.background_color"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <description>&defaultCompose.label;</description>
+ <radiogroup id="defaultCompose"
+ class="indent"
+ preference="mail.compose.default_to_paragraph">
+ <radio value="false"
+ label="&defaultBodyText.label;"
+ accesskey="&defaultBodyText.accesskey;"/>
+ <radio value="true"
+ label="&defaultParagraph.label;"
+ accesskey="&defaultParagraph.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js
new file mode 100644
index 0000000000..b5c31d424d
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js
@@ -0,0 +1,151 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gListbox;
+var gPref;
+var gError;
+
+function Startup()
+{
+ // Store some useful elements in globals.
+ gListbox =
+ {
+ html: document.getElementById("html_domains"),
+ plaintext: document.getElementById("plaintext_domains")
+ };
+ gPref =
+ {
+ html_domains: document.getElementById("mailnews.html_domains"),
+ plaintext_domains: document.getElementById("mailnews.plaintext_domains")
+ };
+ gError = document.getElementById("formatting_error_msg");
+
+ // Make it easier to access the pref pane from onsync.
+ gListbox.html.pane = this;
+ gListbox.plaintext.pane = this;
+}
+
+function AddDomain(aType)
+{
+ var domains = null;
+ var result = {value: null};
+ if (Services.prompt.prompt(window, gListbox[aType].getAttribute("title"),
+ gListbox[aType].getAttribute("msg"), result,
+ null, {value: 0}))
+ domains = result.value.replace(/ /g, "").split(",");
+
+ if (domains)
+ {
+ var added = false;
+ var removed = false;
+ var listbox = gListbox[aType];
+ var other = aType == "html" ? gListbox.plaintext : gListbox.html;
+ for (var i = 0; i < domains.length; i++)
+ {
+ var domainName = TidyDomainName(domains[i], true);
+ if (domainName)
+ {
+ if (!DomainFirstMatch(listbox, domainName))
+ {
+ var match = DomainFirstMatch(other, domainName);
+ if (match)
+ {
+ match.remove();
+ removed = true;
+ }
+ listbox.appendItem(domainName);
+ added = true;
+ }
+ }
+ }
+ if (added)
+ listbox.doCommand();
+ if (removed)
+ other.doCommand();
+ }
+}
+
+function TidyDomainName(aDomain, aWarn)
+{
+ // See if it is an email address and if so take just the domain part.
+ aDomain = aDomain.replace(/.*@/, "");
+
+ // See if it is a valid domain otherwise return null.
+ if (!/.\../.test(aDomain))
+ {
+ if (aWarn)
+ {
+ var errorMsg = gError.getAttribute("inverr").replace(/@string@/, aDomain);
+ Services.prompt.alert(window, gError.getAttribute("title"), errorMsg);
+ }
+ return null;
+ }
+
+ // Finally make sure the domain is in lowercase.
+ return aDomain.toLowerCase();
+}
+
+function DomainFirstMatch(aListbox, aDomain)
+{
+ return aListbox.getElementsByAttribute("label", aDomain).item(0);
+}
+
+function RemoveDomains(aType, aEvent)
+{
+ if (aEvent && aEvent.keyCode != KeyEvent.DOM_VK_DELETE &&
+ aEvent.keyCode != KeyEvent.DOM_VK_BACK_SPACE)
+ return;
+
+ var nextNode = null;
+ var listbox = gListbox[aType];
+
+ while (listbox.selectedItem)
+ {
+ var selectedNode = listbox.selectedItem;
+ nextNode = selectedNode.nextSibling || selectedNode.previousSibling;
+ selectedNode.remove();
+ }
+
+ if (nextNode)
+ listbox.selectItem(nextNode);
+
+ listbox.doCommand();
+}
+
+function ReadDomains(aListbox)
+{
+ var arrayOfPrefs = gPref[aListbox.id].value.replace(/ /g, "").split(",");
+ if (arrayOfPrefs)
+ {
+ var i;
+ // Check all the existing items, remove any that are not needed and
+ // make sure we do not duplicate any by removing from pref array.
+ var domains = aListbox.getElementsByAttribute("label", "*");
+ if (domains)
+ {
+ for (i = domains.length; --i >= 0; )
+ {
+ var domain = domains[i];
+ var index = arrayOfPrefs.indexOf(domain.label);
+ if (index > -1)
+ arrayOfPrefs.splice(index, 1);
+ else
+ domain.remove();
+ }
+ }
+ for (i = 0; i < arrayOfPrefs.length; i++)
+ {
+ var str = TidyDomainName(arrayOfPrefs[i], false);
+ if (str)
+ aListbox.appendItem(str);
+ }
+ }
+}
+
+function WriteDomains(aListbox)
+{
+ var domains = aListbox.getElementsByAttribute("label", "*");
+ return Array.from(domains, e => e.label).join(",");
+}
diff --git a/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul
new file mode 100644
index 0000000000..0167a0990f
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/messengercompose/pref-formatting.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="formatting_pane"
+ label="&pref.formatting.title;"
+ script="chrome://messenger/content/messengercompose/pref-formatting.js">
+ <preferences id="formatting_preferences">
+ <preference id="mail.default_html_action"
+ name="mail.default_html_action"
+ type="int"/>
+ <preference id="mailnews.html_domains"
+ name="mailnews.html_domains"
+ type="string"/>
+ <preference id="mailnews.plaintext_domains"
+ name="mailnews.plaintext_domains"
+ type="string"/>
+ <preference id="mailnews.sendformat.auto_downgrade"
+ name="mailnews.sendformat.auto_downgrade"
+ type="bool"/>
+ </preferences>
+
+ <data id="formatting_error_msg"
+ title="&domainnameError.title;"
+ inverr="&invalidEntryError.label;"/>
+
+ <description>&sendMaildesc.label;</description>
+
+ <radiogroup id="mailDefaultHTMLAction"
+ preference="mail.default_html_action">
+ <radio value="0"
+ label="&askMe.label;"
+ accesskey="&askMe.accesskey;"/>
+ <radio value="1"
+ label="&convertPlain2.label;"
+ accesskey="&convertPlain2.accesskey;"/>
+ <radio value="2"
+ label="&sendHTML2.label;"
+ accesskey="&sendHTML2.accesskey;"/>
+ <radio value="3"
+ label="&sendBoth2.label;"
+ accesskey="&sendBoth2.accesskey;"/>
+ </radiogroup>
+
+ <groupbox flex="1">
+ <caption label="&domain.title;"/>
+
+ <description>&domaindesc.label;</description>
+
+ <hbox flex="1">
+ <vbox flex="1">
+ <label value="&HTMLdomaintitle.label;"
+ accesskey="&HTMLdomaintitle.accesskey;"
+ control="html_domains"/>
+ <hbox flex="1">
+ <listbox id="html_domains"
+ title="&add.htmltitle;"
+ msg="&add.htmldomain;"
+ flex="1"
+ seltype="multiple"
+ preference="mailnews.html_domains"
+ onsyncfrompreference="return this.pane.ReadDomains(this);"
+ onsynctopreference="return this.pane.WriteDomains(this);"
+ onkeypress="RemoveDomains('html', event);"/>
+ <vbox>
+ <button label="&AddButton.label;"
+ accesskey="&AddHtmlDomain.accesskey;"
+ oncommand="AddDomain('html');">
+ <observes element="html_domains" attribute="disabled"/>
+ </button>
+ <button label="&DeleteButton.label;"
+ accesskey="&DeleteHtmlDomain.accesskey;"
+ oncommand="RemoveDomains('html', null);">
+ <observes element="html_domains" attribute="disabled"/>
+ </button>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <label value="&PlainTexttitle.label;"
+ accesskey="&PlainTexttitle.accesskey;"
+ control="plaintext_domains"/>
+ <hbox flex="1">
+ <listbox id="plaintext_domains"
+ title="&add.plaintexttitle;"
+ msg="&add.plaintextdomain;"
+ flex="1"
+ seltype="multiple"
+ preference="mailnews.plaintext_domains"
+ onsyncfrompreference="return this.pane.ReadDomains(this);"
+ onsynctopreference="return this.pane.WriteDomains(this);"
+ onkeypress="RemoveDomains('plaintext', event);"/>
+ <vbox>
+ <button label="&AddButton.label;"
+ accesskey="&AddPlainText.accesskey;"
+ oncommand="AddDomain('plaintext');">
+ <observes element="plaintext_domains" attribute="disabled"/>
+ </button>
+ <button label="&DeleteButton.label;"
+ accesskey="&DeletePlainText.accesskey;"
+ oncommand="RemoveDomains('plaintext', null);">
+ <observes element="plaintext_domains" attribute="disabled"/>
+ </button>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+ </groupbox>
+
+ <checkbox id="autoDowngrade"
+ label="&autoDowngrade.label;"
+ accesskey="&autoDowngrade.accesskey;"
+ preference="mailnews.sendformat.auto_downgrade"/>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/compose/jar.mn b/comm/suite/mailnews/components/compose/jar.mn
new file mode 100644
index 0000000000..c9465fa8d7
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/jar.mn
@@ -0,0 +1,14 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/messengercompose/pref-composing_messages.xul (content/prefs/pref-composing_messages.xul)
+ content/messenger/messengercompose/pref-composing_messages.js (content/prefs/pref-composing_messages.js)
+ content/messenger/messengercompose/pref-formatting.xul (content/prefs/pref-formatting.xul)
+ content/messenger/messengercompose/pref-formatting.js (content/prefs/pref-formatting.js)
+* content/messenger/messengercompose/messengercompose.xul (content/messengercompose.xul)
+ content/messenger/messengercompose/mailComposeOverlay.xul (content/mailComposeOverlay.xul)
+ content/messenger/messengercompose/msgComposeContextOverlay.xul (content/msgComposeContextOverlay.xul)
+ content/messenger/messengercompose/MsgComposeCommands.js (content/MsgComposeCommands.js)
+ content/messenger/messengercompose/addressingWidgetOverlay.js (content/addressingWidgetOverlay.js)
diff --git a/comm/suite/mailnews/components/compose/moz.build b/comm/suite/mailnews/components/compose/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/mailnews/components/compose/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/mailnews/components/moz.build b/comm/suite/mailnews/components/moz.build
new file mode 100644
index 0000000000..9d5b9f36ad
--- /dev/null
+++ b/comm/suite/mailnews/components/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ "compose",
+ "prefs",
+ "addrbook",
+ "smime",
+]
diff --git a/comm/suite/mailnews/components/prefs/content/mailPrefsOverlay.xul b/comm/suite/mailnews/components/prefs/content/mailPrefsOverlay.xul
new file mode 100644
index 0000000000..2a4acef93b
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/mailPrefsOverlay.xul
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<!DOCTYPE overlay [
+<!ENTITY % mailPrefsOverlayDTD SYSTEM "chrome://messenger/locale/mailPrefsOverlay.dtd">
+%mailPrefsOverlayDTD;
+]>
+
+<overlay id="mailPrefsOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <preferences id="appearance_preferences">
+ <preference id="general.startup.mail"
+ name="general.startup.mail"
+ type="bool"/>
+ <preference id="general.startup.addressbook"
+ name="general.startup.addressbook"
+ type="bool"/>
+ </preferences>
+
+ <!-- mail startup toggle -->
+ <groupbox id="generalStartupPreferences">
+ <checkbox id="generalStartupMail"
+ insertafter="generalStartupBrowser"
+ label="&mail.label;"
+ accesskey="&mail.accesskey;"
+ preference="general.startup.mail"/>
+ <checkbox id="generalStartupAddressBook"
+ insertafter="generalStartupEditor,generalStartupMail"
+ label="&addressbook.label;"
+ accesskey="&addressbook.accesskey;"
+ preference="general.startup.addressbook"/>
+ </groupbox>
+
+ <!-- category tree entries for mail/news -->
+ <treechildren id="prefsPanelChildren">
+ <treeitem container="true"
+ id="mailnewsItem"
+ insertafter="navigatorItem"
+ label="&mail.label;"
+ prefpane="mailnews_pane"
+ url="chrome://messenger/content/pref-mailnews.xul"
+ helpTopic="mail_prefs_general">
+ <treechildren id="messengerChildren">
+ <treeitem id="viewingMessagesItem"
+ label="&viewingMessages.label;"
+ prefpane="viewing_messages_pane"
+ url="chrome://messenger/content/pref-viewing_messages.xul"
+ helpTopic="mail_prefs_display"/>
+ <treeitem id="notificationsItem"
+ label="&notifications.label;"
+ prefpane="notifications_pane"
+ url="chrome://messenger/content/pref-notifications.xul"
+ helpTopic="mail_prefs_notifications"/>
+ <treeitem id="composingItem"
+ label="&composingMessages.label;"
+ prefpane="composing_messages_pane"
+ url="chrome://messenger/content/messengercompose/pref-composing_messages.xul"
+ helpTopic="mail_prefs_messages"/>
+ <treeitem id="formattingItem"
+ label="&format.label;"
+ prefpane="formatting_pane"
+ url="chrome://messenger/content/messengercompose/pref-formatting.xul"
+ helpTopic="mail_prefs_formatting"/>
+ <treeitem id="addressItem"
+ label="&address.label;"
+ prefpane="addressing_pane"
+ url="chrome://messenger/content/addressbook/pref-addressing.xul"
+ helpTopic="mail_prefs_addressing"/>
+ <treeitem id="junkItem"
+ label="&junk.label;"
+ prefpane="junk_pane"
+ url="chrome://messenger/content/pref-junk.xul"
+ helpTopic="mail-prefs-junk"/>
+ <treeitem id="tagsItem"
+ label="&tags.label;"
+ prefpane="tags_pane"
+ url="chrome://messenger/content/pref-tags.xul"
+ helpTopic="mail-prefs-tags"/>
+ <treeitem id="receiptsItem"
+ label="&return.label;"
+ prefpane="receipts_pane"
+ url="chrome://messenger/content/pref-receipts.xul"
+ helpTopic="mail-prefs-receipts"/>
+ <treeitem id="characterEncodingItem"
+ label="&characterEncoding2.label;"
+ prefpane="character_encoding_pane"
+ url="chrome://messenger/content/pref-character_encoding.xul"
+ helpTopic="mail_prefs_text_encoding"/>
+ <treeitem id="offlineItem"
+ label="&networkStorage.label;"
+ prefpane="offline_pane"
+ url="chrome://messenger/content/pref-offline.xul"
+ helpTopic="mail_prefs_offline"/>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-character_encoding.js b/comm/suite/mailnews/components/prefs/content/pref-character_encoding.js
new file mode 100644
index 0000000000..0ae30e9b1f
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-character_encoding.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The contents of this file will be loaded into the scope of the object
+// <prefpane id="character_encoding_pane">!
+
+var updatingPref = false;
+
+function Startup ()
+{
+ PrefChanged(document.getElementById('mailnews.view_default_charset'));
+ PrefChanged(document.getElementById('mailnews.send_default_charset'));
+}
+
+function PrefChanged(aPref)
+{
+ if (updatingPref)
+ return;
+
+ var id = aPref.id.substr(9, 4) + "DefaultCharsetList";
+ var menulist = document.getElementById(id);
+ if (!aPref.hasUserValue)
+ menulist.selectedIndex = 0;
+ else {
+ var bundle = document.getElementById("charsetBundle");
+ menulist.value = bundle.getString(aPref.value.toLowerCase());
+ }
+}
+
+function UpdatePref(aMenulist)
+{
+ updatingPref = true;
+ var id = "mailnews." + aMenulist.id.substr(0, 4) + "_default_charset";
+ var pref = document.getElementById(id);
+ if (aMenulist.selectedIndex)
+ pref.value = aMenulist.value;
+ else
+ pref.value = undefined; // reset to default
+ updatingPref = false;
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-character_encoding.xul b/comm/suite/mailnews/components/prefs/content/pref-character_encoding.xul
new file mode 100755
index 0000000000..009f5f49de
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-character_encoding.xul
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % prefCharacterEncodingDTD SYSTEM "chrome://messenger/locale/pref-character_encoding.dtd"> %prefCharacterEncodingDTD;
+ <!ENTITY % prefUtilitiesDTD SYSTEM "chrome://communicator/locale/pref/prefutilities.dtd"> %prefUtilitiesDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="character_encoding_pane"
+ label="&pref.character.encoding2.title;"
+ script="chrome://messenger/content/pref-character_encoding.js">
+ <preferences id="character_encoding_preferences">
+ <preference id="mailnews.view_default_charset"
+ name="mailnews.view_default_charset"
+ type="wstring"
+ onchange="PrefChanged(this);"/>
+ <preference id="mail.strictly_mime"
+ name="mail.strictly_mime"
+ type="bool"/>
+ <preference id="mailnews.send_default_charset"
+ name="mailnews.send_default_charset"
+ type="wstring"
+ onchange="PrefChanged(this);"/>
+ <preference id="mailnews.reply_in_default_charset"
+ name="mailnews.reply_in_default_charset"
+ type="bool"/>
+ </preferences>
+
+ <groupbox align="start">
+ <caption label="&messageDisplay.caption;"/>
+ <hbox align="center">
+ <label control="viewDefaultCharsetList"
+ value="&viewFallbackCharset2.label;"
+ accesskey="&viewFallbackCharset2.accesskey;"/>
+ <menulist id="viewDefaultCharsetList"
+ oncommand="UpdatePref(this);">
+ <menupopup>
+ <menuitem label="&FallbackCharset.auto;" value=""/>
+ <menuitem label="&FallbackCharset.unicode;" value="UTF-8"/>
+ <menuitem label="&FallbackCharset.other;" value="windows-1252"/>
+ <menuseparator/>
+ <menuitem label="&FallbackCharset.arabic;" value="windows-1256"/>
+ <menuitem label="&FallbackCharset.baltic;" value="windows-1257"/>
+ <menuitem label="&FallbackCharset.ceiso;" value="ISO-8859-2"/>
+ <menuitem label="&FallbackCharset.cewindows;" value="windows-1250"/>
+ <menuitem label="&FallbackCharset.simplified;" value="gbk"/>
+ <menuitem label="&FallbackCharset.traditional;" value="Big5"/>
+ <menuitem label="&FallbackCharset.cyrillic;" value="windows-1251"/>
+ <menuitem label="&FallbackCharset.greek;" value="ISO-8859-7"/>
+ <menuitem label="&FallbackCharset.hebrew;" value="windows-1255"/>
+ <menuitem label="&FallbackCharset.japanese;" value="Shift_JIS"/>
+ <menuitem label="&FallbackCharset.korean;" value="EUC-KR"/>
+ <menuitem label="&FallbackCharset.thai;" value="windows-874"/>
+ <menuitem label="&FallbackCharset.turkish;" value="windows-1254"/>
+ <menuitem label="&FallbackCharset.vietnamese;" value="windows-1258"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <description>&viewFallbackCharset.desc;</description>
+ </groupbox>
+
+ <!-- Composing Mail -->
+ <groupbox align="start">
+ <caption label="&composingMessages.caption;"/>
+
+ <checkbox id="strictlyMime"
+ label="&useMIME.label;"
+ accesskey="&useMIME.accesskey;"
+ preference="mail.strictly_mime"/>
+
+ <hbox align="center">
+ <label value="&sendDefaultCharset2.label;"
+ accesskey="&sendDefaultCharset2.accesskey;"
+ control="sendDefaultCharsetList"/>
+ <menulist id="sendDefaultCharsetList"
+ oncommand="UpdatePref(this);">
+ <menupopup>
+ <menuitem label="&FallbackCharset.auto;" value=""/>
+ <menuitem label="&FallbackCharset.unicode;" value="UTF-8"/>
+ <menuitem label="&FallbackCharset.other;" value="windows-1252"/>
+ <menuseparator/>
+ <menuitem label="&FallbackCharset.arabic;" value="windows-1256"/>
+ <menuitem label="&FallbackCharset.baltic;" value="windows-1257"/>
+ <menuitem label="&FallbackCharset.ceiso;" value="ISO-8859-2"/>
+ <menuitem label="&FallbackCharset.cewindows;" value="windows-1250"/>
+ <menuitem label="&FallbackCharset.simplified;" value="gbk"/>
+ <menuitem label="&FallbackCharset.traditional;" value="Big5"/>
+ <menuitem label="&FallbackCharset.cyrillic;" value="windows-1251"/>
+ <menuitem label="&FallbackCharset.greek;" value="ISO-8859-7"/>
+ <menuitem label="&FallbackCharset.hebrew;" value="windows-1255"/>
+ <menuitem label="&FallbackCharset.japanese;" value="Shift_JIS"/>
+ <menuitem label="&FallbackCharset.korean;" value="EUC-KR"/>
+ <menuitem label="&FallbackCharset.thai;" value="windows-874"/>
+ <menuitem label="&FallbackCharset.turkish;" value="windows-1254"/>
+ <menuitem label="&FallbackCharset.vietnamese;" value="windows-1258"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <checkbox id="replyInDefaultCharset"
+ label="&replyInDefaultCharset3.label;"
+ accesskey="&replyInDefaultCharset3.accesskey;"
+ preference="mailnews.reply_in_default_charset"/>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-junk.js b/comm/suite/mailnews/components/prefs/content/pref-junk.js
new file mode 100644
index 0000000000..9f31050c46
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-junk.js
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function Startup()
+{
+ UpdateDependentElement("manualMark", "manualMarkMode");
+ UpdateDependentElement("enableJunkLogging", "openJunkLog");
+}
+
+function UpdateDependentElement(aBaseId, aDependentId)
+{
+ var pref = document.getElementById(aBaseId).getAttribute("preference");
+ EnableElementById(aDependentId, document.getElementById(pref).value, false);
+}
+
+function OpenJunkLog()
+{
+ window.openDialog("chrome://messenger/content/junkLog.xul",
+ "junkLog",
+ "chrome,modal,titlebar,resizable,centerscreen");
+}
+
+function ResetTrainingData()
+{
+ // make sure the user really wants to do this
+ var bundle = document.getElementById("bundleJunkPreferences");
+ var title = bundle.getString("confirmResetJunkTrainingTitle");
+ var text = bundle.getString("confirmResetJunkTrainingText");
+
+ // if the user says no, then just fall out
+ if (Services.prompt.confirmEx(window, title, text,
+ Services.prompt.STD_YES_NO_BUTTONS |
+ Services.prompt.BUTTON_POS_1_DEFAULT,
+ "", "", "", null, {}))
+ return;
+
+ // otherwise go ahead and remove the training data
+ MailServices.junk.resetTrainingData();
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-junk.xul b/comm/suite/mailnews/components/prefs/content/pref-junk.xul
new file mode 100644
index 0000000000..b6d1f4507d
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-junk.xul
@@ -0,0 +1,134 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % prefJunkDTD SYSTEM "chrome://messenger/locale/pref-junk.dtd">
+%prefJunkDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="junk_pane"
+ label="&pref.junk.title;"
+ script="chrome://messenger/content/pref-junk.js">
+ <preferences id="junk_preferences">
+ <preference id="mail.spam.manualMark"
+ name="mail.spam.manualMark"
+ type="bool"
+ onchange="EnableElementById('manualMarkMode', this.value, false);"/>
+ <preference id="mail.spam.manualMarkMode"
+ name="mail.spam.manualMarkMode"
+ type="int"/>
+ <preference id="mail.spam.markAsReadOnSpam"
+ name="mail.spam.markAsReadOnSpam"
+ type="bool"/>
+ <preference id="mailnews.ui.junk.manualMarkAsJunkMarksRead"
+ name="mailnews.ui.junk.manualMarkAsJunkMarksRead"
+ type="bool"/>
+ <preference id="mail.spam.logging.enabled"
+ name="mail.spam.logging.enabled"
+ type="bool"
+ onchange="EnableElementById('openJunkLog', this.value, false);"/>
+ <preference id="pref.junk.disable_button.openJunkLog"
+ name="pref.junk.disable_button.openJunkLog"
+ type="string"/>
+ <preference id="pref.junk.disable_button.resetTrainingData"
+ name="pref.junk.disable_button.resetTrainingData"
+ type="string"/>
+ <preference id="mail.phishing.detection.enabled"
+ name="mail.phishing.detection.enabled"
+ type="bool"/>
+ <preference id="mailnews.downloadToTempFile"
+ name="mailnews.downloadToTempFile"
+ type="bool"/>
+ </preferences>
+
+ <stringbundleset id="junkBundleset">
+ <stringbundle id="bundleJunkPreferences"
+ src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+
+ <groupbox>
+ <caption label="&junkSettings.caption;"/>
+ <description>&junkMail.intro;</description>
+ <class separator="thin"/>
+
+ <checkbox id="manualMark"
+ label="&manualMark.label;"
+ accesskey="&manualMark.accesskey;"
+ preference="mail.spam.manualMark"/>
+ <radiogroup id="manualMarkMode"
+ class="indent"
+ aria-labelledby="manualMark"
+ preference="mail.spam.manualMarkMode">
+ <radio id="manualMarkMode0"
+ label="&manualMarkModeMove.label;"
+ accesskey="&manualMarkModeMove.accesskey;"
+ value="0"/>
+ <radio id="manualMarkMode1"
+ label="&manualMarkModeDelete.label;"
+ accesskey="&manualMarkModeDelete.accesskey;"
+ value="1"/>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <description>&markAsRead.intro;</description>
+ <vbox class="indent">
+ <checkbox id="autoMarkAsRead"
+ label="&autoMarkAsRead.label;"
+ accesskey="&autoMarkAsRead.accesskey;"
+ preference="mail.spam.markAsReadOnSpam"/>
+ <checkbox id="manualMarkAsRead"
+ label="&manualMarkAsRead.label;"
+ accesskey="&manualMarkAsRead.accesskey;"
+ preference="mailnews.ui.junk.manualMarkAsJunkMarksRead"/>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <hbox align="start">
+ <checkbox id="enableJunkLogging"
+ label="&enableJunkLogging.label;"
+ accesskey="&enableJunkLogging.accesskey;"
+ preference="mail.spam.logging.enabled"/>
+ <spacer flex="1"/>
+ <button id="openJunkLog"
+ label="&openJunkLog.label;"
+ accesskey="&openJunkLog.accesskey;"
+ preference="pref.junk.disable_button.openJunkLog"
+ oncommand="OpenJunkLog();"/>
+ </hbox>
+ <hbox align="start">
+ <spacer flex="1"/>
+ <button id="resetTrainingData"
+ label="&resetTrainingData.label;"
+ accesskey="&resetTrainingData.accesskey;"
+ preference="pref.junk.disable_button.resetTrainingData"
+ oncommand="ResetTrainingData();"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&pref.suspectMail.caption;"/>
+
+ <checkbox id="enablePhishingDetector"
+ label="&enablePhishingDetector.label;"
+ accesskey="&enablePhishingDetector.accesskey;"
+ preference="mail.phishing.detection.enabled"/>
+
+ <separator class="thin"/>
+
+ <checkbox id="enableAntiVirusQuarantine"
+ label="&antiVirus.label;"
+ accesskey="&antiVirus.accesskey;"
+ preference="mailnews.downloadToTempFile"/>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-mailnews.js b/comm/suite/mailnews/components/prefs/content/pref-mailnews.js
new file mode 100644
index 0000000000..f057fb46ae
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-mailnews.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function Startup()
+{
+ startPageCheck();
+}
+
+function startPageCheck()
+{
+ var checked = document.getElementById("mailnews.start_page.enabled").value;
+ var urlElement = document.getElementById("mailnewsStartPageUrl");
+ var prefLocked = document.getElementById("mailnews.start_page.url").locked;
+
+ urlElement.disabled = !checked || prefLocked;
+}
+
+function setHomePageToDefaultPage()
+{
+ var startPagePref = document.getElementById("mailnews.start_page.url");
+
+ startPagePref.value = startPagePref.defaultValue;
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-mailnews.xul b/comm/suite/mailnews/components/prefs/content/pref-mailnews.xul
new file mode 100644
index 0000000000..e3fbeb12e8
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-mailnews.xul
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % prefMailnewsDTD SYSTEM "chrome://messenger/locale/pref-mailnews.dtd">
+%prefMailnewsDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="mailnews_pane"
+ label="&pref.mailnews.title;"
+ script="chrome://messenger/content/pref-mailnews.js">
+
+ <preferences id="mailnews_preferences">
+ <preference id="mailnews.confirm.moveFoldersToTrash"
+ name="mailnews.confirm.moveFoldersToTrash" type="bool"/>
+ <preference id="mailnews.remember_selected_message"
+ name="mailnews.remember_selected_message" type="bool"/>
+ <preference id="mailnews.thread_pane_column_unthreads"
+ name="mailnews.thread_pane_column_unthreads"
+ inverted="true" type="bool"/>
+ <preference id="mail.tabs.autoHide"
+ name="mail.tabs.autoHide"
+ type="bool"/>
+ <preference id="mail.tabs.loadInBackground"
+ name="mail.tabs.loadInBackground"
+ inverted="true" type="bool"/>
+ <preference id="mail.biff.on_new_window"
+ name="mail.biff.on_new_window"
+ type="bool"
+ inverted="true"/>
+ <preference id="mail.tabs.opentabfor.middleclick"
+ name="mail.tabs.opentabfor.middleclick"
+ type="bool"/>
+ <preference id="mail.tabs.opentabfor.doubleclick"
+ name="mail.tabs.opentabfor.doubleclick"
+ type="bool"/>
+ <preference id="mailnews.start_page.enabled"
+ onchange="this.parentNode.parentNode.startPageCheck();"
+ name="mailnews.start_page.enabled" type="bool"/>
+ <preference id="mailnews.start_page.url"
+ name="mailnews.start_page.url" type="wstring"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&generalSettings.caption;"/>
+
+ <hbox align="center">
+ <checkbox id="mailnewsConfirmMoveFoldersToTrash" label="&confirmMove.label;"
+ preference="mailnews.confirm.moveFoldersToTrash"
+ accesskey="&confirmMove.accesskey;"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="mailRememberLastMsg" label="&rememberLastMsg.label;"
+ preference="mailnews.remember_selected_message"
+ accesskey="&rememberLastMsg.accesskey;" />
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="mailPreserveThreading"
+ label="&preserveThreading.label;"
+ accesskey="&preserveThreading.accesskey;"
+ preference="mailnews.thread_pane_column_unthreads"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="mailAutoHide"
+ label="&mailAutoHide.label;"
+ accesskey="&mailAutoHide.accesskey;"
+ preference="mail.tabs.autoHide"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="loadInBackground"
+ label="&loadInBackground.label;"
+ accesskey="&loadInBackground.accesskey;"
+ preference="mail.tabs.loadInBackground"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="mailBiffOnNewWindow"
+ label="&mailBiffOnNewWindow.label;"
+ accesskey="&mailBiffOnNewWindow.accesskey;"
+ preference="mail.biff.on_new_window"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox id="mailOpenTabFor" align="start">
+ <caption label="&mailOpenTabsFor.label;"/>
+ <hbox align="center">
+ <checkbox id="mailMiddleClick"
+#ifndef XP_MACOSX
+ label="&mailMiddleClick.label;"
+ accesskey="&mailMiddleClick.accesskey;"
+#else
+ label="&mailMiddleClickMac.label;"
+ accesskey="&mailMiddleClickMac.accesskey;"
+#endif
+ preference="mail.tabs.opentabfor.middleclick"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="mailDoubleClick"
+ label="&mailDoubleClick.label;"
+ accesskey="&mailDoubleClick.accesskey;"
+ preference="mail.tabs.opentabfor.doubleclick"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&messengerStartPage.caption;"/>
+ <hbox align="center">
+ <checkbox id="mailnewsStartPageEnabled" label="&enableStartPage.label;"
+ preference="mailnews.start_page.enabled"
+ accesskey="&enableStartPage.accesskey;"/>
+ </hbox>
+
+ <hbox align="center">
+ <label value="&location.label;" accesskey="&location.accesskey;"
+ control="mailnewsStartPageUrl"/>
+ <textbox id="mailnewsStartPageUrl" flex="1" type="autocomplete"
+ preference="mailnews.start_page.url" timeout="50"
+ autocompletesearch="history" maxrows="6" class="uri-element"/>
+ </hbox>
+ <hbox pack="end">
+ <button label="&useDefault.label;" accesskey="&useDefault.accesskey;"
+ oncommand="setHomePageToDefaultPage();">
+ <observes element="mailnewsStartPageUrl" attribute="disabled"/>
+ </button>
+ </hbox>
+
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-notifications.js b/comm/suite/mailnews/components/prefs/content/pref-notifications.js
new file mode 100644
index 0000000000..89191e7cd6
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-notifications.js
@@ -0,0 +1,91 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The contents of this file will be loaded into the scope of the object
+// <prefpane id="notifications_pane">!
+
+var {AppConstants} = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+var gSoundUrlPref = null;
+
+function Startup()
+{
+ // if we don't have the alert service, hide the pref UI for using alerts to notify on new mail
+ // see bug #158711
+ var newMailNotificationAlertUI = document.getElementById("newMailNotificationAlertBox");
+ newMailNotificationAlertUI.hidden = !("@mozilla.org/alerts-service;1" in Cc);
+
+ // as long as the old notification code is still around, the new options
+ // won't apply if mail.biff.show_new_alert is false and should be hidden
+ document.getElementById("showAlertPreviewText").hidden =
+ document.getElementById("showAlertSubject").hidden =
+ document.getElementById("showAlertSender").hidden =
+ !Services.prefs.getBoolPref("mail.biff.show_new_alert");
+
+ // animate dock icon option currently available for macOS only
+ var newMailNotificationBouncePref = document.getElementById("newMailNotificationBounceBox");
+ newMailNotificationBouncePref.hidden = AppConstants.platform != "macosx";
+
+ // show tray icon option currently available for Windows only
+ var newMailNotificationTrayIconPref = document.getElementById("newMailNotificationTrayIconBox");
+ newMailNotificationTrayIconPref.hidden = AppConstants.platform != "win";
+
+ // use system alert option currently available for Linux only
+ var useSystemAlertPref = document.getElementById("useSystemAlertBox");
+ useSystemAlertPref.hidden = AppConstants.platform != "linux";
+
+ EnableAlert(document.getElementById("mail.biff.show_alert").value, false);
+ EnableTrayIcon(document.getElementById("mail.biff.show_tray_icon").value);
+
+ gSoundUrlPref = document.getElementById("mail.biff.play_sound.url");
+
+ PlaySoundCheck(document.getElementById("mail.biff.play_sound").value);
+}
+
+function EnableAlert(aEnable, aFocus)
+{
+ // switch off the balloon on Windows if the user wants regular alerts
+ if (aEnable && AppConstants.platform == "win") {
+ let balloonAlert = document.getElementById("mail.biff.show_balloon");
+ if (!balloonAlert.locked)
+ balloonAlert.value = false;
+ }
+
+ EnableElementById("showAlertTime", aEnable, aFocus);
+ EnableElementById("showAlertPreviewText", aEnable, false);
+ EnableElementById("showAlertSubject", aEnable, false);
+ EnableElementById("showAlertSender", aEnable, false);
+ EnableElementById("useSystemAlertRadio", aEnable, false);
+}
+
+function EnableTrayIcon(aEnable)
+{
+ EnableElementById("newMailNotificationBalloon", aEnable, false);
+}
+
+function ClearAlert(aEnable)
+{
+ // switch off the regular alerts if the user wants the balloon
+ if (aEnable && AppConstants.platform == "win") {
+ let showAlert = document.getElementById("mail.biff.show_alert");
+ if (!showAlert.locked)
+ showAlert.value = false;
+ }
+}
+
+function PlaySoundCheck(aPlaySound)
+{
+ let playSoundType = document.getElementById("mail.biff.play_sound.type").value;
+
+ EnableElementById("newMailNotificationType", aPlaySound, false);
+ EnableSoundURL(aPlaySound && (playSoundType == 1));
+}
+
+function EnableSoundURL(aEnable)
+{
+ EnableElementById("mailnewsSoundFileUrl", aEnable, false);
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-notifications.xul b/comm/suite/mailnews/components/prefs/content/pref-notifications.xul
new file mode 100644
index 0000000000..20ac974050
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-notifications.xul
@@ -0,0 +1,187 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % prefNotificationsDTD SYSTEM "chrome://messenger/locale/pref-notifications.dtd">
+%prefNotificationsDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="notifications_pane"
+ label="&pref.notifications.title;"
+ script="chrome://messenger/content/pref-notifications.js">
+
+ <preferences id="notifications_preferences">
+ <preference id="mail.biff.show_alert"
+ name="mail.biff.show_alert"
+ type="bool"
+ onchange="EnableAlert(this.value, this.value);"/>
+ <preference id="alerts.totalOpenTime"
+ name="alerts.totalOpenTime"
+ type="int"/>
+ <preference id="mail.biff.alert.show_preview"
+ name="mail.biff.alert.show_preview"
+ type="bool"/>
+ <preference id="mail.biff.alert.show_subject"
+ name="mail.biff.alert.show_subject"
+ type="bool"/>
+ <preference id="mail.biff.alert.show_sender"
+ name="mail.biff.alert.show_sender"
+ type="bool"/>
+ <preference id="mail.biff.use_system_alert"
+ name="mail.biff.use_system_alert"
+ type="bool"/>
+ <preference id="mail.biff.show_tray_icon"
+ name="mail.biff.show_tray_icon"
+ type="bool"
+ onchange="EnableTrayIcon(this.value);"/>
+ <preference id="mail.biff.show_balloon"
+ name="mail.biff.show_balloon"
+ type="bool"
+ onchange="ClearAlert(this.value);"/>
+ <preference id="mail.biff.animate_dock_icon"
+ name="mail.biff.animate_dock_icon"
+ type="bool"/>
+ <preference id="mail.biff.play_sound"
+ name="mail.biff.play_sound"
+ type="bool"
+ onchange="PlaySoundCheck(this.value);"/>
+ <preference id="mail.biff.play_sound.type"
+ name="mail.biff.play_sound.type"
+ type="int"
+ onchange="EnableSoundURL(this.value == 1);"/>
+ <preference id="mail.biff.play_sound.url"
+ name="mail.biff.play_sound.url"
+ type="string"/>
+ </preferences>
+
+ <groupbox id="newMessagesArrivePrefs">
+ <caption label="&notifications.caption;"/>
+
+ <label value="&newMessagesArrive.label;"/>
+ <vbox id="newMailNotificationAlertBox">
+ <hbox align="center">
+ <checkbox id="newMailNotificationAlert"
+ label="&showAlertFor.label;"
+ accesskey="&showAlertFor.accesskey;"
+ preference="mail.biff.show_alert"/>
+ <textbox id="showAlertTime"
+ type="number"
+ size="3"
+ min="1"
+ max="3600"
+ preference="alerts.totalOpenTime"
+ onsyncfrompreference="return document.getElementById(this.getAttribute('preference')).value / 1000;"
+ onsynctopreference="return this.value * 1000;"
+ aria-labelledby="newMailNotificationAlert showAlertTime showAlertTimeEnd"/>
+ <label id="showAlertTimeEnd"
+ value="&showAlertTimeEnd.label;">
+ <observes element="newMailNotificationAlert"
+ attribute="disabled"/>
+ </label>
+ </hbox>
+ <vbox id="showAlertOptionsBox"
+ class="indent">
+ <checkbox id="showAlertPreviewText"
+ label="&showAlertPreviewText.label;"
+ accesskey="&showAlertPreviewText.accesskey;"
+ preference="mail.biff.alert.show_preview"/>
+ <checkbox id="showAlertSubject"
+ label="&showAlertSubject.label;"
+ accesskey="&showAlertSubject.accesskey;"
+ preference="mail.biff.alert.show_subject"/>
+ <checkbox id="showAlertSender"
+ label="&showAlertSender.label;"
+ accesskey="&showAlertSender.accesskey;"
+ preference="mail.biff.alert.show_sender"/>
+ <separator id="newMailNotificationAlertSeparator"
+ class="thin"/>
+ <vbox id="useSystemAlertBox">
+ <radiogroup id="useSystemAlertRadio"
+ preference="mail.biff.use_system_alert">
+ <radio id="useSystemAlert"
+ value="true"
+ label="&useSystemAlert.label;"
+ accesskey="&useSystemAlert.accesskey;"/>
+ <radio id="useBuiltInAlert"
+ value="false"
+ label="&useBuiltInAlert.label;"
+ accesskey="&useBuiltInAlert.accesskey;"/>
+ </radiogroup>
+ <separator id="useSystemAlertSeparator"
+ class="thin"/>
+ </vbox>
+ </vbox>
+ </vbox>
+ <vbox id="newMailNotificationTrayIconBox">
+ <checkbox id="newMailNotificationTrayIcon"
+ label="&showTrayIcon.label;"
+ accesskey="&showTrayIcon.accesskey;"
+ preference="mail.biff.show_tray_icon"/>
+ <checkbox id="newMailNotificationBalloon"
+ class="indent"
+ label="&showBalloon.label;"
+ accesskey="&showBalloon.accesskey;"
+ preference="mail.biff.show_balloon"/>
+ <separator id="newMailNotificationTrayIconSeparator"
+ class="thin"/>
+ </vbox>
+ <vbox id="newMailNotificationBounceBox">
+ <checkbox id="newMailNotificationBounce"
+ label="&bounceSystemDockIcon.label;"
+ accesskey="&bounceSystemDockIcon.accesskey;"
+ preference="mail.biff.animate_dock_icon"/>
+ <separator id="newMailNotificationBounceSeparator"
+ class="thin"/>
+ </vbox>
+ <checkbox id="newMailNotification"
+ label="&playSound.label;"
+ accesskey="&playSound.accesskey;"
+ preference="mail.biff.play_sound"/>
+ <radiogroup id="newMailNotificationType"
+ preference="mail.biff.play_sound.type"
+ class="indent"
+ aria-labelledby="newMailNotification">
+ <radio id="system"
+ value="0"
+ label="&systemsound.label;"
+ accesskey="&systemsound.accesskey;"/>
+ <radio id="custom"
+ value="1"
+ label="&customsound.label;"
+ accesskey="&customsound.accesskey;"/>
+ </radiogroup>
+
+ <hbox align="center" class="indent">
+ <filefield id="mailnewsSoundFileUrl"
+ flex="1"
+ preference="mail.biff.play_sound.url"
+ preference-editable="true"
+ onsyncfrompreference="return WriteSoundField(this, document.getElementById('notifications_pane').gSoundUrlPref.value);"
+ aria-labelledby="custom"/>
+ <hbox align="center">
+ <button id="browse"
+ label="&browse.label;"
+ filepickertitle="&browse.title;"
+ accesskey="&browse.accesskey;"
+ oncommand="SelectSound(gSoundUrlPref);">
+ <observes element="mailnewsSoundFileUrl" attribute="disabled"/>
+ </button>
+ <button id="playButton"
+ label="&playButton.label;"
+ accesskey="&playButton.accesskey;"
+ oncommand="PlaySound(gSoundUrlPref.value, true);">
+ <observes element="mailnewsSoundFileUrl" attribute="disabled"/>
+ </button>
+ </hbox>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-offline.js b/comm/suite/mailnews/components/prefs/content/pref-offline.js
new file mode 100644
index 0000000000..d453c8f97a
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-offline.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The contents of this file will be loaded into the scope of the object
+// <prefpane id="offline_pane">!
+
+function Startup()
+{
+ var value = document.getElementById("mail.prompt_purge_threshhold").value;
+ EnableElementById("offlineCompactFolderMin", value, false);
+}
+
+function EnableMailPurgeThreshold(aValue)
+{
+ var focus = (document.getElementById("offlineCompactFolder") == document.commandDispatcher.focusedElement);
+ EnableElementById("offlineCompactFolderMin", aValue, focus);
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-offline.xul b/comm/suite/mailnews/components/prefs/content/pref-offline.xul
new file mode 100644
index 0000000000..49e4288ab0
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-offline.xul
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/pref-offline.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="offline_pane"
+ label="&pref.network.title;"
+ script="chrome://messenger/content/pref-offline.js">
+
+ <preferences id="offline_preferences">
+ <preference id="offline.startup_state"
+ name="offline.startup_state"
+ type="int"/>
+ <preference id="offline.send.unsent_messages"
+ name="offline.send.unsent_messages"
+ type="int"/>
+ <preference id="offline.download.download_messages"
+ name="offline.download.download_messages"
+ type="int"/>
+ <preference id="mailnews.tcptimeout"
+ name="mailnews.tcptimeout"
+ type="int"/>
+ <preference id="mail.prompt_purge_threshhold"
+ name="mail.prompt_purge_threshhold"
+ type="bool"
+ onchange="EnableMailPurgeThreshold(this.value);"/>
+ <preference id="mail.purge_threshhold_mb"
+ name="mail.purge_threshhold_mb"
+ type="int"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&pref.offline.caption;"/>
+
+ <hbox align="center">
+ <label value="&textStartUp;" control="whenStartingUp"
+ accesskey="&textStartUp.accesskey;"/>
+ <menulist id="whenStartingUp" preference="offline.startup_state">
+ <menupopup>
+ <menuitem value="0" label="&menuitemRememberPrevState;"/>
+ <menuitem value="1" label="&menuitemAskMe;"/>
+ <menuitem value="2" label="&menuitemAlwaysOnline;"/>
+ <menuitem value="3" label="&menuitemAlwaysOffline;"/>
+ <menuitem value="4" label="&menuitemAutomatic;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <separator/>
+
+ <label value="&textGoingOnline;" control="whengoingOnlinestate"/>
+ <radiogroup id="whengoingOnlinestate"
+ orient="horizontal" class="indent"
+ preference="offline.send.unsent_messages">
+ <radio value="1" label="&radioAutoSend;"
+ accesskey="&radioAutoSend.accesskey;"/>
+ <radio value="2" label="&radioNotSend;"
+ accesskey="&radioNotSend.accesskey;"/>
+ <radio value="0" label="&radioAskUnsent;"
+ accesskey="&radioAskUnsent.accesskey;"/>
+ </radiogroup>
+
+ <separator/>
+
+ <label value="&textGoingOffline;" control="whengoingOfflinestate"/>
+ <radiogroup id="whengoingOfflinestate"
+ orient="horizontal" class="indent"
+ preference="offline.download.download_messages">
+ <radio value="1" label="&radioAutoDownload;"
+ accesskey="&radioAutoDownload.accesskey;"/>
+ <radio value="2" label="&radioNotDownload;"
+ accesskey="&radioNotDownload.accesskey;"/>
+ <radio value="0" label="&radioAskDownload;"
+ accesskey="&radioAskDownload.accesskey;"/>
+ </radiogroup>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&mailConnections.caption;"/>
+ <hbox align="center">
+ <label id="timeoutLabel"
+ value="&mailnewsTimeout.label;"
+ accesskey="&mailnewsTimeout.accesskey;"
+ control="mailnewsTimeoutSeconds"/>
+ <textbox id="mailnewsTimeoutSeconds"
+ type="number"
+ size="4"
+ preference="mailnews.tcptimeout"
+ aria-labelledby="timeoutLabel mailnewsTimeoutSeconds timeoutSeconds"/>
+ <label id="timeoutSeconds" value="&mailnewsTimeoutSeconds.label;"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&Diskspace;"/>
+ <hbox align="center">
+ <checkbox id="offlineCompactFolder"
+ label="&offlineCompactFolders.label;"
+ accesskey="&offlineCompactFolders.accesskey;"
+ preference="mail.prompt_purge_threshhold"
+ aria-labelledby="offlineCompactFolder offlineCompactFolderMin offlineCompactFolderMB"/>
+ <textbox id="offlineCompactFolderMin"
+ type="number"
+ size="4"
+ min="1"
+ max="2048"
+ increment="10"
+ value="20"
+ preference="mail.purge_threshhold_mb"
+ aria-labelledby="offlineCompactFolder offlineCompactFolderMin offlineCompactFolderMB"/>
+ <label id="offlineCompactFolderMB" value="&offlineCompactFoldersMB.label;"/>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-receipts.js b/comm/suite/mailnews/components/prefs/content/pref-receipts.js
new file mode 100644
index 0000000000..7a85ac1a1f
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-receipts.js
@@ -0,0 +1,28 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gNotInToCcLabel;
+var gOutsideDomainLabel;
+var gOtherCasesLabel;
+
+function Startup()
+{
+ gNotInToCcLabel = document.getElementById("notInToCcLabel");
+ gOutsideDomainLabel = document.getElementById("outsideDomainLabel");
+ gOtherCasesLabel = document.getElementById("otherCasesLabel");
+
+ var value = document.getElementById("mail.mdn.report.enabled").value;
+ EnableDisableAllowedReceipts(value);
+}
+
+function EnableDisableAllowedReceipts(aEnable)
+{
+ EnableElementById("notInToCcPref", aEnable, false);
+ EnableElementById("outsideDomainPref", aEnable, false);
+ EnableElementById("otherCasesPref", aEnable, false);
+ gNotInToCcLabel.disabled = !aEnable;
+ gOutsideDomainLabel.disabled = !aEnable;
+ gOtherCasesLabel.disabled = !aEnable;
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-receipts.xul b/comm/suite/mailnews/components/prefs/content/pref-receipts.xul
new file mode 100644
index 0000000000..0ca17a02ed
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-receipts.xul
@@ -0,0 +1,146 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/pref-receipts.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="receipts_pane"
+ label="&pref.receipts.title;"
+ script="chrome://messenger/content/pref-receipts.js">
+ <preferences id="receipts_preferences">
+ <preference id="mail.receipt.request_return_receipt_on"
+ name="mail.receipt.request_return_receipt_on"
+ type="bool"/>
+ <preference id="mail.incorporate.return_receipt"
+ name="mail.incorporate.return_receipt"
+ type="int"/>
+ <preference id="mail.mdn.report.enabled"
+ name="mail.mdn.report.enabled"
+ type="bool"
+ onchange="EnableDisableAllowedReceipts(this.value);"/>
+ <preference id="mail.mdn.report.not_in_to_cc"
+ name="mail.mdn.report.not_in_to_cc"
+ type="int"/>
+ <preference id="mail.mdn.report.outside_domain"
+ name="mail.mdn.report.outside_domain"
+ type="int"/>
+ <preference id="mail.mdn.report.other"
+ name="mail.mdn.report.other"
+ type="int"/>
+ </preferences>
+
+ <groupbox>
+ <caption label="&prefReceipts.caption;"/>
+
+ <vbox id="returnReceiptSettings" align="start">
+ <checkbox id="alwaysRequest"
+ label="&requestReceipt.label;"
+ accesskey="&requestReceipt.accesskey;"
+ preference="mail.receipt.request_return_receipt_on"/>
+
+ <separator/>
+
+ <vbox id="receiptArrive">
+ <label control="receiptFolder">&receiptArrive.label;</label>
+ <radiogroup id="receiptFolder"
+ class="indent"
+ preference="mail.incorporate.return_receipt">
+ <radio value="0"
+ label="&leaveIt.label;"
+ accesskey="&leaveIt.accesskey;"/>
+ <radio value="1"
+ label="&moveToSent.label;"
+ accesskey="&moveToSent.accesskey;"/>
+ </radiogroup>
+ </vbox>
+
+ <separator/>
+
+ <vbox id="receiptRequest">
+ <label control="receiptSend">&requestMDN.label;</label>
+ <radiogroup id="receiptSend"
+ class="indent"
+ preference="mail.mdn.report.enabled">
+ <radio value="false"
+ label="&never.label;"
+ accesskey="&never.accesskey;"/>
+ <radio value="true"
+ label="&returnSome.label;"
+ accesskey="&returnSome.accesskey;"/>
+
+ <hbox id="receiptSendIf" class="indent">
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label id="notInToCcLabel"
+ accesskey="&notInToCc.accesskey;"
+ control="notInToCcPref"
+ value="&notInToCc.label;"/>
+ <menulist id="notInToCcPref"
+ preference="mail.mdn.report.not_in_to_cc">
+ <menupopup>
+ <menuitem value="0"
+ label="&neverSend.label;"/>
+ <menuitem value="1"
+ label="&alwaysSend.label;"/>
+ <menuitem value="2"
+ label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label id="outsideDomainLabel"
+ accesskey="&outsideDomain.accesskey;"
+ control="outsideDomainPref"
+ value="&outsideDomain.label;"/>
+ <menulist id="outsideDomainPref"
+ preference="mail.mdn.report.outside_domain">
+ <menupopup>
+ <menuitem value="0"
+ label="&neverSend.label;"/>
+ <menuitem value="1"
+ label="&alwaysSend.label;"/>
+ <menuitem value="2"
+ label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <label id="otherCasesLabel"
+ accesskey="&otherCases.accesskey;"
+ control="otherCasesPref"
+ value="&otherCases.label;"/>
+ <menulist id="otherCasesPref"
+ preference="mail.mdn.report.other">
+ <menupopup>
+ <menuitem value="0"
+ label="&neverSend.label;"/>
+ <menuitem value="1"
+ label="&alwaysSend.label;"/>
+ <menuitem value="2"
+ label="&askMe.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ </radiogroup>
+
+ </vbox>
+
+ </vbox>
+
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-tags.js b/comm/suite/mailnews/components/prefs/content/pref-tags.js
new file mode 100644
index 0000000000..8182fe7237
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-tags.js
@@ -0,0 +1,478 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// Each tag entry in our list looks like this:
+// <listitem>
+// <listcell>
+// <textbox/>
+// </listcell>
+// <listcell>
+// <colorpicker type='button'/>
+// </listcell>
+// </listitem>
+// For ease of handling, all tag data is stored in <listitem>.tagInfo also.
+
+const kOrdinalCharLow = "a";
+const kOrdinalCharHigh = "z";
+const kOrdinalPadding = String.fromCharCode(kOrdinalCharLow.charCodeAt(0) - 1);
+
+var gInstantApply = document.documentElement.instantApply; // read only once
+var gTagList = null; // tagList root element
+var gAddButton = null;
+var gDeleteButton = null;
+var gRaiseButton = null;
+var gLowerButton = null;
+
+var gDeletedTags = {}; // tags marked for deletion in non-instant apply mode
+
+
+function Startup()
+{
+ gTagList = document.getElementById('tagList');
+ gAddButton = document.getElementById('addTagButton');
+ gDeleteButton = document.getElementById('deleteTagButton');
+ gRaiseButton = document.getElementById('raiseTagButton');
+ gLowerButton = document.getElementById('lowerTagButton');
+ InitTagList();
+ if (!gInstantApply)
+ window.addEventListener("dialogaccept", this.OnOK, true);
+ UpdateButtonStates();
+}
+
+function InitTagList()
+{
+ // Read the tags from preferences via the tag service.
+ var tagArray = MailServices.tags.getAllTags();
+ for (var i = 0; i < tagArray.length; ++i)
+ {
+ var t = tagArray[i];
+ var tagInfo = {tag: t.tag,
+ key: t.key,
+ color: t.color,
+ ordinal: t.ordinal,
+ new: false, // not added in this run
+ changed: false}; // not changed (yet)
+ AppendTagEntry(tagInfo, null);
+ }
+}
+
+// read text and color from the listitem
+function UpdateTagInfo(aTagInfo, aEntry)
+{
+ var tag = aEntry.firstChild.firstChild.value;
+ var color = aEntry.lastChild.lastChild.color;
+ if (tag != aTagInfo.tag || color != aTagInfo.color)
+ {
+ aTagInfo.changed = true; // never unset changed flag here!
+ aTagInfo.tag = tag;
+ aTagInfo.color = color;
+ }
+}
+
+// set text and color of the listitem
+function UpdateTagEntry(aTagInfo, aEntry)
+{
+ aEntry.firstChild.firstChild.value = aTagInfo.tag;
+ aEntry.lastChild.lastChild.color = aTagInfo.color || 'inherit';
+}
+
+function AppendTagEntry(aTagInfo, aRefChild)
+{
+ // Creating a colorpicker dynamically in an onload handler is really sucky.
+ // You MUST first set its type attribute (to select the correct binding), then
+ // add the element to the DOM (to bind the binding) and finally set the color
+ // property(!) afterwards. Try in any other order and fail... :-(
+ var tagCell = document.createElement('listcell');
+ var textbox = document.createElement('textbox');
+ textbox.setAttribute('flex', 1);
+ textbox.setAttribute('value', aTagInfo.tag);
+ tagCell.appendChild(textbox);
+
+ var colorCell = document.createElement('listcell');
+ var colorpicker = document.createElement('colorpicker');
+ colorpicker.setAttribute('type', 'button');
+ colorpicker.setAttribute('color', aTagInfo.color || 'inherit')
+ colorCell.appendChild(colorpicker);
+
+ var entry = document.createElement('listitem');
+ entry.addEventListener('focus', OnFocus, true);
+ entry.addEventListener('change', OnChange);
+ entry.setAttribute('allowevents', 'true'); // activate textbox and colorpicker
+ entry.tagInfo = aTagInfo;
+ entry.appendChild(tagCell);
+ entry.appendChild(colorCell);
+
+ gTagList.insertBefore(entry, aRefChild);
+ return entry;
+}
+
+function OnFocus(aEvent)
+{
+ gTagList.selectedItem = this;
+ UpdateButtonStates();
+}
+
+function FocusTagEntry(aEntry)
+{
+ // focus the entry's textbox
+ gTagList.ensureElementIsVisible(aEntry);
+ aEntry.firstChild.firstChild.focus();
+}
+
+function GetTagOrdinal(aTagInfo)
+{
+ if (aTagInfo.ordinal)
+ return aTagInfo.ordinal;
+ return aTagInfo.key;
+}
+
+function SetTagOrdinal(aTagInfo, aOrdinal)
+{
+ var ordinal = aTagInfo.ordinal;
+ aTagInfo.ordinal = (aTagInfo.key != aOrdinal) ? aOrdinal : '';
+ if (aTagInfo.ordinal != ordinal)
+ aTagInfo.changed = true;
+}
+
+function BisectString(aPrev, aNext)
+{
+ // find a string which is lexically greater than aPrev and lesser than aNext:
+ // - copy leading parts common to aPrev and aNext into the result
+ // - find the first position where aPrev and aNext differ:
+ // - if we can squeeze a character in between there: fine, done!
+ // - if not:
+ // - if the rest of aNext is longer than one character, we can squeeze
+ // in just the first aNext rest-character and be done!
+ // - else we try to "increment" aPrev a bit to fit in
+ if ((aPrev >= aNext) || (aPrev + kOrdinalCharLow >= aNext))
+ return ''; // no such string exists
+
+ // pad the shorter string
+ var lenPrev = aPrev.length;
+ var lenNext = aNext.length;
+ var lenMax = Math.max(lenPrev, lenNext);
+
+ // loop over both strings at once, padding if necessary
+ var constructing = false;
+ var result = '';
+ for (var i = 0; i < lenMax; ++i)
+ {
+ var prevChar = (i < lenPrev) ? aPrev[i] : kOrdinalPadding;
+ var nextChar = constructing ? kOrdinalCharHigh
+ : (i < lenNext) ? aNext[i]
+ : kOrdinalPadding;
+ var prevCode = prevChar.charCodeAt(0);
+ var nextCode = nextChar.charCodeAt(0);
+ if (prevCode == nextCode)
+ {
+ // copy common characters
+ result += prevChar;
+ }
+ else if (prevCode + 1 < nextCode)
+ {
+ // found a real bisecting string
+ result += String.fromCharCode((prevCode + nextCode) / 2);
+ return result;
+ }
+ else
+ {
+ // nextCode is greater than prevCode, but there's no place in between.
+ // But if aNext[i+1] exists, then nextChar will suffice and we're done!
+ // ("x" < "xsomething")
+ if (i + 1 < lenNext)
+ {
+ // found a real bisecting string
+ return result + nextChar;
+ }
+ // just copy over prevChar and enter construction mode
+ result += prevChar;
+ constructing = true;
+ }
+ }
+ return ''; // nothing found
+}
+
+function RecalculateOrdinal(aEntry)
+{
+ // Calculate a new ordinal for the given entry, assuming that both its
+ // predecessor's and successor's are correct, i.e. ord(p) < ord(s)!
+ var tagInfo = aEntry.tagInfo;
+ var ordinal = tagInfo.key;
+ // get neighbouring ordinals
+ var prevOrdinal = '', nextOrdinal = '';
+ var prev = aEntry.previousSibling;
+ if (prev && prev.nodeName == 'listitem') // first.prev == listhead
+ prevOrdinal = GetTagOrdinal(prev.tagInfo);
+ var next = aEntry.nextSibling;
+ if (next)
+ {
+ nextOrdinal = GetTagOrdinal(next.tagInfo);
+ }
+ else
+ {
+ // ensure key < nextOrdinal if entry is the last/only entry
+ nextOrdinal = prevOrdinal || ordinal;
+ nextOrdinal = String.fromCharCode(nextOrdinal.charCodeAt(0) + 2);
+ }
+
+ if (prevOrdinal < ordinal && ordinal < nextOrdinal)
+ {
+ // no ordinal needed, just clear it
+ SetTagOrdinal(tagInfo, '')
+ return;
+ }
+
+ // so we need a new ordinal, because key <= prevOrdinal or key >= nextOrdinal
+ ordinal = BisectString(prevOrdinal, nextOrdinal);
+ if (ordinal)
+ {
+ // found a new ordinal
+ SetTagOrdinal(tagInfo, ordinal)
+ return;
+ }
+
+ // couldn't find an ordinal before the nextOrdinal, so take that instead
+ // and recalculate a new one for the next entry
+ SetTagOrdinal(tagInfo, nextOrdinal);
+ if (next)
+ ApplyChange(next);
+}
+
+function OnChange(aEvent)
+{
+ ApplyChange(aEvent.currentTarget);
+}
+
+function ApplyChange(aEntry)
+{
+ if (!aEntry)
+ {
+ dump('ApplyChange: aEntry is null! (called by ' + ApplyChange.caller.name + ')\n');
+ return;
+ }
+
+ // the tag data got changed, so write it back to the system
+ var tagInfo = aEntry.tagInfo;
+ UpdateTagInfo(tagInfo, aEntry);
+ // ensure unique tag name
+ var dupeList = ReadTagListFromUI(aEntry);
+ var uniqueTag = DisambiguateTag(tagInfo.tag, dupeList);
+ if (tagInfo.tag != uniqueTag)
+ {
+ tagInfo.tag = uniqueTag;
+ tagInfo.changed = true;
+ UpdateTagEntry(tagInfo, aEntry);
+ }
+
+ if (gInstantApply)
+ {
+ // If the item was newly added, we still can rename the key,
+ // so that it's in sync with the actual tag.
+ if (tagInfo.new && tagInfo.key)
+ {
+ // Do not clear the "new" flag!
+ // The key will only stick after closing the dialog.
+ MailServices.tags.deleteKey(tagInfo.key);
+ tagInfo.key = '';
+ }
+ if (!tagInfo.key)
+ {
+ // create a new key, based upon the new tag
+ MailServices.tags.addTag(tagInfo.tag, '', '');
+ tagInfo.key = MailServices.tags.getKeyForTag(tagInfo.tag);
+ }
+
+ // Recalculate the sort ordinal, if necessary.
+ // We assume that the neighbour's ordinals are correct,
+ // i.e. that ordinal(pos - 1) < ordinal(pos + 1)!
+ RecalculateOrdinal(aEntry);
+ WriteTag(tagInfo);
+ }
+}
+
+function WriteTag(aTagInfo)
+{
+//dump('********** WriteTag: ' + aTagInfo.toSource() + '\n');
+ try
+ {
+ MailServices.tags.addTagForKey(aTagInfo.key, aTagInfo.tag, aTagInfo.color,
+ aTagInfo.ordinal);
+ aTagInfo.changed = false;
+ }
+ catch (e)
+ {
+ dump('WriteTag: update exception:\n' + e);
+ }
+}
+
+function UpdateButtonStates()
+{
+ var entry = gTagList.selectedItem;
+ // disable Delete if no selection
+ gDeleteButton.disabled = !entry;
+ // disable Raise if no selection or first entry
+ gRaiseButton.disabled = !entry || !gTagList.getPreviousItem(entry, 1);
+ // disable Lower if no selection or last entry
+ gLowerButton.disabled = !entry || !gTagList.getNextItem(entry, 1);
+}
+
+function ReadTagListFromUI(aIgnoreEntry)
+{
+ // reads distinct tag names from the UI
+ var dupeList = {}; // indexed by tag
+ for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
+ if ((entry != aIgnoreEntry) && (entry.localName == 'listitem'))
+ dupeList[entry.firstChild.firstChild.value] = true;
+ return dupeList;
+}
+
+function DisambiguateTag(aTag, aTagList)
+{
+ if (aTag in aTagList)
+ {
+ var suffix = 2;
+ while (aTag + ' ' + suffix in aTagList)
+ ++suffix;
+ aTag += ' ' + suffix;
+ }
+ return aTag;
+}
+
+function AddTag()
+{
+ // Add a new tag to the UI here.
+ // It will be be written to the preference system
+ // (a) directly on each change for instant apply, or
+ // (b) only if the dialogaccept handler is executed.
+
+ // create new unique tag name
+ var dupeList = ReadTagListFromUI();
+ var tag = DisambiguateTag(gAddButton.getAttribute('defaulttagname'), dupeList);
+
+ // create new tag list entry
+ var tagInfo = {tag: tag,
+ key: '',
+ color: 'inherit',
+ ordinal: '',
+ new: true,
+ changed: true};
+ var refChild = gTagList.getNextItem(gTagList.selectedItem, 1);
+ var newEntry = AppendTagEntry(tagInfo, refChild);
+ ApplyChange(newEntry);
+ FocusTagEntry(newEntry);
+}
+
+function DeleteTag()
+{
+ // Delete the selected tag from the UI here. If it was added during this
+ // preference dialog session, we can drop it at once; if it was read from
+ // the preferences system, we may need to remember killing it in OnOK.
+ var entry = gTagList.selectedItem;
+ var key = entry.tagInfo.key;
+ if (key)
+ {
+ if (gInstantApply)
+ MailServices.tags.deleteKey(key);
+ else
+ gDeletedTags[key] = true; // dummy value
+ }
+ // after removing, move focus to next entry, if it exist, else try previous
+ var newFocusItem = gTagList.getNextItem(entry, 1) ||
+ gTagList.getPreviousItem(entry, 1);
+ gTagList.removeItemAt(gTagList.getIndexOfItem(entry));
+ if (newFocusItem)
+ FocusTagEntry(newFocusItem);
+ else
+ UpdateButtonStates();
+}
+
+function MoveTag(aMoveUp)
+{
+ // Move the selected tag one position up or down in the tagList's child order.
+ // This reordering may require changing ordinal strings.
+ var entry = gTagList.selectedItem;
+ var tagInfo = entry.tagInfo;
+ UpdateTagInfo(tagInfo, entry); // remember changed values
+ var successor = aMoveUp ? gTagList.getPreviousItem(entry, 1)
+ : gTagList.getNextItem(entry, 2);
+ entry.parentNode.insertBefore(entry, successor);
+ FocusTagEntry(entry);
+ tagInfo.changed = true;
+ UpdateTagEntry(tagInfo, entry); // needs to be visible
+ ApplyChange(entry);
+}
+
+function Restore()
+{
+ // clear pref panel tag list
+ // Remember any known keys for deletion in the OKHandler.
+ while (gTagList.getRowCount())
+ {
+ var key = gTagList.removeItemAt(0).tagInfo.key;
+ if (key)
+ {
+ if (gInstantApply)
+ MailServices.tags.deleteKey(key);
+ else
+ gDeletedTags[key] = true; // dummy value
+ }
+ }
+ // add default items (no ordinal strings for those)
+ for (var i = 1; i <= 5; ++i)
+ {
+ // create default tags from the former label defaults
+ var key = "$label" + i;
+ var tag = GetLocalizedStringPref("mailnews.labels.description." + i);
+ var color = Services.prefs.getDefaultBranch("mailnews.labels.color.").getCharPref(i);
+ var tagInfo = {tag: tag,
+ key: key,
+ color: color,
+ ordinal: '',
+ new: false,
+ changed: true};
+ var newEntry = AppendTagEntry(tagInfo, null);
+ ApplyChange(newEntry);
+ }
+ FocusTagEntry(gTagList.getItemAtIndex(0));
+}
+
+function OnOK()
+{
+ // remove all deleted tags from the preferences system
+ for (var key in gDeletedTags)
+ MailServices.tags.deleteKey(key);
+
+ // Write tags to the preferences system, creating keys and ordinal strings.
+ for (var entry = gTagList.firstChild; entry; entry = entry.nextSibling)
+ {
+ if (entry.localName == 'listitem')
+ {
+ // only write listitems which have changed (this includes new ones)
+ var tagInfo = entry.tagInfo;
+ if (tagInfo.changed)
+ {
+ if (!tagInfo.key)
+ {
+ // newly added tag, need to create a key and read it
+ MailServices.tags.addTag(tagInfo.tag, '', '');
+ tagInfo.key = MailServices.tags.getKeyForTag(tagInfo.tag);
+ }
+ if (tagInfo.key)
+ {
+ // Recalculate the sort ordinal, if necessary.
+ // We assume that the neighbour's ordinals are correct,
+ // i.e. that ordinal(pos - 1) < ordinal(pos + 1)!
+ RecalculateOrdinal(entry);
+ // update the tag definition
+ WriteTag(tagInfo);
+ }
+ }
+ }
+ }
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-tags.xul b/comm/suite/mailnews/components/prefs/content/pref-tags.xul
new file mode 100644
index 0000000000..cd99824a25
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-tags.xul
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/prefPanels.css" type="text/css"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/pref-tags.dtd">
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="tags_pane"
+ label="&pref.tags.title;"
+ script="chrome://messenger/content/pref-tags.js">
+
+ <preferences id="tags_preferences">
+ <preference id="pref.tags.disable_button.add"
+ name="pref.tags.disable_button.add"
+ type="bool"/>
+ <preference id="pref.tags.disable_button.delete"
+ name="pref.tags.disable_button.delete"
+ type="bool"/>
+ <preference id="pref.tags.disable_button.lower"
+ name="pref.tags.disable_button.lower"
+ type="bool"/>
+ <preference id="pref.tags.disable_button.raise"
+ name="pref.tags.disable_button.raise"
+ type="bool"/>
+ <preference id="pref.tags.disable_button.restore"
+ name="pref.tags.disable_button.restore"
+ type="bool"/>
+ </preferences>
+
+ <groupbox flex="1">
+ <caption label="&pref.tags.caption;"/>
+ <label control="tagList">&pref.tags.description;</label>
+ <hbox flex="1">
+ <listbox id="tagList" flex="1" onselect="UpdateButtonStates();">
+ <listcols>
+ <listcol flex="1"/>
+ <listcol/>
+ </listcols>
+ <listhead>
+ <listheader label="&tagColumn.label;"/>
+ <listheader label="&colorColumn.label;"/>
+ </listhead>
+ </listbox>
+
+ <vbox>
+ <button id="addTagButton"
+ label="&addTagButton.label;"
+ accesskey="&addTagButton.accesskey;"
+ defaulttagname="&defaultTagName.label;"
+ preference="pref.tags.disable_button.add"
+ oncommand="AddTag();"/>
+ <button id="deleteTagButton"
+ label="&deleteTagButton.label;"
+ accesskey="&deleteTagButton.accesskey;"
+ preference="pref.tags.disable_button.delete"
+ oncommand="DeleteTag();"/>
+ <spacer flex="1"/>
+ <button id="raiseTagButton"
+ label="&raiseTagButton.label;"
+ accesskey="&raiseTagButton.accesskey;"
+ preference="pref.tags.disable_button.raise"
+ oncommand="MoveTag(true);"/>
+ <button id="lowerTagButton"
+ label="&lowerTagButton.label;"
+ accesskey="&lowerTagButton.accesskey;"
+ preference="pref.tags.disable_button.lower"
+ oncommand="MoveTag(false);"/>
+ <spacer flex="1"/>
+ <button id="restoreButton"
+ label="&restoreButton.label;"
+ accesskey="&restoreButton.accesskey;"
+ preference="pref.tags.disable_button.restore"
+ oncommand="Restore();"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.js b/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.js
new file mode 100644
index 0000000000..75b5da1b3d
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The contents of this file will be loaded into the scope of the object
+// <prefpane id="viewing_messages_pane">!
+
+function Startup()
+{
+ var autoPref = document.getElementById("mailnews.mark_message_read.auto");
+ UpdateMarkAsReadOptions(autoPref.value);
+}
+
+function UpdateMarkAsReadOptions(aEnableReadDelay)
+{
+ EnableElementById("markAsReadAfterPreferences", aEnableReadDelay, false);
+ // ... and the extras!
+ var delayPref = document.getElementById("mailnews.mark_message_read.delay");
+ UpdateMarkAsReadTextbox(aEnableReadDelay && delayPref.value, false);
+}
+
+function UpdateMarkAsReadTextbox(aEnable, aFocus)
+{
+ EnableElementById("markAsReadDelay", aEnable, aFocus);
+}
diff --git a/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.xul b/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.xul
new file mode 100644
index 0000000000..117761c86b
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/content/pref-viewing_messages.xul
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % pref-viewing_messagesDTD SYSTEM "chrome://messenger/locale/pref-viewing_messages.dtd">
+%pref-viewing_messagesDTD;
+<!ENTITY % editorOverlayDTD SYSTEM "chrome://editor/locale/editorOverlay.dtd">
+%editorOverlayDTD;
+]>
+
+<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <prefpane id="viewing_messages_pane"
+ label="&pref.viewing.messages.title;"
+ script="chrome://messenger/content/pref-viewing_messages.js">
+
+ <preferences id="viewing_messages_preferences">
+ <preference id="mailnews.reuse_message_window"
+ name="mailnews.reuse_message_window"
+ type="bool"/>
+ <preference id="mail.close_message_window.on_delete"
+ name="mail.close_message_window.on_delete"
+ type="bool"/>
+ <preference id="mailnews.message_display.disable_remote_image"
+ name="mailnews.message_display.disable_remote_image"
+ type="bool"/>
+ <preference id="mailnews.mark_message_read.auto"
+ name="mailnews.mark_message_read.auto"
+ type="bool"
+ onchange="UpdateMarkAsReadOptions(this.value);"/>
+ <preference id="mailnews.mark_message_read.delay"
+ name="mailnews.mark_message_read.delay"
+ type="bool"
+ onchange="UpdateMarkAsReadTextbox(this.value, this.value);"/>
+ <preference id="mailnews.mark_message_read.delay.interval"
+ name="mailnews.mark_message_read.delay.interval"
+ type="int"/>
+ <preference id="mail.fixed_width_messages"
+ name="mail.fixed_width_messages"
+ type="bool"/>
+ <preference id="mail.wrap_long_lines"
+ name="mail.wrap_long_lines"
+ type="bool"/>
+ <preference id="mail.display_glyph"
+ name="mail.display_glyph"
+ type="bool"/>
+ <preference id="mail.quoted_style"
+ name="mail.quoted_style"
+ type="int"/>
+ <preference id="mail.quoted_size"
+ name="mail.quoted_size"
+ type="int"/>
+ <preference id="mail.citation_color"
+ name="mail.citation_color"
+ type="string"/>
+ <preference id="mail.showCondensedAddresses"
+ name="mail.showCondensedAddresses"
+ type="bool"/>
+ </preferences>
+
+ <groupbox align="start">
+ <caption label="&generalMessageDisplay.caption;"/>
+ <label value="&openingMessages.label;" control="reuseMessageWindow"/>
+ <vbox class="indent">
+ <radiogroup id="reuseMessageWindow"
+ orient="horizontal"
+ preference="mailnews.reuse_message_window">
+ <radio id="new"
+ label="&newWindowRadio.label;"
+ accesskey="&newWindowRadio.accesskey;"
+ value="false"/>
+ <radio id="existing"
+ label="&existingWindowRadio.label;"
+ accesskey="&existingWindowRadio.accesskey;"
+ value="true"/>
+ </radiogroup>
+ <checkbox id="closeMsgWindowOnDelete"
+ label="&closeMsgWindowOnDelete.label;"
+ accesskey="&closeMsgWindowOnDelete.accesskey;"
+ preference="mail.close_message_window.on_delete"/>
+ </vbox>
+
+ <checkbox id="disableContent" label="&disableContent.label;"
+ accesskey="&disableContent.accesskey;"
+ preference="mailnews.message_display.disable_remote_image"/>
+ <checkbox id="showCondensedAddresses"
+ label="&showCondensedAddresses.label;"
+ accesskey="&showCondensedAddresses.accesskey;"
+ preference="mail.showCondensedAddresses"/>
+
+ <separator class="thin"/>
+
+ <checkbox id="automaticallyMarkAsRead"
+ preference="mailnews.mark_message_read.auto"
+ label="&autoMarkAsRead.label;"
+ accesskey="&autoMarkAsRead.accesskey;"
+ oncommand="UpdateMarkAsReadOptions(this.checked);"/>
+
+ <hbox align="center" class="indent">
+ <checkbox id="markAsReadAfterPreferences"
+ label="&markAsReadAfter.label;"
+ accesskey="&markAsReadAfter.accesskey;"
+ preference="mailnews.mark_message_read.delay"/>
+ <textbox id="markAsReadDelay"
+ type="number"
+ size="2"
+ maximum="99"
+ preference="mailnews.mark_message_read.delay.interval"
+ aria-labelledby="markAsReadAfterPreferences markAsReadDelay secondsLabel"/>
+ <label id="secondsLabel"
+ value="&secondsLabel.label;">
+ <observes element="markAsReadAfterPreferences"
+ attribute="disabled"/>
+ </label>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&displayPlainText.caption;"/>
+ <hbox align="center">
+ <label value="&fontPlainText.label;"
+ accesskey="&fontPlainText.accesskey;"
+ control="mailFixedWidthMessages"/>
+ <radiogroup id="mailFixedWidthMessages"
+ orient="horizontal"
+ preference="mail.fixed_width_messages">
+ <radio label="&fontFixedWidth.label;"
+ accesskey="&fontFixedWidth.accesskey;"
+ value="true"/>
+ <radio label="&fontVarWidth.label;"
+ accesskey="&fontVarWidth.accesskey;"
+ value="false"/>
+ </radiogroup>
+ </hbox>
+
+ <checkbox id="wrapLongLines" label="&wrapInMsg.label;"
+ accesskey="&wrapInMsg.accesskey;"
+ preference="mail.wrap_long_lines"/>
+ <checkbox id="displayGlyph" label="&convertEmoticons.label;"
+ accesskey="&convertEmoticons.accesskey;"
+ preference="mail.display_glyph"/>
+
+ <separator class="thin"/>
+
+ <description>&displayQuoted.label;</description>
+ <hbox class="indent" align="center">
+ <label value="&style.label;" accesskey="&style.accesskey;" control="mailQuotedStyle"/>
+ <menulist id="mailQuotedStyle" preference="mail.quoted_style">
+ <menupopup>
+ <menuitem value="0" label="&regular.label;"/>
+ <menuitem value="1" label="&bold.label;"/>
+ <menuitem value="2" label="&italic.label;"/>
+ <menuitem value="3" label="&boldItalic.label;"/>
+ </menupopup>
+ </menulist>
+
+ <label value="&size.label;" accesskey="&size.accesskey;" control="mailQuotedSize"/>
+ <menulist id="mailQuotedSize" preference="mail.quoted_size">
+ <menupopup>
+ <menuitem value="0" label="&regular.label;"/>
+ <menuitem value="1" label="&bigger.label;"/>
+ <menuitem value="2" label="&smaller.label;"/>
+ </menupopup>
+ </menulist>
+
+ <label value="&color.label;" accesskey="&color.accesskey;" control="mailCitationColor"/>
+ <colorpicker type="button" id="mailCitationColor" palettename="standard"
+ preference="mail.citation_color"/>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</overlay>
diff --git a/comm/suite/mailnews/components/prefs/jar.mn b/comm/suite/mailnews/components/prefs/jar.mn
new file mode 100644
index 0000000000..9baa230079
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/jar.mn
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+ content/messenger/mailPrefsOverlay.xul (content/mailPrefsOverlay.xul)
+* content/messenger/pref-mailnews.xul (content/pref-mailnews.xul)
+ content/messenger/pref-mailnews.js (content/pref-mailnews.js)
+ content/messenger/pref-notifications.xul (content/pref-notifications.xul)
+ content/messenger/pref-notifications.js (content/pref-notifications.js)
+ content/messenger/pref-junk.xul (content/pref-junk.xul)
+ content/messenger/pref-junk.js (content/pref-junk.js)
+ content/messenger/pref-tags.xul (content/pref-tags.xul)
+ content/messenger/pref-tags.js (content/pref-tags.js)
+ content/messenger/pref-viewing_messages.xul (content/pref-viewing_messages.xul)
+ content/messenger/pref-viewing_messages.js (content/pref-viewing_messages.js)
+ content/messenger/pref-receipts.xul (content/pref-receipts.xul)
+ content/messenger/pref-receipts.js (content/pref-receipts.js)
+ content/messenger/pref-character_encoding.xul (content/pref-character_encoding.xul)
+ content/messenger/pref-character_encoding.js (content/pref-character_encoding.js)
+ content/messenger/pref-offline.xul (content/pref-offline.xul)
+ content/messenger/pref-offline.js (content/pref-offline.js)
+
diff --git a/comm/suite/mailnews/components/prefs/moz.build b/comm/suite/mailnews/components/prefs/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/mailnews/components/prefs/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/suite/mailnews/components/smime/content/msgCompSMIMEOverlay.js b/comm/suite/mailnews/components/smime/content/msgCompSMIMEOverlay.js
new file mode 100644
index 0000000000..e802130008
--- /dev/null
+++ b/comm/suite/mailnews/components/smime/content/msgCompSMIMEOverlay.js
@@ -0,0 +1,354 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+// Account encryption policy values:
+// const kEncryptionPolicy_Never = 0;
+// 'IfPossible' was used by ns4.
+// const kEncryptionPolicy_IfPossible = 1;
+var kEncryptionPolicy_Always = 2;
+
+var gEncryptedURIService =
+ Cc["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
+ .getService(Ci.nsIEncryptedSMIMEURIsService);
+
+var gNextSecurityButtonCommand = "";
+var gSMFields = null;
+var gEncryptOptionChanged;
+var gSignOptionChanged;
+
+function onComposerLoad()
+{
+ // Are we already set up ? Or are the required fields missing ?
+ if (gSMFields || !gMsgCompose || !gMsgCompose.compFields)
+ return;
+
+ gMsgCompose.compFields.composeSecure = null;
+
+ gSMFields = Cc["@mozilla.org/messengercompose/composesecure;1"]
+ .createInstance(Ci.nsIMsgComposeSecure);
+ if (!gSMFields)
+ return;
+
+ gMsgCompose.compFields.composeSecure = gSMFields;
+
+ // Set up the initial security state.
+ gSMFields.requireEncryptMessage =
+ gCurrentIdentity.getIntAttribute("encryptionpolicy") == kEncryptionPolicy_Always;
+ if (!gSMFields.requireEncryptMessage &&
+ gEncryptedURIService &&
+ gEncryptedURIService.isEncrypted(gMsgCompose.originalMsgURI))
+ {
+ // Override encryption setting if original is known as encrypted.
+ gSMFields.requireEncryptMessage = true;
+ }
+ if (gSMFields.requireEncryptMessage)
+ setEncryptionUI();
+ else
+ setNoEncryptionUI();
+
+ gSMFields.signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
+ if (gSMFields.signMessage)
+ setSignatureUI();
+ else
+ setNoSignatureUI();
+}
+
+addEventListener("load", smimeComposeOnLoad, {capture: false, once: true});
+
+// this function gets called multiple times
+function smimeComposeOnLoad()
+{
+ onComposerLoad();
+
+ top.controllers.appendController(SecurityController);
+
+ addEventListener("compose-from-changed", onComposerFromChanged, true);
+ addEventListener("compose-send-message", onComposerSendMessage, true);
+
+ addEventListener("unload", smimeComposeOnUnload, {capture: false, once: true});
+}
+
+function smimeComposeOnUnload()
+{
+ removeEventListener("compose-from-changed", onComposerFromChanged, true);
+ removeEventListener("compose-send-message", onComposerSendMessage, true);
+
+ top.controllers.removeController(SecurityController);
+}
+
+function showNeedSetupInfo()
+{
+ let compSmimeBundle = document.getElementById("bundle_comp_smime");
+ let brandBundle = document.getElementById("brandBundle");
+ if (!compSmimeBundle || !brandBundle)
+ return;
+
+ let buttonPressed = Services.prompt.confirmEx(window,
+ brandBundle.getString("brandShortName"),
+ compSmimeBundle.getString("NeedSetup"),
+ Services.prompt.STD_YES_NO_BUTTONS, 0, 0, 0, null, {});
+ if (buttonPressed == 0)
+ openHelp("sign-encrypt", "chrome://communicator/locale/help/suitehelp.rdf");
+}
+
+function toggleEncryptMessage()
+{
+ if (!gSMFields)
+ return;
+
+ gSMFields.requireEncryptMessage = !gSMFields.requireEncryptMessage;
+
+ if (gSMFields.requireEncryptMessage)
+ {
+ // Make sure we have a cert.
+ if (!gCurrentIdentity.getUnicharAttribute("encryption_cert_name"))
+ {
+ gSMFields.requireEncryptMessage = false;
+ showNeedSetupInfo();
+ return;
+ }
+
+ setEncryptionUI();
+ }
+ else
+ {
+ setNoEncryptionUI();
+ }
+
+ gEncryptOptionChanged = true;
+}
+
+function toggleSignMessage()
+{
+ if (!gSMFields)
+ return;
+
+ gSMFields.signMessage = !gSMFields.signMessage;
+
+ if (gSMFields.signMessage) // make sure we have a cert name...
+ {
+ if (!gCurrentIdentity.getUnicharAttribute("signing_cert_name"))
+ {
+ gSMFields.signMessage = false;
+ showNeedSetupInfo();
+ return;
+ }
+
+ setSignatureUI();
+ }
+ else
+ {
+ setNoSignatureUI();
+ }
+
+ gSignOptionChanged = true;
+}
+
+function setSecuritySettings(menu_id)
+{
+ if (!gSMFields)
+ return;
+
+ document.getElementById("menu_securityEncryptRequire" + menu_id)
+ .setAttribute("checked", gSMFields.requireEncryptMessage);
+ document.getElementById("menu_securitySign" + menu_id)
+ .setAttribute("checked", gSMFields.signMessage);
+}
+
+function setNextCommand(what)
+{
+ gNextSecurityButtonCommand = what;
+}
+
+function doSecurityButton()
+{
+ var what = gNextSecurityButtonCommand;
+ gNextSecurityButtonCommand = "";
+
+ switch (what)
+ {
+ case "encryptMessage":
+ toggleEncryptMessage();
+ break;
+
+ case "signMessage":
+ toggleSignMessage();
+ break;
+
+ case "show":
+ default:
+ showMessageComposeSecurityStatus();
+ }
+}
+
+function setNoSignatureUI()
+{
+ top.document.getElementById("securityStatus").removeAttribute("signing");
+ top.document.getElementById("signing-status").collapsed = true;
+}
+
+function setSignatureUI()
+{
+ top.document.getElementById("securityStatus").setAttribute("signing", "ok");
+ top.document.getElementById("signing-status").collapsed = false;
+}
+
+function setNoEncryptionUI()
+{
+ top.document.getElementById("securityStatus").removeAttribute("crypto");
+ top.document.getElementById("encryption-status").collapsed = true;
+}
+
+function setEncryptionUI()
+{
+ top.document.getElementById("securityStatus").setAttribute("crypto", "ok");
+ top.document.getElementById("encryption-status").collapsed = false;
+}
+
+function showMessageComposeSecurityStatus()
+{
+ Recipients2CompFields(gMsgCompose.compFields);
+
+ window.openDialog(
+ "chrome://messenger-smime/content/msgCompSecurityInfo.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ {
+ compFields : gMsgCompose.compFields,
+ subject : GetMsgSubjectElement().value,
+ smFields : gSMFields,
+ isSigningCertAvailable :
+ gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
+ isEncryptionCertAvailable :
+ gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
+ currentIdentity : gCurrentIdentity
+ }
+ );
+}
+
+var SecurityController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command)
+ {
+ case "cmd_viewSecurityStatus":
+ return true;
+
+ default:
+ return false;
+ }
+ }
+};
+
+function onComposerSendMessage()
+{
+ let emailAddresses = [];
+
+ try
+ {
+ if (!gMsgCompose.compFields.composeSecure.requireEncryptMessage)
+ return;
+
+ emailAddresses = Cc["@mozilla.org/messenger-smime/smimejshelper;1"]
+ .createInstance(Ci.nsISMimeJSHelper)
+ .getNoCertAddresses(gMsgCompose.compFields);
+ }
+ catch (e)
+ {
+ return;
+ }
+
+ if (emailAddresses.length > 0)
+ {
+ // The rules here: If the current identity has a directoryServer set, then
+ // use that, otherwise, try the global preference instead.
+
+ let autocompleteDirectory;
+
+ // Does the current identity override the global preference?
+ if (gCurrentIdentity.overrideGlobalPref)
+ {
+ autocompleteDirectory = gCurrentIdentity.directoryServer;
+ }
+ else
+ {
+ // Try the global one
+ if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory"))
+ autocompleteDirectory =
+ Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer");
+ }
+
+ if (autocompleteDirectory)
+ window.openDialog("chrome://messenger-smime/content/certFetchingStatus.xul",
+ "",
+ "chrome,modal,resizable,centerscreen",
+ autocompleteDirectory,
+ emailAddresses);
+ }
+}
+
+function onComposerFromChanged()
+{
+ if (!gSMFields)
+ return;
+
+ var encryptionPolicy = gCurrentIdentity.getIntAttribute("encryptionpolicy");
+ var useEncryption = false;
+
+ if (!gEncryptOptionChanged)
+ {
+ // Encryption wasn't manually checked.
+ // Set up the encryption policy from the setting of the new identity.
+
+ // 0 == never, 1 == if possible (ns4), 2 == always encrypt.
+ useEncryption = (encryptionPolicy == kEncryptionPolicy_Always);
+ }
+ else
+ {
+ useEncryption = !!gCurrentIdentity.getUnicharAttribute("encryption_cert_name");
+ }
+
+ gSMFields.requireEncryptMessage = useEncryption;
+ if (useEncryption)
+ setEncryptionUI();
+ else
+ setNoEncryptionUI();
+
+ // - If signing is disabled, we will not turn it on automatically.
+ // - If signing is enabled, but the new account defaults to not sign, we will turn signing off.
+ var signMessage = gCurrentIdentity.getBoolAttribute("sign_mail");
+ var useSigning = false;
+
+ if (!gSignOptionChanged)
+ {
+ // Signing wasn't manually checked.
+ // Set up the signing policy from the setting of the new identity.
+ useSigning = signMessage;
+ }
+ else
+ {
+ useSigning = !!gCurrentIdentity.getUnicharAttribute("signing_cert_name");
+ }
+ gSMFields.signMessage = useSigning;
+ if (useSigning)
+ setSignatureUI();
+ else
+ setNoSignatureUI();
+}
diff --git a/comm/suite/mailnews/components/smime/content/msgHdrViewSMIMEOverlay.js b/comm/suite/mailnews/components/smime/content/msgHdrViewSMIMEOverlay.js
new file mode 100644
index 0000000000..09f665b4d0
--- /dev/null
+++ b/comm/suite/mailnews/components/smime/content/msgHdrViewSMIMEOverlay.js
@@ -0,0 +1,258 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gSignedUINode = null;
+var gEncryptedUINode = null;
+var gSMIMEContainer = null;
+var gStatusBar = null;
+var gSignedStatusPanel = null;
+var gEncryptedStatusPanel = null;
+
+var gEncryptedURIService = null;
+var gMyLastEncryptedURI = null;
+
+var gSMIMEBundle = null;
+// var gBrandBundle; -- defined in mailWindow.js
+
+// manipulates some globals from msgReadSMIMEOverlay.js
+
+var nsICMSMessageErrors = Ci.nsICMSMessageErrors;
+
+/// Get the necko URL for the message URI.
+function neckoURLForMessageURI(aMessageURI)
+{
+ let msgSvc = Cc["@mozilla.org/messenger;1"]
+ .createInstance(Ci.nsIMessenger)
+ .messageServiceFromURI(aMessageURI);
+ let neckoURI = msgSvc.getUrlForUri(aMessageURI);
+ return neckoURI.spec;
+}
+
+var smimeHeaderSink =
+{
+ maxWantedNesting: function()
+ {
+ return 1;
+ },
+
+ signedStatus: function(aNestingLevel, aSignatureStatus, aSignerCert)
+ {
+ if (aNestingLevel > 1) {
+ // we are not interested
+ return;
+ }
+
+ gSignatureStatus = aSignatureStatus;
+ gSignerCert = aSignerCert;
+
+ gSMIMEContainer.collapsed = false;
+ gSignedUINode.collapsed = false;
+ gSignedStatusPanel.collapsed = false;
+
+ switch (aSignatureStatus) {
+ case nsICMSMessageErrors.SUCCESS:
+ gSignedUINode.setAttribute("signed", "ok");
+ gStatusBar.setAttribute("signed", "ok");
+ break;
+
+ case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
+ gSignedUINode.setAttribute("signed", "unknown");
+ gStatusBar.setAttribute("signed", "unknown");
+ break;
+
+ case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
+ case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
+ gSignedUINode.setAttribute("signed", "mismatch");
+ gStatusBar.setAttribute("signed", "mismatch");
+ break;
+
+ default:
+ gSignedUINode.setAttribute("signed", "notok");
+ gStatusBar.setAttribute("signed", "notok");
+ break;
+ }
+ },
+
+ encryptionStatus: function(aNestingLevel, aEncryptionStatus, aRecipientCert)
+ {
+ if (aNestingLevel > 1) {
+ // we are not interested
+ return;
+ }
+
+ gEncryptionStatus = aEncryptionStatus;
+ gEncryptionCert = aRecipientCert;
+
+ gSMIMEContainer.collapsed = false;
+ gEncryptedUINode.collapsed = false;
+ gEncryptedStatusPanel.collapsed = false;
+
+ if (nsICMSMessageErrors.SUCCESS == aEncryptionStatus)
+ {
+ gEncryptedUINode.setAttribute("encrypted", "ok");
+ gStatusBar.setAttribute("encrypted", "ok");
+ }
+ else
+ {
+ gEncryptedUINode.setAttribute("encrypted", "notok");
+ gStatusBar.setAttribute("encrypted", "notok");
+ }
+
+ if (gEncryptedURIService)
+ {
+ // Remember the message URI and the corresponding necko URI.
+ gMyLastEncryptedURI = GetLoadedMessage();
+ gEncryptedURIService.rememberEncrypted(gMyLastEncryptedURI);
+ gEncryptedURIService.rememberEncrypted(
+ neckoURLForMessageURI(gMyLastEncryptedURI));
+ }
+
+ switch (aEncryptionStatus)
+ {
+ case nsICMSMessageErrors.SUCCESS:
+ case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
+ break;
+ default:
+ var brand = gBrandBundle.getString("brandShortName");
+ var title = gSMIMEBundle.getString("CantDecryptTitle").replace(/%brand%/g, brand);
+ var body = gSMIMEBundle.getString("CantDecryptBody").replace(/%brand%/g, brand);
+
+ // insert our message
+ msgWindow.displayHTMLInMessagePane(title,
+ "<html>\n" +
+ "<body bgcolor=\"#fafaee\">\n" +
+ "<center><br><br><br>\n" +
+ "<table>\n" +
+ "<tr><td>\n" +
+ "<center><strong><font size=\"+3\">\n" +
+ title+"</font></center><br>\n" +
+ body+"\n" +
+ "</td></tr></table></center></body></html>", false);
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgSMIMEHeaderSink"]),
+};
+
+function forgetEncryptedURI()
+{
+ if (gMyLastEncryptedURI && gEncryptedURIService)
+ {
+ gEncryptedURIService.forgetEncrypted(gMyLastEncryptedURI);
+ gEncryptedURIService.forgetEncrypted(
+ neckoURLForMessageURI(gMyLastEncryptedURI));
+ gMyLastEncryptedURI = null;
+ }
+}
+
+function onSMIMEStartHeaders()
+{
+ gEncryptionStatus = -1;
+ gSignatureStatus = -1;
+
+ gSignerCert = null;
+ gEncryptionCert = null;
+
+ gSMIMEContainer.collapsed = true;
+
+ gSignedUINode.collapsed = true;
+ gSignedUINode.removeAttribute("signed");
+ gSignedStatusPanel.collapsed = true;
+ gStatusBar.removeAttribute("signed");
+
+ gEncryptedUINode.collapsed = true;
+ gEncryptedUINode.removeAttribute("encrypted");
+ gEncryptedStatusPanel.collapsed = true;
+ gStatusBar.removeAttribute("encrypted");
+
+ forgetEncryptedURI();
+}
+
+function onSMIMEEndHeaders()
+{}
+
+function onSmartCardChange()
+{
+ // only reload encrypted windows
+ if (gMyLastEncryptedURI && gEncryptionStatus != -1)
+ ReloadMessage();
+}
+
+function msgHdrViewSMIMEOnLoad(event)
+{
+ window.crypto.enableSmartCardEvents = true;
+ document.addEventListener("smartcard-insert", onSmartCardChange);
+ document.addEventListener("smartcard-remove", onSmartCardChange);
+ if (!gSMIMEBundle)
+ gSMIMEBundle = document.getElementById("bundle_read_smime");
+
+ // we want to register our security header sink as an opaque nsISupports
+ // on the msgHdrSink used by mail.....
+ msgWindow.msgHeaderSink.securityInfo = smimeHeaderSink;
+
+ gSignedUINode = document.getElementById('signedHdrIcon');
+ gEncryptedUINode = document.getElementById('encryptedHdrIcon');
+ gSMIMEContainer = document.getElementById('smimeBox');
+ gStatusBar = document.getElementById('status-bar');
+ gSignedStatusPanel = document.getElementById('signed-status');
+ gEncryptedStatusPanel = document.getElementById('encrypted-status');
+
+ // add ourself to the list of message display listeners so we get notified when we are about to display a
+ // message.
+ var listener = {};
+ listener.onStartHeaders = onSMIMEStartHeaders;
+ listener.onEndHeaders = onSMIMEEndHeaders;
+ gMessageListeners.push(listener);
+
+ gEncryptedURIService =
+ Cc["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"]
+ .getService(Ci.nsIEncryptedSMIMEURIsService);
+}
+
+function msgHdrViewSMIMEOnUnload(event)
+{
+ window.crypto.enableSmartCardEvents = false;
+ document.removeEventListener("smartcard-insert", onSmartCardChange);
+ document.removeEventListener("smartcard-remove", onSmartCardChange);
+ forgetEncryptedURI();
+ removeEventListener("messagepane-loaded", msgHdrViewSMIMEOnLoad, true);
+ removeEventListener("messagepane-unloaded", msgHdrViewSMIMEOnUnload, true);
+ removeEventListener("messagepane-hide", msgHdrViewSMIMEOnMessagePaneHide, true);
+ removeEventListener("messagepane-unhide", msgHdrViewSMIMEOnMessagePaneUnhide, true);
+}
+
+function msgHdrViewSMIMEOnMessagePaneHide()
+{
+ gSMIMEContainer.collapsed = true;
+ gSignedUINode.collapsed = true;
+ gSignedStatusPanel.collapsed = true;
+ gEncryptedUINode.collapsed = true;
+ gEncryptedStatusPanel.collapsed = true;
+}
+
+function msgHdrViewSMIMEOnMessagePaneUnhide()
+{
+ if (gEncryptionStatus != -1 || gSignatureStatus != -1)
+ {
+ gSMIMEContainer.collapsed = false;
+
+ if (gSignatureStatus != -1)
+ {
+ gSignedUINode.collapsed = false;
+ gSignedStatusPanel.collapsed = false;
+ }
+
+ if (gEncryptionStatus != -1)
+ {
+ gEncryptedUINode.collapsed = false;
+ gEncryptedStatusPanel.collapsed = false;
+ }
+ }
+}
+
+addEventListener('messagepane-loaded', msgHdrViewSMIMEOnLoad, true);
+addEventListener('messagepane-unloaded', msgHdrViewSMIMEOnUnload, true);
+addEventListener('messagepane-hide', msgHdrViewSMIMEOnMessagePaneHide, true);
+addEventListener('messagepane-unhide', msgHdrViewSMIMEOnMessagePaneUnhide, true);
diff --git a/comm/suite/mailnews/components/smime/jar.mn b/comm/suite/mailnews/components/smime/jar.mn
new file mode 100644
index 0000000000..2f1f63cf32
--- /dev/null
+++ b/comm/suite/mailnews/components/smime/jar.mn
@@ -0,0 +1,15 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+messenger.jar:
+% content messenger-smime %content/messenger-smime/
+ content/messenger/certpicker.js (../../../../mailnews/extensions/smime/certpicker.js)
+ content/messenger/certpicker.xhtml (../../../../mailnews/extensions/smime/certpicker.xhtml)
+ content/messenger-smime/msgHdrViewSMIMEOverlay.js (content/msgHdrViewSMIMEOverlay.js)
+ content/messenger-smime/msgCompSMIMEOverlay.js (content/msgCompSMIMEOverlay.js)
+ content/messenger-smime/msgReadSMIMEOverlay.js (../../../../mailnews/extensions/smime/msgReadSMIMEOverlay.js)
+ content/messenger-smime/msgCompSecurityInfo.js (../../../../mailnews/extensions/smime/msgCompSecurityInfo.js)
+ content/messenger-smime/msgCompSecurityInfo.xhtml (../../../../mailnews/extensions/smime/msgCompSecurityInfo.xhtml)
+ content/messenger-smime/certFetchingStatus.js (../../../../mailnews/extensions/smime/certFetchingStatus.js)
+ content/messenger-smime/certFetchingStatus.xhtml (../../../../mailnews/extensions/smime/certFetchingStatus.xhtml)
diff --git a/comm/suite/mailnews/components/smime/moz.build b/comm/suite/mailnews/components/smime/moz.build
new file mode 100644
index 0000000000..de5cd1bf81
--- /dev/null
+++ b/comm/suite/mailnews/components/smime/moz.build
@@ -0,0 +1,6 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ["jar.mn"]