diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mailnews/addrbook/content/abResultsPane.js | 482 |
1 files changed, 482 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/content/abResultsPane.js b/comm/mailnews/addrbook/content/abResultsPane.js new file mode 100644 index 0000000000..2a4bd99f3b --- /dev/null +++ b/comm/mailnews/addrbook/content/abResultsPane.js @@ -0,0 +1,482 @@ +/* -*- 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/. */ + +/* import-globals-from ../../../mail/base/content/utilityOverlay.js */ +/* import-globals-from ../../../mail/components/addrbook/content/abCommon.js */ +/* import-globals-from abView.js */ + +/** + * Use of items in this file require: + * + * AbResultsPaneDoubleClick(card) + * Is called when the results pane is double-clicked, with the clicked card. + * GetAbViewListener() + * Called when creating a new view + */ +/* globals AbResultsPaneDoubleClick, GetAbViewListener */ // abContactsPane.js or abSearchDialog.js + +var kDefaultSortColumn = "GeneratedName"; + +// List/card selections in the results pane. +var kNothingSelected = 0; +var kListsAndCards = 1; +var kMultipleListsOnly = 2; +var kSingleListOnly = 3; +var kCardsOnly = 4; + +// Global Variables + +// Holds a reference to the "abResultsTree" document element. Initially +// set up by SetAbView. +var gAbResultsTree = null; +// gAbView is the current value of gAbResultsTree.view, without passing +// through XPCOM, so we can access extra functions if necessary. +var gAbView = null; + +function SetAbView(aURI, aSearchQuery, aSearchString) { + // If we don't have a URI, just clear the view and leave everything else + // alone. + if (!aURI) { + if (gAbView) { + CloseAbView(); + } + 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"); + } + } + + gAbView = gAbResultsTree.view = new ABView( + GetDirectoryFromURI(aURI), + aSearchQuery, + aSearchString, + GetAbViewListener(), + sortColumn, + sortDirection + ).QueryInterface(Ci.nsITreeView); + window.dispatchEvent(new CustomEvent("viewchange")); + + UpdateSortIndicators(sortColumn, 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://") && !aSearchQuery) { + 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() { + gAbView = null; + if (gAbResultsTree) { + gAbResultsTree.view = null; + } +} + +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) { + console.error("ERROR: GetSelectedCardTypes: |cards| is null."); + return kNothingSelected; // no view + } + var count = cards.length; + if (count == 0) { + // Nothing selected. + return kNothingSelected; + } + + 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++; + } + } + + if (mailingListCnt == 0) { + return kCardsOnly; + } + if (cardCnt > 0) { + return kListsAndCards; + } + if (mailingListCnt == 1) { + return kSingleListOnly; + } + return 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 = {}; + var end = {}; + 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 (!abView?.selection) { + return []; + } + + let cards = []; + var count = abView.selection.getRangeCount(); + 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 = {}; + var end = {}; + gAbView.selection.getRangeAt(i, start, end); + for (let j = start.value; j <= end.value; ++j) { + if (selectedRows) { + selectedRows += ","; + } + selectedRows += j; + } + } + + return selectedRows; +} + +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.target; + + 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.getRowAt(event.clientX, event.clientY); + if (row == -1) { + return; + } + + if (event.detail == 2) { + AbResultsPaneDoubleClick(gAbView.getCardFromRow(row)); + } + } +} + +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.firstElementChild.firstElementChild; + while (currCol) { + if (currCol != sortedColumn && currCol.localName == "treecol") { + currCol.removeAttribute("sortDirection"); + } + currCol = currCol.nextElementSibling; + } +} + +// Controller object for Results Pane +var ResultsPaneController = { + supportsCommand(command) { + switch (command) { + case "cmd_selectAll": + case "cmd_delete": + case "button_delete": + case "cmd_print": + case "cmd_printcard": + return true; + default: + return false; + } + }, + + isCommandEnabled(command) { + switch (command) { + case "cmd_selectAll": + return true; + case "cmd_delete": + case "button_delete": { + let numSelected; + let enabled = false; + if (gAbView && gAbView.selection) { + if (gAbView.directory) { + enabled = !gAbView.directory.readOnly; + } else { + enabled = true; + } + numSelected = gAbView.selection.count; + } else { + numSelected = 0; + } + enabled = enabled && numSelected > 0; + + if (enabled && !gAbView?.directory) { + // Undefined gAbView.directory means "All Address Books" is selected. + // Disable the menu/button if any selected card is from a read only + // directory. + enabled = !GetSelectedAbCards().some( + card => + MailServices.ab.getDirectoryFromUID(card.directoryUID).readOnly + ); + } + + if (command == "cmd_delete") { + switch (GetSelectedCardTypes()) { + case kSingleListOnly: + updateDeleteControls("valueList"); + break; + case kMultipleListsOnly: + updateDeleteControls("valueLists"); + break; + case kListsAndCards: + updateDeleteControls("valueItems"); + break; + case kCardsOnly: + default: + updateDeleteControls( + numSelected < 2 ? "valueCard" : "valueCards" + ); + } + } + return enabled; + } + case "cmd_print": + // cmd_print is currently only used in SeaMonkey. + // Prevent printing when we don't have an opener (browserDOMWindow is + // null). + let enabled = window.browserDOMWindow && GetNumSelectedCards() > 0; + document.querySelectorAll("[command=cmd_print]").forEach(e => { + e.disabled = !enabled; + }); + return enabled; + case "cmd_printcard": + // Prevent printing when we don't have an opener (browserDOMWindow is + // null). + return window.browserDOMWindow && GetNumSelectedCards() > 0; + default: + return false; + } + }, + + doCommand(command) { + switch (command) { + case "cmd_selectAll": + if (gAbView) { + gAbView.selection.selectAll(); + } + break; + case "cmd_delete": + case "button_delete": + AbDelete(); + break; + } + }, +}; + +function updateDeleteControls( + labelAttribute, + accessKeyAttribute = "accesskeyDefault" +) { + goSetMenuValue("cmd_delete", labelAttribute); + goSetAccessKey("cmd_delete", accessKeyAttribute); + + // The toolbar button doesn't update itself from the command. Do that now. + let button = document.getElementById("button-abdelete"); + if (!button) { + return; + } + + let command = document.getElementById("cmd_delete"); + button.label = command.getAttribute("label"); + button.setAttribute( + "tooltiptext", + button.getAttribute( + labelAttribute == "valueCardDAV" ? "tooltipCardDAV" : "tooltipDefault" + ) + ); +} + +/** + * Generate a comma separated list of addresses from the given cards. + * + * @param {nsIAbCard[]} cards - The cards to get addresses for. + * @returns {string} A string of comma separated mailboxes. + */ +function GetAddressesForCards(cards) { + if (!cards) { + return ""; + } + + return cards + .map(makeMimeAddressFromCard) + .filter(addr => addr) + .join(","); +} + +/** + * Make a MIME encoded string output of the card. This will make a difference + * e.g. in scenarios where non-ASCII is used in the mailbox, or when then + * display name include special characters such as comma. + * + * @param {nsIAbCard} card - The card to use. + * @returns {string} A MIME encoded mailbox representation of the card. + */ +function makeMimeAddressFromCard(card) { + if (!card) { + return ""; + } + + let email; + if (card.isMailList) { + let directory = GetDirectoryFromURI(card.mailListURI); + email = directory.description || card.displayName; + } else { + email = card.emailAddresses[0]; + } + return MailServices.headerParser.makeMimeAddress(card.displayName, email); +} |