summaryrefslogtreecommitdiffstats
path: root/mailnews/addrbook/content
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/addrbook/content')
-rw-r--r--mailnews/addrbook/content/abAddressBookNameDialog.js72
-rw-r--r--mailnews/addrbook/content/abAddressBookNameDialog.xul26
-rw-r--r--mailnews/addrbook/content/abDragDrop.js424
-rw-r--r--mailnews/addrbook/content/abEditCardDialog.xul17
-rw-r--r--mailnews/addrbook/content/abMailListDialog.js613
-rw-r--r--mailnews/addrbook/content/abNewCardDialog.xul33
-rw-r--r--mailnews/addrbook/content/abResultsPane.js502
-rw-r--r--mailnews/addrbook/content/abResultsPaneOverlay.xul90
-rw-r--r--mailnews/addrbook/content/addrbookWidgets.xml439
-rw-r--r--mailnews/addrbook/content/print.css94
10 files changed, 2310 insertions, 0 deletions
diff --git a/mailnews/addrbook/content/abAddressBookNameDialog.js b/mailnews/addrbook/content/abAddressBookNameDialog.js
new file mode 100644
index 000000000..a62659cc3
--- /dev/null
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gOkButton;
+var gNameInput;
+var gDirectory = null;
+
+var kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+var kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+var kAllDirectoryRoot = "moz-abdirectory://";
+var kPABDirectory = 2; // defined in nsDirPrefs.h
+
+function abNameOnLoad()
+{
+ // Get the document elements.
+ gOkButton = document.documentElement.getButton('accept');
+ gNameInput = document.getElementById('name');
+
+ // look in arguments[0] for parameters to see if we have a directory or not
+ if ("arguments" in window && window.arguments[0] &&
+ "selectedDirectory" in window.arguments[0]) {
+ gDirectory = window.arguments[0].selectedDirectory;
+ gNameInput.value = gDirectory.dirName;
+ }
+
+ // Work out the window title (if we have a directory specified, then it's a
+ // rename).
+ var bundle = document.getElementById("bundle_addressBook");
+
+ if (gDirectory) {
+ let oldListName = gDirectory.dirName;
+ document.title = bundle.getFormattedString("addressBookTitleEdit", [oldListName]);
+ } else {
+ document.title = bundle.getString("addressBookTitleNew");
+ }
+
+ if (gDirectory &&
+ (gDirectory.URI == kCollectedAddressbookURI ||
+ gDirectory.URI == kPersonalAddressbookURI ||
+ gDirectory.URI == kAllDirectoryRoot + "?")) {
+ // Address book name is not editable, therefore disable the field and
+ // only have an ok button that doesn't do anything.
+ gNameInput.readOnly = true;
+ document.documentElement.buttons = "accept";
+ document.documentElement.removeAttribute("ondialogaccept");
+ } else {
+ gNameInput.focus();
+ abNameDoOkEnabling();
+ }
+}
+
+function abNameOKButton()
+{
+ var newName = gNameInput.value.trim();
+
+ // Either create a new directory or update an existing one depending on what
+ // we were given when we started.
+ if (gDirectory)
+ gDirectory.dirName = newName;
+ else
+ MailServices.ab.newAddressBook(newName, "", kPABDirectory);
+
+ return true;
+}
+
+function abNameDoOkEnabling()
+{
+ gOkButton.disabled = gNameInput.value.trim() == "";
+}
diff --git a/mailnews/addrbook/content/abAddressBookNameDialog.xul b/mailnews/addrbook/content/abAddressBookNameDialog.xul
new file mode 100644
index 000000000..f707cd597
--- /dev/null
+++ b/mailnews/addrbook/content/abAddressBookNameDialog.xul
@@ -0,0 +1,26 @@
+<?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/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/addressbook/abAddressBookNameDialog.dtd">
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 36em;"
+ onload="abNameOnLoad();"
+ ondialogaccept="return abNameOKButton();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_addressBook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://messenger/content/addressbook/abAddressBookNameDialog.js"/>
+
+ <hbox align="center">
+ <label control="name" value="&name.label;" accesskey="&name.accesskey;"/>
+ <textbox id="name" oninput="abNameDoOkEnabling();" flex="1"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/addrbook/content/abDragDrop.js b/mailnews/addrbook/content/abDragDrop.js
new file mode 100644
index 000000000..6160ec530
--- /dev/null
+++ b/mailnews/addrbook/content/abDragDrop.js
@@ -0,0 +1,424 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+// Returns the load context for the current window
+function getLoadContext() {
+ return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+}
+
+var abFlavorDataProvider = {
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFlavorDataProvider]),
+
+ getFlavorData: function(aTransferable, aFlavor, aData, aDataLen)
+ {
+ if (aFlavor == "application/x-moz-file-promise")
+ {
+ var primitive = {};
+ aTransferable.getTransferData("text/vcard", primitive, {});
+ var vCard = primitive.value.QueryInterface(Components.interfaces.nsISupportsString).data;
+ aTransferable.getTransferData("application/x-moz-file-promise-dest-filename", primitive, {});
+ var leafName = primitive.value.QueryInterface(Components.interfaces.nsISupportsString).data;
+ aTransferable.getTransferData("application/x-moz-file-promise-dir", primitive, {});
+ var localFile = primitive.value.QueryInterface(Components.interfaces.nsIFile).clone();
+ localFile.append(leafName);
+
+ var ofStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
+ ofStream.init(localFile, -1, -1, 0);
+ var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].createInstance(Components.interfaces.nsIConverterOutputStream);
+ converter.init(ofStream, null, 0, 0);
+ converter.writeString(vCard);
+ converter.close();
+
+ aData.value = localFile;
+ }
+ }
+};
+
+var abResultsPaneObserver = {
+ onDragStart: function (aEvent, aXferData, aDragAction)
+ {
+ var selectedRows = GetSelectedRows();
+
+ if (!selectedRows)
+ return;
+
+ var selectedAddresses = GetSelectedAddresses();
+
+ aXferData.data = new TransferData();
+ aXferData.data.addDataForFlavour("moz/abcard", selectedRows);
+ aXferData.data.addDataForFlavour("text/x-moz-address", selectedAddresses);
+ aXferData.data.addDataForFlavour("text/unicode", selectedAddresses);
+
+ let srcDirectory = getSelectedDirectory();
+ // The default allowable actions are copy, move and link, so we need
+ // to restrict them here.
+ if (!srcDirectory.readOnly)
+ // Only allow copy & move from read-write directories.
+ aDragAction.action = Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY |
+ Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_MOVE;
+ else
+ // Only allow copy from read-only directories.
+ aDragAction.action = Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY;
+
+ var card = GetSelectedCard();
+ if (card && card.displayName) {
+ let vCard = card.translateTo("vcard");
+ aXferData.data.addDataForFlavour("text/vcard", decodeURIComponent(vCard));
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise-dest-filename", card.displayName + ".vcf");
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise-url", "data:text/vcard," + vCard);
+ aXferData.data.addDataForFlavour("application/x-moz-file-promise", abFlavorDataProvider);
+ }
+ },
+
+ onDrop: function (aEvent, aXferData, aDragSession)
+ {
+ },
+
+ onDragExit: function (aEvent, aDragSession)
+ {
+ },
+
+ onDragOver: function (aEvent, aFlavour, aDragSession)
+ {
+ },
+
+ getSupportedFlavours: function ()
+ {
+ return null;
+ }
+};
+
+
+var dragService = Components.classes["@mozilla.org/widget/dragservice;1"]
+ .getService(Components.interfaces.nsIDragService);
+
+var abDirTreeObserver = {
+ /**
+ * canDrop - determine if the tree will accept the dropping of a item
+ * onto it.
+ *
+ * Note 1: We don't allow duplicate mailing list names, therefore copy
+ * is not allowed for mailing lists.
+ * Note 2: Mailing lists currently really need a card in the parent
+ * address book, therefore only moving to an address book is allowed.
+ *
+ * The possibilities:
+ *
+ * anything -> same place = Not allowed
+ * anything -> read only directory = Not allowed
+ * mailing list -> mailing list = Not allowed
+ * (we currently do not support recursive lists)
+ * address book card -> different address book = MOVE or COPY
+ * address book card -> mailing list = COPY only
+ * (cards currently have to exist outside list for list to work correctly)
+ * mailing list -> different address book = MOVE only
+ * (lists currently need to have unique names)
+ * card in mailing list -> parent mailing list = Not allowed
+ * card in mailing list -> other mailing list = MOVE or COPY
+ * card in mailing list -> other address book = MOVE or COPY
+ * read only directory item -> anywhere = COPY only
+ */
+ canDrop: function(index, orientation, dataTransfer)
+ {
+ if (orientation != Components.interfaces.nsITreeView.DROP_ON)
+ return false;
+ if (!dataTransfer.types.includes("moz/abcard")) {
+ return false;
+ }
+
+ let targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
+
+ let srcURI = getSelectedDirectoryURI();
+
+ // We cannot allow copy/move to "All Address Books".
+ if (targetURI == kAllDirectoryRoot + "?")
+ return false;
+
+ // The same place case
+ if (targetURI == srcURI)
+ return false;
+
+ // determine if we dragging from a mailing list on a directory x to the parent (directory x).
+ // if so, don't allow the drop
+ if (srcURI.startsWith(targetURI))
+ return false;
+
+ // check if we can write to the target directory
+ // e.g. LDAP is readonly currently
+ var targetDirectory = GetDirectoryFromURI(targetURI);
+
+ if (targetDirectory.readOnly)
+ return false;
+
+ var dragSession = dragService.getCurrentSession();
+ if (!dragSession)
+ return false;
+
+ // XXX Due to bug 373125/bug 349044 we can't specify a default action,
+ // so we default to move and this means that the user would have to press
+ // ctrl to copy which most users don't realise.
+ //
+ // If target directory is a mailing list, then only allow copies.
+ // if (targetDirectory.isMailList &&
+ // dragSession.dragAction != Components.interfaces.
+ // nsIDragService.DRAGDROP_ACTION_COPY)
+ //return false;
+
+ var srcDirectory = GetDirectoryFromURI(srcURI);
+
+ // Only allow copy from read-only directories.
+ if (srcDirectory.readOnly &&
+ dragSession.dragAction != Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY)
+ return false;
+
+ // Go through the cards checking to see if one of them is a mailing list
+ // (if we are attempting a copy) - we can't copy mailing lists as
+ // that would give us duplicate names which isn't allowed at the
+ // moment.
+ var draggingMailList = false;
+
+ // The data contains the a string of "selected rows", eg.: "1,2".
+ var rows = dataTransfer.getData("moz/abcard").split(",").map(j => parseInt(j, 10));
+
+ for (var j = 0; j < rows.length; j++)
+ {
+ if (gAbView.getCardFromRow(rows[j]).isMailList)
+ {
+ draggingMailList = true;
+ break;
+ }
+ }
+
+ // The rest of the cases - allow cards for copy or move, but only allow
+ // move of mailing lists if we're not going into another mailing list.
+ if (draggingMailList &&
+ (targetDirectory.isMailList ||
+ dragSession.dragAction == Components.interfaces.
+ nsIDragService.DRAGDROP_ACTION_COPY))
+ {
+ return false;
+ }
+
+ dragSession.canDrop = true;
+ return true;
+ },
+
+ /**
+ * onDrop - we don't need to check again for correctness as the
+ * tree view calls canDrop just before calling onDrop.
+ *
+ */
+ onDrop: function(index, orientation, dataTransfer)
+ {
+ var dragSession = dragService.getCurrentSession();
+ if (!dragSession)
+ return;
+ if (!dataTransfer.types.includes("moz/abcard")) {
+ return;
+ }
+
+ let targetURI = gDirectoryTreeView.getDirectoryAtIndex(index).URI;
+ let srcURI = getSelectedDirectoryURI();
+
+ // The data contains the a string of "selected rows", eg.: "1,2".
+ var rows = dataTransfer.getData("moz/abcard").split(",").map(j => parseInt(j, 10));
+ var numrows = rows.length;
+
+ var result;
+ // needToCopyCard is used for whether or not we should be creating
+ // copies of the cards in a mailing list in a different address book
+ // - it's not for if we are moving or not.
+ var needToCopyCard = true;
+ if (srcURI.length > targetURI.length) {
+ result = srcURI.split(targetURI);
+ if (result[0] != srcURI) {
+ // src directory is a mailing list on target directory, no need to copy card
+ needToCopyCard = false;
+ }
+ }
+ else {
+ result = targetURI.split(srcURI);
+ if (result[0] != targetURI) {
+ // target directory is a mailing list on src directory, no need to copy card
+ needToCopyCard = false;
+ }
+ }
+
+ // if we still think we have to copy the card,
+ // check if srcURI and targetURI are mailing lists on same directory
+ // if so, we don't have to copy the card
+ if (needToCopyCard) {
+ var targetParentURI = GetParentDirectoryFromMailingListURI(targetURI);
+ if (targetParentURI && (targetParentURI == GetParentDirectoryFromMailingListURI(srcURI)))
+ needToCopyCard = false;
+ }
+
+ var directory = GetDirectoryFromURI(targetURI);
+
+ // Only move if we are not transferring to a mail list
+ var actionIsMoving = (dragSession.dragAction & dragSession.DRAGDROP_ACTION_MOVE) && !directory.isMailList;
+
+ let cardsToCopy = [];
+ for (let j = 0; j < numrows; j++) {
+ cardsToCopy.push(gAbView.getCardFromRow(rows[j]));
+ }
+ for (let card of cardsToCopy) {
+ if (card.isMailList) {
+ // This check ensures we haven't slipped through by mistake
+ if (needToCopyCard && actionIsMoving) {
+ directory.addMailList(GetDirectoryFromURI(card.mailListURI));
+ }
+ } else {
+ let srcDirectory = null;
+ if (srcURI == (kAllDirectoryRoot + "?") && actionIsMoving) {
+ let dirId = card.directoryId.substring(0, card.directoryId.indexOf("&"));
+ srcDirectory = MailServices.ab.getDirectoryFromId(dirId);
+ }
+
+ directory.dropCard(card, needToCopyCard);
+
+ // This is true only if srcURI is "All ABs" and action is moving.
+ if (srcDirectory) {
+ let cardArray =
+ Components.classes["@mozilla.org/array;1"]
+ .createInstance(Components.interfaces.nsIMutableArray);
+ cardArray.appendElement(card, false);
+ srcDirectory.deleteCards(cardArray);
+ }
+ }
+ }
+
+ var cardsTransferredText;
+
+ // If we are moving, but not moving to a directory, then delete the
+ // selected cards and display the appropriate text
+ if (actionIsMoving && srcURI != (kAllDirectoryRoot + "?")) {
+ // If we have moved the cards, then delete them as well.
+ gAbView.deleteSelectedCards();
+ }
+
+ if (actionIsMoving) {
+ cardsTransferredText = PluralForm.get(numrows,
+ gAddressBookBundle.getFormattedString("contactsMoved", [numrows]));
+ } else {
+ cardsTransferredText = PluralForm.get(numrows,
+ gAddressBookBundle.getFormattedString("contactsCopied", [numrows]));
+ }
+
+ if (srcURI == kAllDirectoryRoot + "?") {
+ SetAbView(srcURI);
+ }
+
+ document.getElementById("statusText").label = cardsTransferredText;
+ },
+
+ onToggleOpenState: function()
+ {
+ },
+
+ onCycleHeader: function(colID, elt)
+ {
+ },
+
+ onCycleCell: function(row, colID)
+ {
+ },
+
+ onSelectionChanged: function()
+ {
+ },
+
+ onPerformAction: function(action)
+ {
+ },
+
+ onPerformActionOnRow: function(action, row)
+ {
+ },
+
+ onPerformActionOnCell: function(action, row, colID)
+ {
+ }
+}
+
+function DragAddressOverTargetControl(event)
+{
+ var dragSession = gDragService.getCurrentSession();
+
+ if (!dragSession.isDataFlavorSupported("text/x-moz-address"))
+ return;
+
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+
+ var canDrop = true;
+
+ for ( var i = 0; i < dragSession.numDropItems; ++i )
+ {
+ dragSession.getData ( trans, i );
+ var dataObj = new Object();
+ var bestFlavor = new Object();
+ var len = new Object();
+ try
+ {
+ trans.getAnyTransferData ( bestFlavor, dataObj, len );
+ }
+ catch (ex)
+ {
+ canDrop = false;
+ break;
+ }
+ }
+ dragSession.canDrop = canDrop;
+}
+
+function DropAddressOverTargetControl(event)
+{
+ var dragSession = gDragService.getCurrentSession();
+
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+ trans.addDataFlavor("text/x-moz-address");
+
+ for ( var i = 0; i < dragSession.numDropItems; ++i )
+ {
+ dragSession.getData ( trans, i );
+ var dataObj = new Object();
+ var bestFlavor = new Object();
+ var len = new Object();
+
+ // Ensure we catch any empty data that may have slipped through
+ try
+ {
+ trans.getAnyTransferData ( bestFlavor, dataObj, len);
+ }
+ catch (ex)
+ {
+ continue;
+ }
+
+ if ( dataObj )
+ dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if ( !dataObj )
+ continue;
+
+ // pull the address out of the data object
+ var address = dataObj.data.substring(0, len.value);
+ if (!address)
+ continue;
+
+ DropRecipient(address);
+ }
+}
diff --git a/mailnews/addrbook/content/abEditCardDialog.xul b/mailnews/addrbook/content/abEditCardDialog.xul
new file mode 100644
index 000000000..0a21f3b83
--- /dev/null
+++ b/mailnews/addrbook/content/abEditCardDialog.xul
@@ -0,0 +1,17 @@
+<?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();">
+
+ <stringbundleset id="stringbundleset"/>
+ <vbox id="editcard"/>
+</dialog>
diff --git a/mailnews/addrbook/content/abMailListDialog.js b/mailnews/addrbook/content/abMailListDialog.js
new file mode 100644
index 000000000..ee94d39b7
--- /dev/null
+++ b/mailnews/addrbook/content/abMailListDialog.js
@@ -0,0 +1,613 @@
+/* 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/. */
+
+top.MAX_RECIPIENTS = 1;
+var inputElementType = "";
+
+var gListCard;
+var gEditList;
+var oldListName = "";
+var gLoadListeners = [];
+var gSaveListeners = [];
+
+try
+{
+ var gDragService = Components.classes["@mozilla.org/widget/dragservice;1"]
+ .getService(Components.interfaces.nsIDragService);
+}
+catch (e)
+{
+}
+
+// Returns the load context for the current window
+function getLoadContext() {
+ return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+}
+
+function awHandleKeyPress(element, event)
+{
+ // allow dialog to close on enter if focused textbox has no value
+ if (element.value != "" && event.keyCode == KeyEvent.DOM_VK_RETURN) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+}
+
+function mailingListExists(listname)
+{
+ if (MailServices.ab.mailListNameExists(listname))
+ {
+ Services.prompt.alert(window,
+ gAddressBookBundle.getString("mailListNameExistsTitle"),
+ gAddressBookBundle.getString("mailListNameExistsMessage"));
+ return true;
+ }
+ return false;
+}
+
+function GetListValue(mailList, doAdd)
+{
+ var listname = document.getElementById("ListName").value.trim();
+
+ if (listname.length == 0)
+ {
+ var alertText = gAddressBookBundle.getString("emptyListName");
+ alert(alertText);
+ return false;
+ }
+ else
+ {
+ var canonicalNewListName = listname.toLowerCase();
+ var canonicalOldListName = oldListName.toLowerCase();
+ if (doAdd)
+ {
+ if (mailingListExists(canonicalNewListName))
+ return false;
+ }
+ else if (canonicalOldListName != canonicalNewListName)
+ {
+ if (mailingListExists(canonicalNewListName))
+ return false;
+ }
+ }
+
+ mailList.isMailList = true;
+ mailList.dirName = listname;
+ mailList.listNickName = document.getElementById('ListNickName').value;
+ mailList.description = document.getElementById('ListDescription').value;
+
+ var oldTotal = mailList.addressLists.length;
+ var i = 1;
+ var pos = 0;
+ var inputField, fieldValue, cardproperty;
+ while ((inputField = awGetInputElement(i)))
+ {
+
+ fieldValue = inputField.value;
+
+ if (doAdd || (!doAdd && pos >= oldTotal))
+ cardproperty = Components.classes["@mozilla.org/addressbook/cardproperty;1"].createInstance();
+ else
+ cardproperty = mailList.addressLists.queryElementAt(pos, Components.interfaces.nsIAbCard);
+
+ if (fieldValue == "")
+ {
+ if (!doAdd && cardproperty)
+ try
+ {
+ mailList.addressLists.removeElementAt(pos);
+ --oldTotal;
+ }
+ catch(ex)
+ {
+ // Ignore attempting to remove an item
+ // at a position greater than the number
+ // of elements in the addressLists attribute
+ }
+ }
+ else if (cardproperty)
+ {
+ cardproperty = cardproperty.QueryInterface(Components.interfaces.nsIAbCard);
+ if (cardproperty)
+ {
+ let addrObjects = MailServices.headerParser
+ .makeFromDisplayAddress(fieldValue, {});
+ for (let j = 0; j < addrObjects.length; j++)
+ {
+ if (j > 0)
+ {
+ cardproperty = Components.classes["@mozilla.org/addressbook/cardproperty;1"].createInstance();
+ cardproperty = cardproperty.QueryInterface(Components.interfaces.nsIAbCard);
+ }
+ cardproperty.primaryEmail = addrObjects[j].email;
+ cardproperty.displayName = addrObjects[j].name || addrObjects[j].email;
+
+ if (doAdd || (doAdd == false && pos >= oldTotal))
+ mailList.addressLists.appendElement(cardproperty, false);
+ }
+ pos++;
+ }
+ }
+ i++;
+ }
+
+ --i;
+
+ if (doAdd == false && i < oldTotal)
+ {
+ for (var j = i; j < oldTotal; j++)
+ mailList.addressLists.removeElementAt(j);
+ }
+ return true;
+}
+
+function MailListOKButton()
+{
+ var popup = document.getElementById('abPopup');
+ if (popup)
+ {
+ var uri = popup.getAttribute('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
+ // -----
+
+ //Add mailing list to database
+ var mailList = Components.classes["@mozilla.org/addressbook/directoryproperty;1"].createInstance();
+ mailList = mailList.QueryInterface(Components.interfaces.nsIAbDirectory);
+
+ if (GetListValue(mailList, true))
+ {
+ var parentDirectory = GetDirectoryFromURI(uri);
+ mailList = parentDirectory.addMailList(mailList);
+ NotifySaveListeners(mailList);
+ }
+ else
+ return false;
+ }
+
+ return true; // close the window
+}
+
+function OnLoadNewMailList()
+{
+ var selectedAB = null;
+
+ InitCommonJS();
+
+ if ("arguments" in window && window.arguments[0])
+ {
+ var abURI = window.arguments[0].selectedAB;
+ if (abURI && abURI != kAllDirectoryRoot + "?") {
+ var directory = GetDirectoryFromURI(abURI);
+ if (directory.isMailList) {
+ var parentURI = GetParentDirectoryFromMailingListURI(abURI);
+ if (parentURI) {
+ selectedAB = parentURI;
+ }
+ }
+ else if (directory.readOnly) {
+ selectedAB = kPersonalAddressbookURI;
+ }
+ else {
+ selectedAB = abURI;
+ }
+ }
+ }
+
+ if (!selectedAB)
+ selectedAB = kPersonalAddressbookURI;
+
+ // set popup with address book names
+ var abPopup = document.getElementById('abPopup');
+ abPopup.value = selectedAB;
+
+ AppendNewRowAndSetFocus();
+ awFitDummyRows(1);
+
+ document.addEventListener("keypress", awDocumentKeyPress, true);
+
+ // focus on first name
+ var listName = document.getElementById('ListName');
+ if (listName)
+ setTimeout( function(firstTextBox) { firstTextBox.focus(); }, 0, listName );
+
+ NotifyLoadListeners(directory);
+}
+
+function EditListOKButton()
+{
+ //edit mailing list in database
+ if (GetListValue(gEditList, false))
+ {
+ if (gListCard) {
+ // modify the list card (for the results pane) from the mailing list
+ gListCard.displayName = gEditList.dirName;
+ gListCard.lastName = gEditList.dirName;
+ gListCard.setProperty("NickName", gEditList.listNickName);
+ gListCard.setProperty("Notes", gEditList.description);
+ }
+
+ NotifySaveListeners(gEditList);
+ gEditList.editMailListToDatabase(gListCard);
+
+ window.arguments[0].refresh = true;
+ return true; // close the window
+ }
+
+ return false;
+}
+
+function OnLoadEditList()
+{
+ InitCommonJS();
+
+ gListCard = window.arguments[0].abCard;
+ var listUri = window.arguments[0].listURI;
+
+ gEditList = GetDirectoryFromURI(listUri);
+
+ document.getElementById('ListName').value = gEditList.dirName;
+ document.getElementById('ListNickName').value = gEditList.listNickName;
+ document.getElementById('ListDescription').value = gEditList.description;
+ oldListName = gEditList.dirName;
+
+ document.title = gAddressBookBundle.getFormattedString("mailingListTitleEdit", [oldListName]);
+
+ if (gEditList.addressLists)
+ {
+ let total = gEditList.addressLists.length;
+ if (total)
+ {
+ let listbox = document.getElementById('addressingWidget');
+ let newListBoxNode = listbox.cloneNode(false);
+ let templateNode = listbox.querySelector("listitem");
+
+ top.MAX_RECIPIENTS = 0;
+ for (let i = 0; i < total; i++)
+ {
+ let card = gEditList.addressLists.queryElementAt(i, Components.interfaces.nsIAbCard);
+ let address = MailServices.headerParser.makeMailboxObject(
+ card.displayName, card.primaryEmail).toString();
+ SetInputValue(address, newListBoxNode, templateNode);
+ }
+ listbox.parentNode.replaceChild(newListBoxNode, listbox);
+ }
+ }
+
+ // Is this directory read-only? If so, we now need to set all the fields to
+ // read-only.
+ if (gEditList.readOnly) {
+ const kMailListFields = [ 'ListName', 'ListNickName', 'ListDescription' ];
+
+ for (let i = 0; i < kMailListFields.length; ++i)
+ document.getElementById(kMailListFields[i]).readOnly = true;
+
+ document.documentElement.buttons = "accept";
+ document.documentElement.removeAttribute("ondialogaccept");
+
+ // Getting a sane read-only implementation for the addressing widget would
+ // basically need a separate dialog. Given I'm not sure about the future of
+ // the mailing list dialog in its current state, let's just disable it
+ // completely.
+ document.getElementById("addressingWidget").disabled = true;
+ }
+
+ document.addEventListener("keypress", awDocumentKeyPress, true);
+
+ // workaround for bug 118337 - for mailing lists that have more rows than fits inside
+ // the display, the value of the textbox inside the new row isn't inherited into the input -
+ // the first row then appears to be duplicated at the end although it is actually empty.
+ // see awAppendNewRow which copies first row and clears it
+ setTimeout(AppendLastRow, 0);
+ NotifyLoadListeners(gEditList);
+}
+
+function AppendLastRow()
+{
+ AppendNewRowAndSetFocus();
+ awFitDummyRows(1);
+
+ // focus on first name
+ var listName = document.getElementById('ListName');
+ if (listName)
+ listName.focus();
+}
+
+function AppendNewRowAndSetFocus()
+{
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value)
+ awAppendNewRow(true);
+ else
+ awSetFocus(top.MAX_RECIPIENTS, lastInput);
+}
+
+function SetInputValue(inputValue, parentNode, templateNode)
+{
+ top.MAX_RECIPIENTS++;
+
+ var newNode = templateNode.cloneNode(true);
+ parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if (input && input.length == 1)
+ {
+ //We need to set the value using both setAttribute and .value else we will
+ // lose the content when the field is not visible. See bug 37435
+ input[0].setAttribute("value", inputValue);
+ input[0].value = inputValue;
+ input[0].setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
+ }
+}
+
+function awNotAnEmptyArea(event)
+{
+ //This is temporary until i figure out how to ensure to always having an empty space after the last row
+
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value)
+ awAppendNewRow(false);
+
+ event.stopPropagation();
+}
+
+function awClickEmptySpace(target, setFocus)
+{
+ if (target == null ||
+ (target.localName != "listboxbody" &&
+ target.localName != "listcell" &&
+ target.localName != "listitem"))
+ return;
+
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+
+ if (lastInput && lastInput.value)
+ awAppendNewRow(setFocus);
+ else
+ if (setFocus)
+ awSetFocus(top.MAX_RECIPIENTS, lastInput);
+}
+
+function awReturnHit(inputElement)
+{
+ var row = awGetRowByInputElement(inputElement);
+ if (inputElement.value)
+ {
+ var nextInput = awGetInputElement(row+1);
+ if (!nextInput)
+ awAppendNewRow(true);
+ else
+ awSetFocus(row+1, nextInput);
+ }
+}
+
+function awDeleteRow(rowToDelete)
+{
+ /* When we delete a row, we must reset the id of others row in order to not break the sequence */
+ var maxRecipients = top.MAX_RECIPIENTS;
+ awRemoveRow(rowToDelete);
+
+ var numberOfCols = awGetNumberOfCols();
+ for (var row = rowToDelete + 1; row <= maxRecipients; row ++)
+ for (var col = 1; col <= numberOfCols; col++)
+ awGetElementByCol(row, col).setAttribute("id", "addressCol" + (col) + "#" + (row-1));
+
+ awTestRowSequence();
+}
+
+function awInputChanged(inputElement)
+{
+// AutoCompleteAddress(inputElement);
+
+ //Do we need to add a new row?
+ var lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ if (lastInput && lastInput.value && !top.doNotCreateANewRow)
+ awAppendNewRow(false);
+ top.doNotCreateANewRow = false;
+}
+
+function awInputElementName()
+{
+ if (inputElementType == "")
+ inputElementType = document.getElementById("addressCol1#1").localName;
+ return inputElementType;
+}
+
+function awAppendNewRow(setFocus)
+{
+ var body = document.getElementById("addressingWidget");
+ var listitem1 = awGetListItem(1);
+
+ if (body && listitem1)
+ {
+ var nextDummy = awGetNextDummyRow();
+ var newNode = listitem1.cloneNode(true);
+ if (nextDummy)
+ body.replaceChild(newNode, nextDummy);
+ else
+ body.appendChild(newNode);
+
+ top.MAX_RECIPIENTS++;
+
+ var input = newNode.getElementsByTagName(awInputElementName());
+ if (input && input.length == 1)
+ {
+ input[0].setAttribute("value", "");
+ input[0].setAttribute("id", "addressCol1#" + top.MAX_RECIPIENTS);
+
+ if (input[0].getAttribute('focused') != '')
+ input[0].removeAttribute('focused');
+ }
+ // focus on new input widget
+ if (setFocus && input )
+ awSetFocus(top.MAX_RECIPIENTS, input[0]);
+ }
+}
+
+
+// functions for accessing the elements in the addressing widget
+
+function awGetInputElement(row)
+{
+ return document.getElementById("addressCol1#" + row);
+}
+
+
+function _awSetFocus()
+{
+ var listbox = document.getElementById('addressingWidget');
+ try
+ {
+ var theNewRow = awGetListItem(top.awRow);
+
+ listbox.ensureElementIsVisible(theNewRow);
+ top.awInputElement.focus();
+ }
+ catch(ex)
+ {
+ top.awFocusRetry ++;
+ if (top.awFocusRetry < 8)
+ {
+ dump("_awSetFocus failed, try it again...\n");
+ setTimeout(_awSetFocus, 0);
+ }
+ else
+ dump("_awSetFocus failed, forget about it!\n");
+ }
+}
+
+function awTabFromRecipient(element, event)
+{
+ //If we are the last element in the listbox, we don't want to create a new row.
+ if (element == awGetInputElement(top.MAX_RECIPIENTS))
+ top.doNotCreateANewRow = true;
+}
+
+function DragOverAddressListTree(event)
+{
+ var validFlavor = false;
+ var dragSession = gDragService.getCurrentSession();
+
+ // XXX add support for other flavors here
+ if (dragSession.isDataFlavorSupported("text/x-moz-address")) {
+ dragSession.canDrop = true;
+ }
+}
+
+function DropOnAddressListTree(event)
+{
+ let dragSession = gDragService.getCurrentSession();
+ let trans;
+
+ try {
+ trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+ trans.addDataFlavor("text/x-moz-address");
+ }
+ catch (ex) {
+ return;
+ }
+
+ for (let i = 0; i < dragSession.numDropItems; ++i)
+ {
+ dragSession.getData(trans, i);
+ let dataObj = new Object();
+ let bestFlavor = new Object();
+ let len = new Object();
+ trans.getAnyTransferData(bestFlavor, dataObj, len);
+ if (dataObj)
+ dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsString);
+ if (!dataObj)
+ continue;
+
+ // pull the URL out of the data object
+ let address = dataObj.data.substring(0, len.value);
+ if (!address)
+ continue;
+
+ DropListAddress(event.target, address);
+ }
+}
+
+function DropListAddress(target, address)
+{
+ // Set focus on a new available, visible row.
+ awClickEmptySpace(target, true);
+ if (top.MAX_RECIPIENTS == 0)
+ top.MAX_RECIPIENTS = 1;
+
+ // Break apart the MIME-ready header address into individual addressees to
+ // add to the dialog.
+ let addresses = {}, names = {}, fullNames = {};
+ MailServices.headerParser.parseHeadersWithArray(address, addresses, names,
+ fullNames);
+ for (let full of fullNames.value)
+ {
+ let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
+ lastInput.value = full;
+ awAppendNewRow(true);
+ }
+}
+
+/* Allows extensions to register a listener function for
+ * when a mailing list is loaded. The listener function
+ * should take two parameters - the first being the
+ * mailing list being loaded, the second one being the
+ * current window document.
+ */
+function RegisterLoadListener(aListener)
+{
+ gLoadListeners.push(aListener);
+}
+
+/* Allows extensions to unload a load listener function.
+ */
+function UnregisterLoadListener(aListener)
+{
+ var fIndex = gLoadListeners.indexOf(aListener);
+ if (fIndex != -1)
+ gLoadListeners.splice(fIndex, 1);
+}
+
+/* Allows extensions to register a listener function for
+ * when a mailing list is saved. Like a load listener,
+ * the save listener should take two parameters: the first
+ * being a copy of the mailing list that is being saved,
+ * and the second being the current window document.
+ */
+function RegisterSaveListener(aListener)
+{
+ gSaveListeners.push(aListener);
+}
+
+/* Allows extensions to unload a save listener function.
+ */
+function UnregisterSaveListener(aListener)
+{
+ var fIndex = gSaveListeners.indexOf(aListener);
+ if (fIndex != -1)
+ gSaveListeners.splice(fIndex, 1);
+}
+
+/* Notifies all load listeners.
+ */
+function NotifyLoadListeners(aMailingList)
+{
+ for (let i = 0; i < gLoadListeners.length; i++)
+ gLoadListeners[i](aMailingList, document);
+}
+
+/* Notifies all save listeners.
+ */
+function NotifySaveListeners(aMailingList)
+{
+ for (let i = 0; i < gSaveListeners.length; i++)
+ gSaveListeners[i](aMailingList, document);
+}
+
diff --git a/mailnews/addrbook/content/abNewCardDialog.xul b/mailnews/addrbook/content/abNewCardDialog.xul
new file mode 100644
index 000000000..4f4d1a49b
--- /dev/null
+++ b/mailnews/addrbook/content/abNewCardDialog.xul
@@ -0,0 +1,33 @@
+<?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"
+ onload="OnLoadNewCard()"
+ ondialogaccept="return NewCardOKButton();">
+
+ <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/mailnews/addrbook/content/abResultsPane.js b/mailnews/addrbook/content/abResultsPane.js
new file mode 100644
index 000000000..58a1771cb
--- /dev/null
+++ b/mailnews/addrbook/content/abResultsPane.js
@@ -0,0 +1,502 @@
+/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 ; js-indent-level: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * Use of items in this file require:
+ *
+ * getSelectedDirectoryURI()
+ * returns the URI of the selected directory
+ * AbResultsPaneDoubleClick(card)
+ * Is called when the results pane is double-clicked, with the clicked card.
+ * AbEditCard(card)
+ * Is called when a card is to be edited, with the card as the parameter.
+ *
+ * The following function is only required if ResultsPaneController is used:
+ *
+ * goSetMenuValue()
+ * Core function in globalOverlay.js
+ */
+
+// List/card selections in the results pane.
+var kNothingSelected = 0;
+var kListsAndCards = 1;
+var kMultipleListsOnly = 2;
+var kSingleListOnly = 3;
+var kCardsOnly = 4;
+
+// Global Variables
+
+// gAbView holds an object with an nsIAbView interface
+var gAbView = null;
+// Holds a reference to the "abResultsTree" document element. Initially
+// set up by SetAbView.
+var gAbResultsTree = null;
+
+function SetAbView(aURI)
+{
+ // If we don't have a URI, just clear the view and leave everything else
+ // alone.
+ if (!aURI) {
+ gAbView.clearView();
+ return;
+ }
+
+ // If we do have a URI, we want to allow updating the review even if the
+ // URI is the same, as the search results may be different.
+
+ var sortColumn = kDefaultSortColumn;
+ var sortDirection = kDefaultAscending;
+
+ if (!gAbResultsTree) {
+ gAbResultsTree = document.getElementById("abResultsTree");
+ gAbResultsTree.controllers.appendController(ResultsPaneController);
+ }
+
+ if (gAbView) {
+ sortColumn = gAbView.sortColumn;
+ sortDirection = gAbView.sortDirection;
+ }
+ else {
+ if (gAbResultsTree.hasAttribute("sortCol"))
+ sortColumn = gAbResultsTree.getAttribute("sortCol");
+ var sortColumnNode = document.getElementById(sortColumn);
+ if (sortColumnNode && sortColumnNode.hasAttribute("sortDirection"))
+ sortDirection = sortColumnNode.getAttribute("sortDirection");
+ }
+
+ var directory = GetDirectoryFromURI(aURI);
+
+ if (!gAbView)
+ gAbView = Components.classes["@mozilla.org/addressbook/abview;1"]
+ .createInstance(Components.interfaces.nsIAbView);
+
+ var actualSortColumn = gAbView.setView(directory, GetAbViewListener(),
+ sortColumn, sortDirection);
+
+ gAbResultsTree.treeBoxObject.view =
+ gAbView.QueryInterface(Components.interfaces.nsITreeView);
+
+ UpdateSortIndicators(actualSortColumn, sortDirection);
+
+ // If the selected address book is LDAP and the search box is empty,
+ // inform the user of the empty results pane.
+ let abResultsTree = document.getElementById("abResultsTree");
+ let cardViewOuterBox = document.getElementById("CardViewOuterBox");
+ let blankResultsPaneMessageBox = document.getElementById("blankResultsPaneMessageBox");
+ if (aURI.startsWith("moz-abldapdirectory://") && !aURI.includes("?")) {
+ if (abResultsTree)
+ abResultsTree.hidden = true;
+ if (cardViewOuterBox)
+ cardViewOuterBox.hidden = true;
+ if (blankResultsPaneMessageBox)
+ blankResultsPaneMessageBox.hidden = false;
+ } else {
+ if (abResultsTree)
+ abResultsTree.hidden = false;
+ if (cardViewOuterBox)
+ cardViewOuterBox.hidden = false;
+ if (blankResultsPaneMessageBox)
+ blankResultsPaneMessageBox.hidden = true;
+ }
+}
+
+function CloseAbView()
+{
+ if (gAbView)
+ gAbView.clearView();
+}
+
+function GetOneOrMoreCardsSelected()
+{
+ return (gAbView && (gAbView.selection.getRangeCount() > 0));
+}
+
+function GetSelectedAddresses()
+{
+ return GetAddressesForCards(GetSelectedAbCards());
+}
+
+function GetNumSelectedCards()
+{
+ try {
+ return gAbView.selection.count;
+ }
+ catch (ex) {
+ }
+
+ // if something went wrong, return 0 for the count.
+ return 0;
+}
+
+function GetSelectedCardTypes()
+{
+ var cards = GetSelectedAbCards();
+ if (!cards) {
+ Components.utils.reportError("ERROR: GetSelectedCardTypes: |cards| is null.");
+ return kNothingSelected; // no view
+ }
+ var count = cards.length;
+ if (count == 0)
+ return kNothingSelected; // nothing selected
+
+ var mailingListCnt = 0;
+ var cardCnt = 0;
+ for (let i = 0; i < count; i++) {
+ // We can assume no values from GetSelectedAbCards will be null.
+ if (cards[i].isMailList)
+ mailingListCnt++;
+ else
+ cardCnt++;
+ }
+
+ return (mailingListCnt == 0) ? kCardsOnly :
+ (cardCnt > 0) ? kListsAndCards :
+ (mailingListCnt == 1) ? kSingleListOnly :
+ kMultipleListsOnly;
+}
+
+// NOTE, will return -1 if more than one card selected, or no cards selected.
+function GetSelectedCardIndex()
+{
+ if (!gAbView)
+ return -1;
+
+ var treeSelection = gAbView.selection;
+ if (treeSelection.getRangeCount() == 1) {
+ var start = new Object;
+ var end = new Object;
+ treeSelection.getRangeAt(0, start, end);
+ if (start.value == end.value)
+ return start.value;
+ }
+
+ return -1;
+}
+
+// NOTE, returns the card if exactly one card is selected, null otherwise
+function GetSelectedCard()
+{
+ var index = GetSelectedCardIndex();
+ return (index == -1) ? null : gAbView.getCardFromRow(index);
+}
+
+/**
+ * Return a (possibly empty) list of cards
+ *
+ * It pushes only non-null/empty element, if any, into the returned list.
+ */
+function GetSelectedAbCards()
+{
+ var abView = gAbView;
+
+ // if sidebar is open, and addressbook panel is open and focused,
+ // then use the ab view from sidebar (gCurFrame is from sidebarOverlay.js)
+ if (document.getElementById("sidebar-box")) {
+ const abPanelUrl =
+ "chrome://messenger/content/addressbook/addressbook-panel.xul";
+ if (gCurFrame &&
+ gCurFrame.getAttribute("src") == abPanelUrl &&
+ document.commandDispatcher.focusedWindow == gCurFrame.contentDocument.defaultView)
+ abView = gCurFrame.contentDocument.defaultView.gAbView;
+ }
+
+ if (!abView)
+ return [];
+
+ let cards = [];
+ var count = abView.selection.getRangeCount();
+ var current = 0;
+ for (let i = 0; i < count; ++i) {
+ let start = {};
+ let end = {};
+
+ abView.selection.getRangeAt(i, start, end);
+
+ for (let j = start.value; j <= end.value; ++j) {
+ // avoid inserting null element into the list. GetRangeAt() may be buggy.
+ let tmp = abView.getCardFromRow(j);
+ if (tmp) {
+ cards.push(tmp);
+ }
+ }
+ }
+ return cards;
+}
+
+// XXX todo
+// an optimization might be to make this return
+// the selected ranges, which would be faster
+// when the user does large selections, but for now, let's keep it simple.
+function GetSelectedRows()
+{
+ var selectedRows = "";
+
+ if (!gAbView)
+ return selectedRows;
+
+ var rangeCount = gAbView.selection.getRangeCount();
+ for (let i = 0; i < rangeCount; ++i) {
+ var start = new Object;
+ var end = new Object;
+ gAbView.selection.getRangeAt(i, start, end);
+ for (let j = start.value;j <= end.value; ++j) {
+ if (selectedRows)
+ selectedRows += ",";
+ selectedRows += j;
+ }
+ }
+
+ return selectedRows;
+}
+
+function AbSwapFirstNameLastName()
+{
+ if (gAbView)
+ gAbView.swapFirstNameLastName();
+}
+
+function AbEditSelectedCard()
+{
+ AbEditCard(GetSelectedCard());
+}
+
+function AbResultsPaneOnClick(event)
+{
+ // we only care about button 0 (left click) events
+ if (event.button != 0) return;
+
+ // all we need to worry about here is double clicks
+ // and column header clicks.
+ //
+ // we get in here for clicks on the "treecol" (headers)
+ // and the "scrollbarbutton" (scrollbar buttons)
+ // we don't want those events to cause a "double click"
+
+ var t = event.originalTarget;
+
+ if (t.localName == "treecol") {
+ var sortDirection;
+ var currentDirection = t.getAttribute("sortDirection");
+
+ // Revert the sort order. If none is set, use Ascending.
+ sortDirection = currentDirection == kDefaultAscending ?
+ kDefaultDescending : kDefaultAscending;
+
+ SortAndUpdateIndicators(t.id, sortDirection);
+ }
+ else if (t.localName == "treechildren") {
+ // figure out what row the click was in
+ var row = gAbResultsTree.treeBoxObject.getRowAt(event.clientX,
+ event.clientY);
+ if (row == -1)
+ return;
+
+ if (event.detail == 2)
+ AbResultsPaneDoubleClick(gAbView.getCardFromRow(row));
+ }
+}
+
+function AbSortAscending()
+{
+ var sortColumn = gAbResultsTree.getAttribute("sortCol");
+ SortAndUpdateIndicators(sortColumn, kDefaultAscending);
+}
+
+function AbSortDescending()
+{
+ var sortColumn = gAbResultsTree.getAttribute("sortCol");
+ SortAndUpdateIndicators(sortColumn, kDefaultDescending);
+}
+
+function SortResultPane(sortColumn)
+{
+ var sortDirection = kDefaultAscending;
+ if (gAbView)
+ sortDirection = gAbView.sortDirection;
+
+ SortAndUpdateIndicators(sortColumn, sortDirection);
+}
+
+function SortAndUpdateIndicators(sortColumn, sortDirection)
+{
+ UpdateSortIndicators(sortColumn, sortDirection);
+
+ if (gAbView)
+ gAbView.sortBy(sortColumn, sortDirection);
+}
+
+function UpdateSortIndicators(colID, sortDirection)
+{
+ var sortedColumn = null;
+
+ // set the sort indicator on the column we are sorted by
+ if (colID) {
+ sortedColumn = document.getElementById(colID);
+ if (sortedColumn) {
+ sortedColumn.setAttribute("sortDirection",sortDirection);
+ gAbResultsTree.setAttribute("sortCol", colID);
+ }
+ }
+
+ // remove the sort indicator from all the columns
+ // except the one we are sorted by
+ var currCol = gAbResultsTree.firstChild.firstChild;
+ while (currCol) {
+ if (currCol != sortedColumn && currCol.localName == "treecol")
+ currCol.removeAttribute("sortDirection");
+ currCol = currCol.nextSibling;
+ }
+}
+
+function InvalidateResultsPane()
+{
+ if (gAbResultsTree)
+ gAbResultsTree.treeBoxObject.invalidate();
+}
+
+// Controller object for Results Pane
+var ResultsPaneController =
+{
+ supportsCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ case "cmd_delete":
+ case "button_delete":
+ case "cmd_properties":
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ isCommandEnabled: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ return true;
+ case "cmd_delete":
+ case "button_delete":
+ var numSelected;
+ var enabled = false;
+ if (gAbView && gAbView.selection) {
+ if (gAbView.directory)
+ enabled = !gAbView.directory.readOnly;
+ numSelected = gAbView.selection.count;
+ }
+ else
+ numSelected = 0;
+
+ // fix me, don't update on isCommandEnabled
+ if (command == "cmd_delete") {
+ switch (GetSelectedCardTypes()) {
+ case kSingleListOnly:
+ goSetMenuValue(command, "valueList");
+ break;
+ case kMultipleListsOnly:
+ goSetMenuValue(command, "valueLists");
+ break;
+ case kListsAndCards:
+ goSetMenuValue(command, "valueItems");
+ break;
+ case kCardsOnly:
+ default:
+ if (numSelected < 2)
+ goSetMenuValue(command, "valueCard");
+ else
+ goSetMenuValue(command, "valueCards");
+ break;
+ }
+ }
+ return (enabled && (numSelected > 0));
+ case "cmd_properties":
+ // Temporary fix for SeaMonkey (see bug 1318852).
+ // goSetLabelAccesskeyTooltiptext() is only defined in mail/.
+ // This will be removed in due course and therefore the
+ // block wasn't indented.
+ if (typeof goSetLabelAccesskeyTooltiptext == "function") {
+ let labelAttr = "valueGeneric";
+ let accKeyAttr = "valueGenericAccessKey";
+ let tooltipTextAttr = "valueGenericTooltipText";
+ switch (GetSelectedCardTypes()) {
+ // Set cmd_properties UI according to the type of the selected item(s),
+ // even with multiple selections for which cmd_properties is
+ // not yet available and hence disabled.
+ case kMultipleListsOnly:
+ case kSingleListOnly:
+ labelAttr = "valueMailingList";
+ accKeyAttr = "valueMailingListAccessKey";
+ tooltipTextAttr = "valueMailingListTooltipText";
+ break;
+ case kCardsOnly:
+ labelAttr = "valueContact";
+ accKeyAttr = "valueContactAccessKey";
+ tooltipTextAttr = "valueContactTooltipText";
+ break;
+ case kListsAndCards:
+ default:
+ //use generic set of attributes declared above
+ break;
+ }
+ // This code is shared between main AB and composition's contacts sidebar.
+ // Note that in composition, there's no cmd_properties-button (yet);
+ // the resulting dump() should be ignored.
+ goSetLabelAccesskeyTooltiptext("cmd_properties-button", null, null,
+ tooltipTextAttr);
+ goSetLabelAccesskeyTooltiptext("cmd_properties-contextMenu",
+ labelAttr, accKeyAttr);
+ goSetLabelAccesskeyTooltiptext("cmd_properties-menu",
+ labelAttr, accKeyAttr);
+ }
+ // While "Edit Contact" dialogue is still modal (bug 115904, bug 135126),
+ // only enable "Properties" button for single selection; then fix bug 119999.
+ return (GetNumSelectedCards() == 1);
+ case "cmd_newlist":
+ case "cmd_newCard":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function(command)
+ {
+ switch (command) {
+ case "cmd_selectAll":
+ if (gAbView)
+ gAbView.selectAll();
+ break;
+ case "cmd_delete":
+ case "button_delete":
+ AbDelete();
+ break;
+ case "cmd_properties":
+ AbEditSelectedCard();
+ 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 SelectFirstCard()
+{
+ if (gAbView && gAbView.selection && (gAbView.selection.count > 0))
+ gAbView.selection.select(0);
+}
diff --git a/mailnews/addrbook/content/abResultsPaneOverlay.xul b/mailnews/addrbook/content/abResultsPaneOverlay.xul
new file mode 100644
index 000000000..6e6d9dbc9
--- /dev/null
+++ b/mailnews/addrbook/content/abResultsPaneOverlay.xul
@@ -0,0 +1,90 @@
+<?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 type="application/javascript" src="chrome://messenger/content/addressbook/abResultsPane.js"/>
+<script type="application/javascript" src="chrome://global/content/nsDragAndDrop.js"/>
+<script type="application/javascript" 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, 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="_AimScreenName"
+ persist="hidden ordinal width sortDirection" flex="1"
+ label="&ScreenName.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;"/>
+
+ <!-- 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/mailnews/addrbook/content/addrbookWidgets.xml b/mailnews/addrbook/content/addrbookWidgets.xml
new file mode 100644
index 000000000..2481d5cd9
--- /dev/null
+++ b/mailnews/addrbook/content/addrbookWidgets.xml
@@ -0,0 +1,439 @@
+<?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/. -->
+
+<bindings id="addrbookBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="addrbooks-menupopup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIAbListener">
+ <!-- A cache of nsIAbDirectory objects. -->
+ <field name="_directories">[]</field>
+
+ <!-- Represents the nsIAbDirectory attribute used as the value of the
+ parent menulist. Defaults to URI but can be e.g. dirPrefId -->
+ <field name="_value">this.getAttribute("value") || "URI"</field>
+
+ <constructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/mailServices.js");
+ // Init the address book cache.
+ const nsIAbDirectory = Components.interfaces.nsIAbDirectory;
+ let directories = MailServices.ab.directories;
+ while (directories && directories.hasMoreElements()) {
+ var ab = directories.getNext();
+ if (ab instanceof nsIAbDirectory && this._matches(ab))
+ this._directories.push(ab);
+ }
+
+ this._directories.sort(this._compare);
+
+ // Now create menuitems for all displayed directories.
+ var menulist = this.parentNode;
+ var value = this._value;
+ this._directories.forEach(function (ab) {
+ menulist.appendItem(ab.dirName, ab[value]);
+ });
+ if (this.hasAttribute("none")) {
+ // Create a dummy menuitem representing no selection.
+ this._directories.unshift(null);
+ menulist.insertItemAt(0, this.getAttribute("none"), "");
+ }
+
+ // Attempt to select the persisted or otherwise first directory.
+ menulist.value = menulist.value;
+ if (!menulist.selectedItem && this.hasChildNodes())
+ menulist.selectedIndex = 0;
+
+ const nsIAbListener = Components.interfaces.nsIAbListener;
+ // Add a listener so we can update correctly if the list should change
+ MailServices.ab.addAddressBookListener(this,
+ nsIAbListener.itemAdded |
+ nsIAbListener.directoryRemoved |
+ nsIAbListener.itemChanged);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Components.utils.import("resource:///modules/mailServices.js");
+ MailServices.ab.removeAddressBookListener(this);
+
+ // Empty out anything in the list.
+ while (this.hasChildNodes())
+ this.lastChild.remove();
+ ]]>
+ </destructor>
+
+ <!-- nsIAbListener methods -->
+ <method name="onItemAdded">
+ <parameter name="aParentDir"/>
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ // Are we interested in this new directory?
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList && this._matches(aItem)) {
+ this._directories.push(aItem);
+ this._directories.sort(this._compare);
+ // Insert the new menuitem at the position to which it was sorted.
+ this.parentNode.insertItemAt(this._directories.indexOf(aItem),
+ aItem.dirName, aItem[this._value]);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onItemRemoved">
+ <parameter name="aParentDir"/>
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList) {
+ // Find the item in the list to remove
+ // We can't use indexOf here because we need loose equality
+ for (var index = this._directories.length; --index >= 0; )
+ if (this._directories[index] == aItem)
+ break;
+ if (index != -1)
+ // Are we removing the selected directory?
+ if (this.parentNode.selectedItem ==
+ this.removeChild(this.childNodes[index]))
+ // If so, try to select the first directory, if available.
+ if (this.hasChildNodes())
+ this.firstChild.doCommand();
+ else
+ this.parentNode.selectedItem = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="onItemPropertyChanged">
+ <parameter name="aItem"/>
+ <parameter name="aProperty"/>
+ <parameter name="aOldValue"/>
+ <parameter name="aNewValue"/>
+ <body><![CDATA[
+ if (aItem instanceof Components.interfaces.nsIAbDirectory &&
+ !aItem.isMailList) {
+ // Find the item in the list to rename.
+ // We can't use indexOf here because we need loose equality
+ for (var oldIndex = this._directories.length; --oldIndex >= 0; )
+ if (this._directories[oldIndex] == aItem)
+ break;
+ if (oldIndex != -1) {
+ // Cache the matching item so that we can use indexOf next time.
+ aItem = this._directories[oldIndex];
+ var child = this.childNodes[oldIndex];
+ child.label = aItem.dirName;
+ this._directories.sort(this._compare);
+ // Reorder the menuitems if renaming changed the directory index.
+ var newIndex = this._directories.indexOf(aItem);
+ if (newIndex < oldIndex)
+ this.insertBefore(child, this.childNodes[newIndex]);
+ else if (newIndex > oldIndex)
+ this.insertBefore(child, this.childNodes[newIndex].nextSibling);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- Private methods -->
+ <!-- Tests to see whether this directory should display in the list. -->
+ <method name="_matches">
+ <parameter name="ab"/>
+ <body><![CDATA[
+ // This condition is used for instance when creating cards
+ if (this.getAttribute("writable") == "true" && ab.readOnly)
+ return false;
+
+ // This condition is used for instance when creating mailing lists
+ if (this.getAttribute("supportsmaillists") == "true" &&
+ !ab.supportsMailingLists)
+ return false;
+
+ return this.getAttribute(ab.isRemote ? "localonly" : "remoteonly") != "true";
+ ]]></body>
+ </method>
+
+ <!-- Used to sort directories in order -->
+ <method name="_compare">
+ <parameter name="a"/>
+ <parameter name="b"/>
+ <body><![CDATA[
+ // Null at the very top.
+ if (!a)
+ return -1;
+
+ if (!b)
+ return 1;
+
+ // Personal at the top.
+ const kPersonalAddressbookURI = "moz-abmdbdirectory://abook.mab";
+ if (a.URI == kPersonalAddressbookURI)
+ return -1;
+
+ if (b.URI == kPersonalAddressbookURI)
+ return 1;
+
+ // Collected at the bottom.
+ const kCollectedAddressbookURI = "moz-abmdbdirectory://history.mab";
+ if (a.URI == kCollectedAddressbookURI)
+ return 1;
+
+ if (b.URI == kCollectedAddressbookURI)
+ return -1;
+
+ // Sort books of the same type by name.
+ if (a.dirType == b.dirType)
+ return a.dirName.localeCompare(b.dirName);
+
+ // If one of the dirTypes is PAB and the other is something else,
+ // then the other will go below the one of type PAB.
+ const PABDirectory = 2;
+ if (a.dirType == PABDirectory)
+ return -1;
+
+ if (b.dirType == PABDirectory)
+ return 1;
+
+ // Sort anything else by the dir type.
+ return a.dirType - b.dirType;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="map-list"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation>
+ <property name="mapURL" readonly="true">
+ <getter><![CDATA[
+ return this._createMapItURL();
+ ]]></getter>
+ </property>
+
+ <constructor>
+ <![CDATA[
+ this._setWidgetDisabled(true);
+ ]]>
+ </constructor>
+
+ <!--
+ Initializes the necessary address data from an addressbook card.
+ @param aCard A nsIAbCard to get the address data from.
+ @param aAddrPrefix A prefix of the card properties to use. Use "Home" or "Work".
+ -->
+ <method name="initMapAddressFromCard">
+ <parameter name="aCard"/>
+ <parameter name="aAddrPrefix"/>
+ <body><![CDATA[
+ let mapItURLFormat = this._getMapURLPref(0);
+ let doNotShowMap = !mapItURLFormat || !aAddrPrefix || !aCard;
+ this._setWidgetDisabled(doNotShowMap);
+ if (doNotShowMap)
+ return;
+
+ this.setAttribute("map_address1", aCard.getProperty(aAddrPrefix + "Address"));
+ this.setAttribute("map_address2", aCard.getProperty(aAddrPrefix + "Address2"));
+ this.setAttribute("map_city" , aCard.getProperty(aAddrPrefix + "City"));
+ this.setAttribute("map_state" , aCard.getProperty(aAddrPrefix + "State"));
+ this.setAttribute("map_zip" , aCard.getProperty(aAddrPrefix + "ZipCode"));
+ this.setAttribute("map_country" , aCard.getProperty(aAddrPrefix + "Country"));
+ ]]></body>
+ </method>
+
+ <!--
+ Initializes the necessary address data from passed in values.
+ -->
+ <method name="initMapAddress">
+ <parameter name="aAddr1"/>
+ <parameter name="aAddr2"/>
+ <parameter name="aCity"/>
+ <parameter name="aState"/>
+ <parameter name="aZip"/>
+ <parameter name="aCountry"/>
+ <body><![CDATA[
+ let mapItURLFormat = this._getMapURLPref(0);
+ let doNotShowMap = !mapItURLFormat || !(aAddr1 + aAddr2 + aCity + aState + aZip + aCountry);
+ this._setWidgetDisabled(doNotShowMap);
+ if (doNotShowMap)
+ return;
+
+ this.setAttribute("map_address1", aAddr1);
+ this.setAttribute("map_address2", aAddr2);
+ this.setAttribute("map_city" , aCity);
+ this.setAttribute("map_state" , aState);
+ this.setAttribute("map_zip" , aZip);
+ this.setAttribute("map_country" , aCountry);
+ ]]></body>
+ </method>
+
+ <!--
+ Sets the disabled/enabled state of the parent widget (e.g. a button).
+ -->
+ <method name="_setWidgetDisabled">
+ <parameter name="aDisabled"/>
+ <body><![CDATA[
+ this.parentNode.disabled = aDisabled;
+ ]]></body>
+ </method>
+
+ <!--
+ Returns the Map service URL from localized pref. Returns null if there
+ is none at the given index.
+ @param aIndex The index of the service to return. 0 is the default service.
+ -->
+ <method name="_getMapURLPref">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ let url = null;
+ if (!aIndex) {
+ url = Services.prefs.getComplexValue("mail.addr_book.mapit_url.format",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } else {
+ try {
+ url = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + aIndex + ".format",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ }
+
+ return url;
+ ]]></body>
+ </method>
+
+ <!--
+ Builds menuitem elements representing map services defined in prefs
+ and attaches them to the specified button.
+ -->
+ <method name="_listMapServices">
+ <body><![CDATA[
+ let index = 1;
+ let itemFound = true;
+ let defaultFound = false;
+ const kUserIndex = 100;
+ let aMapList = this;
+ while (aMapList.hasChildNodes()) {
+ aMapList.lastChild.remove();
+ }
+
+ let defaultUrl = this._getMapURLPref(0);
+
+ // Creates the menuitem with supplied data.
+ function addMapService(aUrl, aName) {
+ let item = document.createElement("menuitem");
+ item.setAttribute("url", aUrl);
+ item.setAttribute("label", aName);
+ item.setAttribute("type", "radio");
+ item.setAttribute("name", "mapit_service");
+ if (aUrl == defaultUrl)
+ item.setAttribute("checked", "true");
+ aMapList.appendChild(item);
+ }
+
+ // Generates a useful generic name by cutting out only the host address.
+ function generateName(aUrl) {
+ return new URL(aUrl).hostname;
+ }
+
+ // Add all defined map services as menuitems.
+ while (itemFound) {
+ let urlName;
+ let urlTemplate = this._getMapURLPref(index);
+ if (!urlTemplate) {
+ itemFound = false;
+ } else {
+ // Name is not mandatory, generate one if not found.
+ try {
+ urlName = Services.prefs.getComplexValue("mail.addr_book.mapit_url." + index + ".name",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ urlName = generateName(urlTemplate);
+ }
+ }
+ if (itemFound) {
+ addMapService(urlTemplate, urlName);
+ index++;
+ if (urlTemplate == defaultUrl)
+ defaultFound = true;
+ } else if (index < kUserIndex) {
+ // After iterating the base region provided urls, check for user defined ones.
+ index = kUserIndex;
+ itemFound = true;
+ }
+ }
+ if (!defaultFound) {
+ // If user had put a customized map URL into mail.addr_book.mapit_url.format
+ // preserve it as a new map service named with the URL.
+ // 'index' now points to the first unused entry in prefs.
+ let defaultName = generateName(defaultUrl);
+ addMapService(defaultUrl, defaultName);
+ Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".format",
+ defaultUrl);
+ Services.prefs.setCharPref("mail.addr_book.mapit_url." + index + ".name",
+ defaultName);
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ Save user selected mapping service.
+ @param aItem The chosen menuitem with map service.
+ -->
+ <method name="_chooseMapService">
+ <parameter name="aItem"/>
+ <body><![CDATA[
+ // Save selected URL as the default.
+ let defaultUrl = Components.classes["@mozilla.org/pref-localizedstring;1"]
+ .createInstance(Components.interfaces.nsIPrefLocalizedString);
+ defaultUrl.data = aItem.getAttribute("url");
+ Services.prefs.setComplexValue("mail.addr_book.mapit_url.format",
+ Components.interfaces.nsIPrefLocalizedString, defaultUrl);
+ ]]></body>
+ </method>
+
+ <!--
+ Generate map URL in the href attribute.
+ -->
+ <method name="_createMapItURL">
+ <body><![CDATA[
+ let urlFormat = this._getMapURLPref(0);
+ if (!urlFormat)
+ return null;
+
+ let address1 = this.getAttribute("map_address1");
+ let address2 = this.getAttribute("map_address2");
+ let city = this.getAttribute("map_city");
+ let state = this.getAttribute("map_state");
+ let zip = this.getAttribute("map_zip");
+ let country = this.getAttribute("map_country");
+
+ urlFormat = urlFormat.replace("@A1", encodeURIComponent(address1));
+ urlFormat = urlFormat.replace("@A2", encodeURIComponent(address2));
+ urlFormat = urlFormat.replace("@CI", encodeURIComponent(city));
+ urlFormat = urlFormat.replace("@ST", encodeURIComponent(state));
+ urlFormat = urlFormat.replace("@ZI", encodeURIComponent(zip));
+ urlFormat = urlFormat.replace("@CO", encodeURIComponent(country));
+
+ return urlFormat;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ this._chooseMapService(event.target);
+ event.stopPropagation();
+ ]]>
+ </handler>
+ <handler event="popupshowing">
+ <![CDATA[
+ this._listMapServices();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/mailnews/addrbook/content/print.css b/mailnews/addrbook/content/print.css
new file mode 100644
index 000000000..0f6965379
--- /dev/null
+++ b/mailnews/addrbook/content/print.css
@@ -0,0 +1,94 @@
+/* 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/. */
+
+directory {
+ display: block;
+}
+
+section {
+ display:block;
+ margin: 15px 20px;
+}
+
+sectiontitle {
+ display:block;
+ font-weight:bold;
+ font-family:verdana;
+ font-size:small;
+}
+
+labelrow {
+ display:block;
+}
+
+label {
+ display:inline;
+ font-family:verdana;
+ font-size:small;
+}
+
+GeneratedName {
+ display:block;
+ font-weight:bold;
+ font-family:verdana;
+ margin-top: 20px;
+ margin-bottom: 3px;
+ margin-inline-end: 10px;
+ margin-inline-start: 10px;
+}
+
+FirstName, LastName,
+HomeAddress, HomeAddress2, HomeCountry,
+WorkAddress, WorkAddress2, WorkCountry,
+JobTitle, Department, Company, Notes {
+ display: block;
+ font-family: verdana;
+ font-size: small;
+}
+
+DisplayName, NickName,
+WorkPhone, HomePhone, FaxNumber, PagerNumber, CellularNumber,
+HomeCity, HomeState, HomeZipCode,
+WorkCity, WorkState, WorkZipCode,
+Custom1, Custom2, Custom3, Custom4 {
+ display: inline;
+ font-family: verdana;
+ font-size: small;
+}
+
+PrimaryEmail, SecondEmail,
+WebPage1, WebPage2 {
+ display: block;
+ font-family: verdana;
+ font-size: small;
+ text-decoration: underline;
+ color: #666666;
+}
+
+separator {
+ display: block;
+ border-bottom: 1px solid #000000;
+ margin-top: 0px;
+ margin-bottom: 0px;
+ margin-inline-end: 0px;
+ margin-inline-start: 10px;
+}
+
+table {
+ display: table;
+}
+
+tr {
+ display: table-row;
+}
+
+td {
+ display: table-cell;
+ vertical-align: top;
+}
+
+PreferMailFormat {
+ display: none;
+}
+