Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1

This commit is contained in:
Matt A. Tobin 2019-11-03 00:17:46 -04:00 • committed by Roy Tam
commit e400f4130a
1564 changed files with 510348 additions and 0 deletions

View file

@ -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() == "";
}

View file

@ -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>

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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>

View file

@ -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);
}

View file

@ -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>

View file

@ -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>

View file

@ -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;
}