From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../components/addrbook/content/abCardOverlay.js | 1397 +++++++ .../components/addrbook/content/abCardOverlay.xul | 515 +++ .../addrbook/content/abCardViewOverlay.js | 527 +++ .../components/addrbook/content/abCommon.js | 1185 ++++++ .../addrbook/content/abEditCardDialog.xul | 18 + .../addrbook/content/abEditListDialog.xul | 22 + .../components/addrbook/content/abListOverlay.xul | 86 + .../addrbook/content/abMailListDialog.xul | 34 + .../addrbook/content/abNewCardDialog.xul | 35 + .../addrbook/content/abResultsPaneOverlay.xul | 94 + .../addrbook/content/abSelectAddressesDialog.js | 399 ++ .../addrbook/content/abSelectAddressesDialog.xul | 92 + .../components/addrbook/content/abTrees.js | 332 ++ .../addrbook/content/addressbook-panel.js | 119 + .../addrbook/content/addressbook-panel.xul | 96 + .../components/addrbook/content/addressbook.js | 581 +++ .../components/addrbook/content/addressbook.xul | 722 ++++ .../addrbook/content/prefs/pref-addressing.js | 24 + .../addrbook/content/prefs/pref-addressing.xul | 92 + comm/suite/mailnews/components/addrbook/jar.mn | 24 + comm/suite/mailnews/components/addrbook/moz.build | 8 + .../calendar/content/suite-overlay-addons.xhtml | 39 + .../content/suite-overlay-preferences.xhtml | 66 + .../calendar/content/suite-overlay-sidebar.js | 47 + .../calendar/content/suite-overlay-sidebar.xhtml | 39 + .../compose/content/MsgComposeCommands.js | 3936 ++++++++++++++++++++ .../compose/content/addressingWidgetOverlay.js | 1167 ++++++ .../compose/content/mailComposeOverlay.xul | 16 + .../compose/content/messengercompose.xul | 720 ++++ .../compose/content/msgComposeContextOverlay.xul | 23 + .../content/prefs/pref-composing_messages.js | 30 + .../content/prefs/pref-composing_messages.xul | 212 ++ .../compose/content/prefs/pref-formatting.js | 151 + .../compose/content/prefs/pref-formatting.xul | 120 + comm/suite/mailnews/components/compose/jar.mn | 14 + comm/suite/mailnews/components/compose/moz.build | 6 + comm/suite/mailnews/components/moz.build | 11 + .../components/prefs/content/mailPrefsOverlay.xul | 102 + .../prefs/content/pref-character_encoding.js | 41 + .../prefs/content/pref-character_encoding.xul | 111 + .../mailnews/components/prefs/content/pref-junk.js | 45 + .../components/prefs/content/pref-junk.xul | 134 + .../components/prefs/content/pref-mailnews.js | 25 + .../components/prefs/content/pref-mailnews.xul | 141 + .../components/prefs/content/pref-notifications.js | 91 + .../prefs/content/pref-notifications.xul | 187 + .../components/prefs/content/pref-offline.js | 19 + .../components/prefs/content/pref-offline.xul | 121 + .../components/prefs/content/pref-receipts.js | 28 + .../components/prefs/content/pref-receipts.xul | 146 + .../mailnews/components/prefs/content/pref-tags.js | 478 +++ .../components/prefs/content/pref-tags.xul | 83 + .../prefs/content/pref-viewing_messages.js | 26 + .../prefs/content/pref-viewing_messages.xul | 174 + comm/suite/mailnews/components/prefs/jar.mn | 23 + comm/suite/mailnews/components/prefs/moz.build | 6 + .../smime/content/msgCompSMIMEOverlay.js | 354 ++ .../smime/content/msgHdrViewSMIMEOverlay.js | 258 ++ comm/suite/mailnews/components/smime/jar.mn | 15 + comm/suite/mailnews/components/smime/moz.build | 6 + 60 files changed, 15613 insertions(+) create mode 100644 comm/suite/mailnews/components/addrbook/content/abCardOverlay.js create mode 100644 comm/suite/mailnews/components/addrbook/content/abCardOverlay.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abCardViewOverlay.js create mode 100644 comm/suite/mailnews/components/addrbook/content/abCommon.js create mode 100644 comm/suite/mailnews/components/addrbook/content/abEditCardDialog.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abEditListDialog.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abListOverlay.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abMailListDialog.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abNewCardDialog.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abResultsPaneOverlay.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.js create mode 100644 comm/suite/mailnews/components/addrbook/content/abSelectAddressesDialog.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/abTrees.js create mode 100644 comm/suite/mailnews/components/addrbook/content/addressbook-panel.js create mode 100644 comm/suite/mailnews/components/addrbook/content/addressbook-panel.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/addressbook.js create mode 100644 comm/suite/mailnews/components/addrbook/content/addressbook.xul create mode 100644 comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.js create mode 100644 comm/suite/mailnews/components/addrbook/content/prefs/pref-addressing.xul create mode 100644 comm/suite/mailnews/components/addrbook/jar.mn create mode 100644 comm/suite/mailnews/components/addrbook/moz.build create mode 100644 comm/suite/mailnews/components/calendar/content/suite-overlay-addons.xhtml create mode 100644 comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml create mode 100644 comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js create mode 100644 comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml create mode 100644 comm/suite/mailnews/components/compose/content/MsgComposeCommands.js create mode 100644 comm/suite/mailnews/components/compose/content/addressingWidgetOverlay.js create mode 100644 comm/suite/mailnews/components/compose/content/mailComposeOverlay.xul create mode 100644 comm/suite/mailnews/components/compose/content/messengercompose.xul create mode 100644 comm/suite/mailnews/components/compose/content/msgComposeContextOverlay.xul create mode 100644 comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.js create mode 100644 comm/suite/mailnews/components/compose/content/prefs/pref-composing_messages.xul create mode 100644 comm/suite/mailnews/components/compose/content/prefs/pref-formatting.js create mode 100644 comm/suite/mailnews/components/compose/content/prefs/pref-formatting.xul create mode 100644 comm/suite/mailnews/components/compose/jar.mn create mode 100644 comm/suite/mailnews/components/compose/moz.build create mode 100644 comm/suite/mailnews/components/moz.build create mode 100644 comm/suite/mailnews/components/prefs/content/mailPrefsOverlay.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-character_encoding.js create mode 100755 comm/suite/mailnews/components/prefs/content/pref-character_encoding.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-junk.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-junk.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-mailnews.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-mailnews.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-notifications.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-notifications.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-offline.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-offline.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-receipts.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-receipts.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-tags.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-tags.xul create mode 100644 comm/suite/mailnews/components/prefs/content/pref-viewing_messages.js create mode 100644 comm/suite/mailnews/components/prefs/content/pref-viewing_messages.xul create mode 100644 comm/suite/mailnews/components/prefs/jar.mn create mode 100644 comm/suite/mailnews/components/prefs/moz.build create mode 100644 comm/suite/mailnews/components/smime/content/msgCompSMIMEOverlay.js create mode 100644 comm/suite/mailnews/components/smime/content/msgHdrViewSMIMEOverlay.js create mode 100644 comm/suite/mailnews/components/smime/jar.mn create mode 100644 comm/suite/mailnews/components/smime/moz.build (limited to 'comm/suite/mailnews/components') 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 @@ + + + + + + + + + + + + + + + + diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml b/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml new file mode 100644 index 0000000000..0e7aad33df --- /dev/null +++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-preferences.xhtml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + %lightningDTD; + + %preferencesDTD; +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js new file mode 100644 index 0000000000..6c27c9c385 --- /dev/null +++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.js @@ -0,0 +1,47 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* import-globals-from ../../../suite/base/content/utilityOverlay.js */ + +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +var ltnSuiteUtils = { + addStartupObserver: function() { + Services.obs.addObserver(this.startupObserver, "lightning-startup-done"); + Services.obs.addObserver(this.startupObserver, "calendar-taskview-startup-done"); + }, + + startupObserver: { + observe: function(subject, topic, state) { + if (topic != "lightning-startup-done" && topic != "calendar-taskview-startup-done") { + return; + } + + const ids = [ + ["CustomizeTaskActionsToolbar", "task-actions-toolbox"], + ["CustomizeCalendarToolbar", "calendar-toolbox"], + ["CustomizeTaskToolbar", "task-toolbox"], + ]; + + ids.forEach(([itemID, toolboxID]) => { + let item = document.getElementById(itemID); + let toolbox = document.getElementById(toolboxID); + toolbox.customizeInit = function() { + item.setAttribute("disabled", "true"); + toolboxCustomizeInit("mail-menubar"); + }; + toolbox.customizeDone = function(aToolboxChanged) { + item.removeAttribute("disabled"); + toolboxCustomizeDone("mail-menubar", toolbox, aToolboxChanged); + }; + toolbox.customizeChange = function(aEvent) { + toolboxCustomizeChange(toolbox, aEvent); + }; + }); + }, + }, +}; + +ltnSuiteUtils.addStartupObserver(); diff --git a/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml new file mode 100644 index 0000000000..79ae5d662a --- /dev/null +++ b/comm/suite/mailnews/components/calendar/content/suite-overlay-sidebar.xhtml @@ -0,0 +1,39 @@ + + + + + + +