diff options
Diffstat (limited to 'mailnews/addrbook/content')
-rw-r--r-- | mailnews/addrbook/content/abAddressBookNameDialog.js | 72 | ||||
-rw-r--r-- | mailnews/addrbook/content/abAddressBookNameDialog.xul | 26 | ||||
-rw-r--r-- | mailnews/addrbook/content/abDragDrop.js | 424 | ||||
-rw-r--r-- | mailnews/addrbook/content/abEditCardDialog.xul | 17 | ||||
-rw-r--r-- | mailnews/addrbook/content/abMailListDialog.js | 613 | ||||
-rw-r--r-- | mailnews/addrbook/content/abNewCardDialog.xul | 33 | ||||
-rw-r--r-- | mailnews/addrbook/content/abResultsPane.js | 502 | ||||
-rw-r--r-- | mailnews/addrbook/content/abResultsPaneOverlay.xul | 90 | ||||
-rw-r--r-- | mailnews/addrbook/content/addrbookWidgets.xml | 439 | ||||
-rw-r--r-- | mailnews/addrbook/content/print.css | 94 |
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; +} + |