476 строки
15 KiB
JavaScript
476 строки
15 KiB
JavaScript
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Mozilla Communicator client code, released
|
|
* March 31, 1998.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998-1999
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Seth Spitzer <sspitzer@netscape.com>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
var gSearchSession = null;
|
|
var gPreQuickSearchView = null;
|
|
var gSearchTimer = null;
|
|
var gViewSearchListener;
|
|
var gSearchBundle;
|
|
var gProgressMeter = null;
|
|
var gSearchInProgress = false;
|
|
var gClearButton = null;
|
|
var gDefaultSearchViewTerms = null;
|
|
var gQSViewIsDirty = false;
|
|
var gNumTotalMessages;
|
|
var gNumUnreadMessages;
|
|
|
|
function SetQSStatusText(aNumHits)
|
|
{
|
|
var statusMsg;
|
|
// if there are no hits, it means no matches were found in the search.
|
|
if (aNumHits == 0)
|
|
statusMsg = gSearchBundle.getString("searchFailureMessage");
|
|
else
|
|
{
|
|
if (aNumHits == 1)
|
|
statusMsg = gSearchBundle.getString("searchSuccessMessage");
|
|
else
|
|
statusMsg = gSearchBundle.getFormattedString("searchSuccessMessages", [aNumHits]);
|
|
}
|
|
|
|
statusFeedback.showStatusString(statusMsg);
|
|
}
|
|
|
|
// nsIMsgSearchNotify object
|
|
var gSearchNotificationListener =
|
|
{
|
|
onSearchHit: function(header, folder)
|
|
{
|
|
gNumTotalMessages++;
|
|
if (!header.isRead)
|
|
gNumUnreadMessages++;
|
|
// XXX todo
|
|
// update status text?
|
|
},
|
|
|
|
onSearchDone: function(status)
|
|
{
|
|
SetQSStatusText(gDBView.QueryInterface(Components.interfaces.nsITreeView).rowCount)
|
|
statusFeedback.showProgress(0);
|
|
gProgressMeter.setAttribute("mode", "normal");
|
|
gSearchInProgress = false;
|
|
|
|
// ### TODO need to find out if there's quick search within a virtual folder.
|
|
if (gCurrentVirtualFolderUri &&
|
|
(!gSearchInput || gSearchInput.value == ""))
|
|
{
|
|
var vFolder = GetMsgFolderFromUri(gCurrentVirtualFolderUri, false);
|
|
var dbFolderInfo = vFolder.msgDatabase.dBFolderInfo;
|
|
dbFolderInfo.numUnreadMessages = gNumUnreadMessages;
|
|
dbFolderInfo.numMessages = gNumTotalMessages;
|
|
vFolder.updateSummaryTotals(true); // force update from db.
|
|
var msgdb = vFolder.msgDatabase;
|
|
msgdb.Commit(Components.interfaces.nsMsgDBCommitType.kLargeCommit);
|
|
// now that we have finished loading a virtual folder,
|
|
// scroll to the correct message if there is at least one.
|
|
if (vFolder.getTotalMessages(false) > 0)
|
|
ScrollToMessageAfterFolderLoad(vFolder);
|
|
}
|
|
},
|
|
|
|
onNewSearch: function()
|
|
{
|
|
statusFeedback.showProgress(0);
|
|
statusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
|
|
gProgressMeter.setAttribute("mode", "undetermined");
|
|
gSearchInProgress = true;
|
|
gNumTotalMessages = 0;
|
|
gNumUnreadMessages = 0;
|
|
}
|
|
}
|
|
|
|
function getDocumentElements()
|
|
{
|
|
gSearchBundle = document.getElementById("bundle_search");
|
|
gProgressMeter = document.getElementById('statusbar-icon');
|
|
gClearButton = document.getElementById('clearButton');
|
|
GetSearchInput();
|
|
}
|
|
|
|
function addListeners()
|
|
{
|
|
gViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
|
|
gSearchSession.registerListener(gViewSearchListener);
|
|
}
|
|
|
|
function removeListeners()
|
|
{
|
|
gSearchSession.unregisterListener(gViewSearchListener);
|
|
}
|
|
|
|
function removeGlobalListeners()
|
|
{
|
|
removeListeners();
|
|
gSearchSession.unregisterListener(gSearchNotificationListener);
|
|
}
|
|
|
|
function initializeGlobalListeners()
|
|
{
|
|
// Setup the javascript object as a listener on the search results
|
|
gSearchSession.registerListener(gSearchNotificationListener);
|
|
}
|
|
|
|
function createQuickSearchView()
|
|
{
|
|
//if not already in quick search view
|
|
if (gDBView.viewType != nsMsgViewType.eShowQuickSearchResults)
|
|
{
|
|
var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView); //clear selection
|
|
if (treeView && treeView.selection)
|
|
treeView.selection.clearSelection();
|
|
gPreQuickSearchView = gDBView;
|
|
if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
|
|
{
|
|
// remove the view as a listener on the search results
|
|
var saveViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
|
|
gSearchSession.unregisterListener(saveViewSearchListener);
|
|
}
|
|
CreateDBView(gDBView.msgFolder, (gXFVirtualFolderTerms) ? nsMsgViewType.eShowVirtualFolderResults : nsMsgViewType.eShowQuickSearchResults, gDBView.viewFlags, gDBView.sortType, gDBView.sortOrder);
|
|
}
|
|
}
|
|
|
|
function initializeSearchBar()
|
|
{
|
|
createQuickSearchView();
|
|
if (!gSearchSession)
|
|
{
|
|
var searchSessionContractID = "@mozilla.org/messenger/searchSession;1";
|
|
gSearchSession = Components.classes[searchSessionContractID].createInstance(Components.interfaces.nsIMsgSearchSession);
|
|
initializeGlobalListeners();
|
|
}
|
|
else
|
|
{
|
|
if (gSearchInProgress)
|
|
{
|
|
onSearchStop();
|
|
gSearchInProgress = false;
|
|
}
|
|
removeListeners();
|
|
}
|
|
addListeners();
|
|
}
|
|
|
|
function onEnterInSearchBar()
|
|
{
|
|
if (!gSearchBundle)
|
|
getDocumentElements();
|
|
if (gSearchInput.value == "")
|
|
{
|
|
let viewType = gDBView && gDBView.viewType;
|
|
if (viewType == nsMsgViewType.eShowQuickSearchResults ||
|
|
viewType == nsMsgViewType.eShowVirtualFolderResults)
|
|
{
|
|
statusFeedback.showStatusString("");
|
|
disableQuickSearchClearButton();
|
|
|
|
viewDebug ("onEnterInSearchBar gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = "
|
|
+ gVirtualFolderTerms + "gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
|
|
var addTerms = gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms;
|
|
if (addTerms)
|
|
{
|
|
viewDebug ("addTerms = " + addTerms + " count = " + addTerms.Count() + "\n");
|
|
initializeSearchBar();
|
|
onSearch(addTerms);
|
|
}
|
|
else
|
|
restorePreSearchView();
|
|
}
|
|
else if (gPreQuickSearchView && !gDefaultSearchViewTerms)// may be a quick search from a cross-folder virtual folder
|
|
restorePreSearchView();
|
|
|
|
gQSViewIsDirty = false;
|
|
return;
|
|
}
|
|
|
|
initializeSearchBar();
|
|
|
|
if (gClearButton)
|
|
gClearButton.setAttribute("disabled", false); //coming into search enable clear button
|
|
|
|
ClearThreadPaneSelection();
|
|
ClearMessagePane();
|
|
|
|
onSearch(null);
|
|
gQSViewIsDirty = false;
|
|
}
|
|
|
|
function restorePreSearchView()
|
|
{
|
|
var selectedHdr = null;
|
|
//save selection
|
|
try
|
|
{
|
|
selectedHdr = gDBView.hdrForFirstSelectedMessage;
|
|
}
|
|
catch (ex)
|
|
{}
|
|
|
|
//we might have to sort the view coming out of quick search
|
|
var sortType = gDBView.sortType;
|
|
var sortOrder = gDBView.sortOrder;
|
|
var viewFlags = gDBView.viewFlags;
|
|
var folder = gDBView.msgFolder;
|
|
|
|
gDBView.close();
|
|
gDBView = null;
|
|
|
|
if (gPreQuickSearchView)
|
|
{
|
|
gDBView = gPreQuickSearchView;
|
|
if (gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
|
|
{
|
|
// readd the view as a listener on the search results
|
|
var saveViewSearchListener = gDBView.QueryInterface(Components.interfaces.nsIMsgSearchNotify);
|
|
if (gSearchSession)
|
|
gSearchSession.registerListener(saveViewSearchListener);
|
|
}
|
|
// dump ("view type = " + gDBView.viewType + "\n");
|
|
|
|
if (sortType != gDBView.sortType || sortOrder != gDBView.sortOrder)
|
|
{
|
|
gDBView.sort(sortType, sortOrder);
|
|
}
|
|
UpdateSortIndicators(sortType, sortOrder);
|
|
|
|
gPreQuickSearchView = null;
|
|
}
|
|
else //create default view type
|
|
CreateDBView(folder, nsMsgViewType.eShowAllThreads, viewFlags, sortType, sortOrder);
|
|
|
|
RerootThreadPane();
|
|
|
|
var scrolled = false;
|
|
|
|
// now restore selection
|
|
if (selectedHdr)
|
|
{
|
|
gDBView.selectMsgByKey(selectedHdr.messageKey);
|
|
var treeView = gDBView.QueryInterface(Components.interfaces.nsITreeView);
|
|
var selectedIndex = treeView.selection.currentIndex;
|
|
if (selectedIndex >= 0)
|
|
{
|
|
// scroll
|
|
EnsureRowInThreadTreeIsVisible(selectedIndex);
|
|
scrolled = true;
|
|
}
|
|
else
|
|
ClearMessagePane();
|
|
}
|
|
if (!scrolled)
|
|
ScrollToMessageAfterFolderLoad(null);
|
|
}
|
|
|
|
function onSearch(aSearchTerms)
|
|
{
|
|
viewDebug("in OnSearch, searchTerms = " + aSearchTerms + "\n");
|
|
RerootThreadPane();
|
|
|
|
if (aSearchTerms)
|
|
createSearchTermsWithList(aSearchTerms);
|
|
else
|
|
createSearchTerms();
|
|
|
|
gDBView.searchSession = gSearchSession;
|
|
try
|
|
{
|
|
gSearchSession.search(msgWindow);
|
|
}
|
|
catch(ex)
|
|
{
|
|
dump("Search Exception\n");
|
|
}
|
|
}
|
|
|
|
function createSearchTermsWithList(aTermsArray)
|
|
{
|
|
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
|
|
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
|
|
var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
|
|
|
|
gSearchSession.clearScopes();
|
|
var searchTerms = gSearchSession.searchTerms;
|
|
var searchTermsArray = searchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
|
|
searchTermsArray.Clear();
|
|
|
|
var i;
|
|
var selectedFolder = GetThreadPaneFolder();
|
|
if (gXFVirtualFolderTerms)
|
|
{
|
|
var msgDatabase = selectedFolder.msgDatabase;
|
|
if (msgDatabase)
|
|
{
|
|
var dbFolderInfo = msgDatabase.dBFolderInfo;
|
|
var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
|
|
viewDebug("createSearchTermsWithList xf vf scope = " + srchFolderUri + "\n");
|
|
var srchFolderUriArray = srchFolderUri.split('|');
|
|
for (i in srchFolderUriArray)
|
|
{
|
|
let realFolder = GetMsgFolderFromUri(srchFolderUriArray[i]);
|
|
if (!realFolder.isServer)
|
|
gSearchSession.addScopeTerm(nsMsgSearchScope.offlineMail, realFolder);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
viewDebug ("in createSearchTermsWithList, adding scope term for selected folder\n");
|
|
gSearchSession.addScopeTerm(nsMsgSearchScope.offlineMail, selectedFolder);
|
|
}
|
|
// add each item in termsArray to the search session
|
|
|
|
var termsArray = aTermsArray.QueryInterface(Components.interfaces.nsISupportsArray);
|
|
for (i = 0; i < termsArray.Count(); ++i)
|
|
gSearchSession.appendTerm(termsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm));
|
|
}
|
|
|
|
function createSearchTerms()
|
|
{
|
|
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
|
|
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
|
|
var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
|
|
|
|
// create an nsISupportsArray to store our search terms
|
|
var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
|
|
var selectedFolder = GetThreadPaneFolder();
|
|
|
|
// implement | for QS
|
|
// does this break if the user types "foo|bar" expecting to see subjects with that string?
|
|
// I claim no, since "foo|bar" will be a hit for "foo" || "bar"
|
|
// they just might get more false positives
|
|
var termList = gSearchInput.value.split("|");
|
|
for (var i = 0; i < termList.length; i ++)
|
|
{
|
|
// if the term is empty, skip it
|
|
if (termList[i] == "")
|
|
continue;
|
|
|
|
// create, fill, and append the subject term
|
|
var term = gSearchSession.createTerm();
|
|
var value = term.value;
|
|
value.str = termList[i];
|
|
term.value = value;
|
|
term.attrib = nsMsgSearchAttrib.Subject;
|
|
term.op = nsMsgSearchOp.Contains;
|
|
term.booleanAnd = false;
|
|
searchTermsArray.AppendElement(term);
|
|
|
|
// create, fill, and append the AllAddresses term
|
|
term = gSearchSession.createTerm();
|
|
value = term.value;
|
|
value.str = termList[i];
|
|
term.value = value;
|
|
term.attrib = nsMsgSearchAttrib.AllAddresses;
|
|
term.op = nsMsgSearchOp.Contains;
|
|
term.booleanAnd = false;
|
|
searchTermsArray.AppendElement(term);
|
|
}
|
|
|
|
// now append the default view or virtual folder criteria to the quick search
|
|
// so we don't lose any default view information
|
|
viewDebug("gDefaultSearchViewTerms = " + gDefaultSearchViewTerms + "gVirtualFolderTerms = " + gVirtualFolderTerms +
|
|
"gXFVirtualFolderTerms = " + gXFVirtualFolderTerms + "\n");
|
|
var defaultSearchTerms = (gDefaultSearchViewTerms || gVirtualFolderTerms || gXFVirtualFolderTerms);
|
|
if (defaultSearchTerms)
|
|
{
|
|
var isupports = null;
|
|
var searchTerm;
|
|
var termsArray = defaultSearchTerms.QueryInterface(Components.interfaces.nsISupportsArray);
|
|
for (i = 0; i < termsArray.Count(); i++)
|
|
{
|
|
isupports = termsArray.GetElementAt(i);
|
|
searchTerm = isupports.QueryInterface(Components.interfaces.nsIMsgSearchTerm);
|
|
searchTermsArray.AppendElement(searchTerm);
|
|
}
|
|
}
|
|
|
|
createSearchTermsWithList(searchTermsArray);
|
|
|
|
// now that we've added the terms, clear out our input array
|
|
searchTermsArray.Clear();
|
|
}
|
|
|
|
function onSearchStop()
|
|
{
|
|
gSearchSession.interruptSearch();
|
|
}
|
|
|
|
function onClearSearch()
|
|
{
|
|
// Use the last focused element so that focus can be restored
|
|
// if it does not exist, try and get the thread tree instead
|
|
var focusedElement = gLastFocusedElement || GetThreadTree();
|
|
Search("");
|
|
focusedElement.focus();
|
|
}
|
|
|
|
function disableQuickSearchClearButton()
|
|
{
|
|
if (gClearButton)
|
|
gClearButton.setAttribute("disabled", true); //going out of search disable clear button
|
|
}
|
|
|
|
function ClearQSIfNecessary()
|
|
{
|
|
GetSearchInput();
|
|
|
|
if (gSearchInput.value == "")
|
|
return;
|
|
|
|
Search("");
|
|
}
|
|
|
|
function Search(str)
|
|
{
|
|
GetSearchInput();
|
|
|
|
if (str != gSearchInput.value)
|
|
{
|
|
gQSViewIsDirty = true;
|
|
viewDebug("in Search(), setting gQSViewIsDirty true\n");
|
|
}
|
|
|
|
gSearchInput.value = str; //on input does not get fired for some reason
|
|
onEnterInSearchBar();
|
|
}
|
|
|
|
function saveViewAsVirtualFolder()
|
|
{
|
|
openNewVirtualFolderDialogWithArgs(gSearchInput.value, gSearchSession.searchTerms);
|
|
}
|