summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/components/addrbook
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
21 files changed, 6402 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"],)