summaryrefslogtreecommitdiffstats
path: root/comm/mail/base/content/SearchDialog.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mail/base/content/SearchDialog.js650
1 files changed, 650 insertions, 0 deletions
diff --git a/comm/mail/base/content/SearchDialog.js b/comm/mail/base/content/SearchDialog.js
new file mode 100644
index 0000000000..127370d7f2
--- /dev/null
+++ b/comm/mail/base/content/SearchDialog.js
@@ -0,0 +1,650 @@
+/* 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 ../../../mailnews/extensions/newsblog/newsblogOverlay.js */
+/* import-globals-from ../../../mailnews/search/content/searchTerm.js */
+/* import-globals-from folderDisplay.js */
+/* import-globals-from globalOverlay.js */
+/* import-globals-from threadPane.js */
+
+/* globals nsMsgStatusFeedback */ // From mailWindow.js
+
+"use strict";
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+var { TagUtils } = ChromeUtils.import("resource:///modules/TagUtils.jsm");
+
+var messenger;
+var msgWindow;
+
+var gCurrentFolder;
+
+var gFolderDisplay;
+
+var gFolderPicker;
+var gStatusFeedback;
+var gSearchBundle;
+
+// Datasource search listener -- made global as it has to be registered
+// and unregistered in different functions.
+var gDataSourceSearchListener;
+var gViewSearchListener;
+
+var gSearchStopButton;
+
+// Should we try to search online?
+var gSearchOnline = false;
+
+window.addEventListener("load", searchOnLoad);
+window.addEventListener("unload", event => {
+ onSearchStop();
+ searchOnUnload();
+});
+
+// Controller object for search results thread pane
+var nsSearchResultsController = {
+ supportsCommand(command) {
+ switch (command) {
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ case "cmd_open":
+ case "file_message_button":
+ case "open_in_folder_button":
+ case "saveas_vf_button":
+ case "cmd_selectAll":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ // this controller only handles commands
+ // that rely on items being selected in
+ // the search results pane.
+ isCommandEnabled(command) {
+ var enabled = true;
+
+ switch (command) {
+ case "open_in_folder_button":
+ if (gFolderDisplay.selectedCount != 1) {
+ enabled = false;
+ }
+ break;
+ case "cmd_delete":
+ case "cmd_shiftDelete":
+ case "button_delete":
+ // this assumes that advanced searches don't cross accounts
+ if (gFolderDisplay.selectedCount <= 0) {
+ enabled = false;
+ }
+ break;
+ case "saveas_vf_button":
+ // need someway to see if there are any search criteria...
+ return true;
+ case "cmd_selectAll":
+ return true;
+ default:
+ if (gFolderDisplay.selectedCount <= 0) {
+ enabled = false;
+ }
+ break;
+ }
+
+ return enabled;
+ },
+
+ doCommand(command) {
+ switch (command) {
+ case "cmd_open":
+ MsgOpenSelectedMessages();
+ return true;
+
+ case "cmd_delete":
+ case "button_delete":
+ MsgDeleteSelectedMessages(Ci.nsMsgViewCommandType.deleteMsg);
+ return true;
+
+ case "cmd_shiftDelete":
+ MsgDeleteSelectedMessages(Ci.nsMsgViewCommandType.deleteNoTrash);
+ return true;
+
+ case "open_in_folder_button":
+ OpenInFolder();
+ return true;
+
+ case "saveas_vf_button":
+ saveAsVirtualFolder();
+ return true;
+
+ case "cmd_selectAll":
+ // move the focus to the search results pane
+ GetThreadTree().focus();
+ gFolderDisplay.doCommand(Ci.nsMsgViewCommandType.selectAll);
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ onEvent(event) {},
+};
+
+function UpdateMailSearch(caller) {
+ document.commandDispatcher.updateCommands("mail-search");
+}
+
+function SetAdvancedSearchStatusText(aNumHits) {}
+
+/**
+ * Subclass the FolderDisplayWidget to deal with UI specific to the search
+ * window.
+ */
+function SearchFolderDisplayWidget() {
+ FolderDisplayWidget.call(this);
+}
+
+SearchFolderDisplayWidget.prototype = {
+ __proto__: FolderDisplayWidget.prototype,
+
+ // folder display will want to show the thread pane; we need do nothing
+ _showThreadPane() {},
+
+ onSearching(aIsSearching) {
+ if (aIsSearching) {
+ // Search button becomes the "stop" button
+ gSearchStopButton.setAttribute(
+ "label",
+ gSearchBundle.GetStringFromName("labelForStopButton")
+ );
+ gSearchStopButton.setAttribute(
+ "accesskey",
+ gSearchBundle.GetStringFromName("labelForStopButton.accesskey")
+ );
+
+ // update our toolbar equivalent
+ UpdateMailSearch("new-search");
+ // spin the meteors
+ gStatusFeedback._startMeteors();
+ // tell the user that we're searching
+ gStatusFeedback.showStatusString(
+ gSearchBundle.GetStringFromName("searchingMessage")
+ );
+ } else {
+ // Stop button resumes being the "search" button
+ gSearchStopButton.setAttribute(
+ "label",
+ gSearchBundle.GetStringFromName("labelForSearchButton")
+ );
+ gSearchStopButton.setAttribute(
+ "accesskey",
+ gSearchBundle.GetStringFromName("labelForSearchButton.accesskey")
+ );
+
+ // update our toolbar equivalent
+ UpdateMailSearch("done-search");
+ // stop spining the meteors
+ gStatusFeedback._stopMeteors();
+ // set the result test
+ this.updateStatusResultText();
+ }
+ },
+
+ /**
+ * If messages were removed, we might have lost some search results and so
+ * should update our search result text. Also, defer to our super-class.
+ */
+ onMessagesRemoved() {
+ // result text is only for when we are not searching
+ if (!this.view.searching) {
+ this.updateStatusResultText();
+ }
+ this.__proto__.__proto__.onMessagesRemoved.call(this);
+ },
+
+ updateStatusResultText() {
+ let rowCount = this.view.dbView.rowCount;
+ let statusMsg;
+
+ if (rowCount == 0) {
+ statusMsg = gSearchBundle.GetStringFromName("noMatchesFound");
+ } else {
+ statusMsg = PluralForm.get(
+ rowCount,
+ gSearchBundle.GetStringFromName("matchesFound")
+ );
+ statusMsg = statusMsg.replace("#1", rowCount);
+ }
+
+ gStatusFeedback.showStatusString(statusMsg);
+ },
+};
+
+function searchOnLoad() {
+ TagUtils.loadTagsIntoCSS(document);
+ initializeSearchWidgets();
+ initializeSearchWindowWidgets();
+ // eslint-disable-next-line no-global-assign
+ messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+
+ gSearchBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/search.properties"
+ );
+ gSearchStopButton.setAttribute(
+ "label",
+ gSearchBundle.GetStringFromName("labelForSearchButton")
+ );
+ gSearchStopButton.setAttribute(
+ "accesskey",
+ gSearchBundle.GetStringFromName("labelForSearchButton.accesskey")
+ );
+
+ // eslint-disable-next-line no-global-assign
+ gFolderDisplay = new SearchFolderDisplayWidget();
+ gFolderDisplay.messenger = messenger;
+ gFolderDisplay.msgWindow = msgWindow;
+ gFolderDisplay.tree = document.getElementById("threadTree");
+
+ // The view is initially unsorted; get the persisted sortDirection column
+ // and set up the user's desired sort. This synthetic view is not backed by
+ // a db, so secondary sorts and custom columns are not supported here.
+ let sortCol = gFolderDisplay.tree.querySelector("[sortDirection]");
+ let sortType, sortOrder;
+ if (sortCol) {
+ sortType = Ci.nsMsgViewSortType[gFolderDisplay.COLUMNS_MAP.get(sortCol.id)];
+ sortOrder =
+ sortCol.getAttribute("sortDirection") == "descending"
+ ? Ci.nsMsgViewSortOrder.descending
+ : Ci.nsMsgViewSortOrder.ascending;
+ }
+
+ gFolderDisplay.view.openSearchView();
+ gFolderDisplay.makeActive();
+
+ if (sortType) {
+ gFolderDisplay.view.sort(sortType, sortOrder);
+ }
+
+ if (window.arguments && window.arguments[0]) {
+ updateSearchFolderPicker(window.arguments[0].folder);
+ }
+
+ // Trigger searchTerm.js to create the first criterion.
+ onMore(null);
+ // Make sure all the buttons are configured.
+ UpdateMailSearch("onload");
+}
+
+function searchOnUnload() {
+ gFolderDisplay.close();
+ top.controllers.removeController(nsSearchResultsController);
+
+ msgWindow.closeWindow();
+}
+
+function initializeSearchWindowWidgets() {
+ gFolderPicker = document.getElementById("searchableFolders");
+ gSearchStopButton = document.getElementById("search-button");
+ hideMatchAllItem();
+
+ // eslint-disable-next-line no-global-assign
+ msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+ );
+ msgWindow.domWindow = window;
+ msgWindow.rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_MAIL;
+
+ gStatusFeedback = new nsMsgStatusFeedback();
+ msgWindow.statusFeedback = gStatusFeedback;
+
+ // functionality to enable/disable buttons using nsSearchResultsController
+ // depending of whether items are selected in the search results thread pane.
+ top.controllers.insertControllerAt(0, nsSearchResultsController);
+}
+
+function onSearchStop() {
+ gFolderDisplay.view.search.session.interruptSearch();
+}
+
+function onResetSearch(event) {
+ onReset(event);
+ gFolderDisplay.view.search.clear();
+
+ gStatusFeedback.showStatusString("");
+}
+
+function updateSearchFolderPicker(folder) {
+ gCurrentFolder = folder;
+ gFolderPicker.menupopup.selectFolder(folder);
+
+ var searchOnline = document.getElementById("checkSearchOnline");
+ // We will hide and disable the search online checkbox if we are offline, or
+ // if the folder does not support online search.
+
+ // Any offlineSupportLevel > 0 is an online server like IMAP or news.
+ if (gCurrentFolder?.server.offlineSupportLevel && !Services.io.offline) {
+ searchOnline.hidden = false;
+ searchOnline.disabled = false;
+ } else {
+ searchOnline.hidden = true;
+ searchOnline.disabled = true;
+ }
+ if (gCurrentFolder) {
+ setSearchScope(GetScopeForFolder(gCurrentFolder));
+ }
+}
+
+function updateSearchLocalSystem() {
+ setSearchScope(GetScopeForFolder(gCurrentFolder));
+}
+
+function UpdateAfterCustomHeaderChange() {
+ updateSearchAttributes();
+}
+
+function onEnterInSearchTerm() {
+ // on enter
+ // if not searching, start the search
+ // if searching, stop and then start again
+ if (
+ gSearchStopButton.getAttribute("label") ==
+ gSearchBundle.GetStringFromName("labelForSearchButton")
+ ) {
+ onSearch();
+ } else {
+ onSearchStop();
+ onSearch();
+ }
+}
+
+function onSearch() {
+ let viewWrapper = gFolderDisplay.view;
+ let searchTerms = getSearchTerms();
+
+ viewWrapper.beginViewUpdate();
+ viewWrapper.search.userTerms = searchTerms.length ? searchTerms : null;
+ viewWrapper.search.onlineSearch = gSearchOnline;
+ viewWrapper.searchFolders = getSearchFolders();
+ viewWrapper.endViewUpdate();
+}
+
+/**
+ * Get the current set of search terms, returning them as a list. We filter out
+ * dangerous and insane predicates.
+ */
+function getSearchTerms() {
+ let termCreator = gFolderDisplay.view.search.session;
+
+ let searchTerms = [];
+ // searchTerm.js stores wrapper objects in its gSearchTerms array. Pluck
+ // them.
+ for (let iTerm = 0; iTerm < gSearchTerms.length; iTerm++) {
+ let termWrapper = gSearchTerms[iTerm].obj;
+ let realTerm = termCreator.createTerm();
+ termWrapper.saveTo(realTerm);
+ // A header search of "" is illegal for IMAP and will cause us to
+ // explode. You don't want that and I don't want that. So let's check
+ // if the bloody term is a subject search on a blank string, and if it
+ // is, let's secretly not add the term. Everyone wins!
+ if (
+ realTerm.attrib != Ci.nsMsgSearchAttrib.Subject ||
+ realTerm.value.str != ""
+ ) {
+ searchTerms.push(realTerm);
+ }
+ }
+
+ return searchTerms;
+}
+
+/**
+ * @returns the list of folders the search should cover.
+ */
+function getSearchFolders() {
+ let searchFolders = [];
+
+ if (!gCurrentFolder.isServer && !gCurrentFolder.noSelect) {
+ searchFolders.push(gCurrentFolder);
+ }
+
+ var searchSubfolders = document.getElementById(
+ "checkSearchSubFolders"
+ ).checked;
+ if (
+ gCurrentFolder &&
+ (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect)
+ ) {
+ AddSubFolders(gCurrentFolder, searchFolders);
+ }
+
+ return searchFolders;
+}
+
+function AddSubFolders(folder, outFolders) {
+ for (let nextFolder of folder.subFolders) {
+ if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual)) {
+ if (!nextFolder.noSelect) {
+ outFolders.push(nextFolder);
+ }
+
+ AddSubFolders(nextFolder, outFolders);
+ }
+ }
+}
+
+function AddSubFoldersToURI(folder) {
+ var returnString = "";
+
+ for (let nextFolder of folder.subFolders) {
+ if (!(nextFolder.flags & Ci.nsMsgFolderFlags.Virtual)) {
+ if (!nextFolder.noSelect && !nextFolder.isServer) {
+ if (returnString.length > 0) {
+ returnString += "|";
+ }
+ returnString += nextFolder.URI;
+ }
+ var subFoldersString = AddSubFoldersToURI(nextFolder);
+ if (subFoldersString.length > 0) {
+ if (returnString.length > 0) {
+ returnString += "|";
+ }
+ returnString += subFoldersString;
+ }
+ }
+ }
+ return returnString;
+}
+
+/**
+ * Determine the proper search scope to use for a folder, so that the user is
+ * presented with a correct list of search capabilities. The user may manually
+ * request on online search for certain server types. To determine if the
+ * folder body may be searched, we ignore whether autosync is enabled,
+ * figuring that after the user manually syncs, they would still expect that
+ * body searches would work.
+ *
+ * The available search capabilities also depend on whether the user is
+ * currently online or offline. Although that is also checked by the server,
+ * we do it ourselves because we have a more complex response to offline
+ * than the server's searchScope attribute provides.
+ *
+ * This method only works for real folders.
+ */
+function GetScopeForFolder(folder) {
+ let searchOnline = document.getElementById("checkSearchOnline");
+ if (!searchOnline.disabled && searchOnline.checked) {
+ gSearchOnline = true;
+ return folder.server.searchScope;
+ }
+ gSearchOnline = false;
+
+ // We are going to search offline. The proper search scope may depend on
+ // whether we have the body and/or junk available or not.
+ let localType;
+ try {
+ localType = folder.server.localStoreType;
+ } catch (e) {} // On error, we'll just assume the default mailbox type
+
+ let hasBody = folder.getFlag(Ci.nsMsgFolderFlags.Offline);
+ let nsMsgSearchScope = Ci.nsMsgSearchScope;
+ switch (localType) {
+ case "news":
+ // News has four offline scopes, depending on whether junk and body
+ // are available.
+ let hasJunk =
+ folder.getInheritedStringProperty(
+ "dobayes.mailnews@mozilla.org#junk"
+ ) == "true";
+ if (hasJunk && hasBody) {
+ return nsMsgSearchScope.localNewsJunkBody;
+ }
+ if (hasJunk) {
+ // and no body
+ return nsMsgSearchScope.localNewsJunk;
+ }
+ if (hasBody) {
+ // and no junk
+ return nsMsgSearchScope.localNewsBody;
+ }
+ // We don't have offline message bodies or junk processing.
+ return nsMsgSearchScope.localNews;
+
+ case "imap":
+ // Junk is always enabled for imap, so the offline scope only depends on
+ // whether the body is available.
+
+ // If we are the root folder, use the server property for body rather
+ // than the folder property.
+ if (folder.isServer) {
+ let imapServer = folder.server.QueryInterface(Ci.nsIImapIncomingServer);
+ if (imapServer && imapServer.offlineDownload) {
+ hasBody = true;
+ }
+ }
+
+ if (!hasBody) {
+ return nsMsgSearchScope.onlineManual;
+ }
+ // fall through to default
+ default:
+ return nsMsgSearchScope.offlineMail;
+ }
+}
+
+function goUpdateSearchItems(commandset) {
+ for (var i = 0; i < commandset.children.length; i++) {
+ var commandID = commandset.children[i].getAttribute("id");
+ if (commandID) {
+ goUpdateCommand(commandID);
+ }
+ }
+}
+
+// used to toggle functionality for Search/Stop button.
+function onSearchButton(event) {
+ if (
+ event.target.label ==
+ gSearchBundle.GetStringFromName("labelForSearchButton")
+ ) {
+ onSearch();
+ } else {
+ onSearchStop();
+ }
+}
+
+function MsgDeleteSelectedMessages(aCommandType) {
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommand(aCommandType);
+}
+
+/**
+ * Move selected messages to the destination folder
+ *
+ * @param destFolder {nsIMsgFolder} - destination folder
+ */
+function MoveMessageInSearch(destFolder) {
+ gFolderDisplay.hintAboutToDeleteMessages();
+ gFolderDisplay.doCommandWithFolder(
+ Ci.nsMsgViewCommandType.moveMessages,
+ destFolder
+ );
+}
+
+function OpenInFolder() {
+ MailUtils.displayMessageInFolderTab(gFolderDisplay.selectedMessage);
+}
+
+function saveAsVirtualFolder() {
+ var searchFolderURIs = gCurrentFolder.URI;
+
+ var searchSubfolders = document.getElementById(
+ "checkSearchSubFolders"
+ ).checked;
+ if (
+ gCurrentFolder &&
+ (searchSubfolders || gCurrentFolder.isServer || gCurrentFolder.noSelect)
+ ) {
+ var subFolderURIs = AddSubFoldersToURI(gCurrentFolder);
+ if (subFolderURIs.length > 0) {
+ searchFolderURIs += "|" + subFolderURIs;
+ }
+ }
+
+ var searchOnline = document.getElementById("checkSearchOnline");
+ var doOnlineSearch = searchOnline.checked && !searchOnline.disabled;
+
+ window.openDialog(
+ "chrome://messenger/content/virtualFolderProperties.xhtml",
+ "",
+ "chrome,titlebar,modal,centerscreen,resizable=yes",
+ {
+ folder: window.arguments[0].folder,
+ searchTerms: getSearchTerms(),
+ searchFolderURIs,
+ searchOnline: doOnlineSearch,
+ }
+ );
+}
+
+function MsgOpenSelectedMessages() {
+ // Toggle message body (feed summary) and content-base url in message pane or
+ // load in browser, per pref, otherwise open summary or web page in new window
+ // or tab, per that pref.
+ if (
+ gFolderDisplay.treeSelection &&
+ gFolderDisplay.treeSelection.count == 1 &&
+ gFolderDisplay.selectedMessageIsFeed
+ ) {
+ let msgHdr = gFolderDisplay.selectedMessage;
+ if (
+ document.documentElement.getAttribute("windowtype") == "mail:3pane" &&
+ FeedMessageHandler.onOpenPref ==
+ FeedMessageHandler.kOpenToggleInMessagePane
+ ) {
+ let showSummary = FeedMessageHandler.shouldShowSummary(msgHdr, true);
+ FeedMessageHandler.setContent(msgHdr, showSummary);
+ return;
+ }
+ if (
+ FeedMessageHandler.onOpenPref == FeedMessageHandler.kOpenLoadInBrowser
+ ) {
+ setTimeout(FeedMessageHandler.loadWebPage, 20, msgHdr, { browser: true });
+ return;
+ }
+ }
+
+ // This is somewhat evil. If we're in a 3pane window, we'd have a tabmail
+ // element and would pass it in here, ensuring that if we open tabs, we use
+ // this tabmail to open them. If we aren't, then we wouldn't, so
+ // displayMessages would look for a 3pane window and open tabs there.
+ MailUtils.displayMessages(
+ gFolderDisplay.selectedMessages,
+ gFolderDisplay.view,
+ document.getElementById("tabmail")
+ );
+}