diff options
Diffstat (limited to '')
-rw-r--r-- | comm/mail/base/content/SearchDialog.js | 650 |
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") + ); +} |