diff options
Diffstat (limited to '')
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="©Address.label;" accesskey="©Address.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"],) |