summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/content/abResultsPane.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/addrbook/content/abResultsPane.js')
-rw-r--r--comm/mailnews/addrbook/content/abResultsPane.js482
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);
+}