gecko-dev/mail/base/content/searchBar.js

849 lines
27 KiB
JavaScript

# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
# ***** 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>
# Scott MacGregor <mscott@mozilla.org>
# David Bienvenu <bienvenu@nventure.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 gStatusBar = null;
var gSearchInProgress = false;
var gSearchInput = null;
var gDefaultSearchViewTerms = null;
var gQSViewIsDirty = false;
var gHighlightedMessageText = false;
var gIgnoreFocus = false;
var gIgnoreClick = false;
var gNumTotalMessages;
var gNumUnreadMessages;
// search criteria mode values
// Note: If you change these constants, please update the menuitem values in
// quick-search-menupopup. Note: These values are stored in localstore.rdf so we
// can remember the users last quick search state. If you add values here, you must add
// them to the end of the list!
const kQuickSearchSubject = 0;
const kQuickSearchSender = 1;
const kQuickSearchSenderOrSubject = 2;
const kQuickSearchBody = 3;
const kQuickSearchHighlight = 4;
const kQuickSearchRecipient = 5;
var gFinder = Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance()
.QueryInterface(Components.interfaces.nsIFind);
// Colors for highlighting
var gHighlightColors = new Array("yellow", "lightpink", "aquamarine",
"darkgoldenrod", "darkseagreen", "lightgreen",
"rosybrown", "seagreen", "chocolate", "violet");
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);
gStatusBar.setAttribute("mode","normal");
gSearchInProgress = false;
viewDebug("gSearchInput = " + gSearchInput.value + "\n");
// ### TODO need to find out if there's quick search within a virtual folder.
if (gCurrentVirtualFolderUri &&
(gSearchInput.value == "" || gSearchInput.showingSearchCriteria))
{
var vFolder = GetMsgFolderFromUri(gCurrentVirtualFolderUri, false);
var dbFolderInfo = vFolder.getMsgDatabase(msgWindow).dBFolderInfo;
dbFolderInfo.NumUnreadMessages = gNumUnreadMessages;
dbFolderInfo.NumMessages = gNumTotalMessages;
vFolder.updateSummaryTotals(true); // force update from db.
var msgdb = vFolder.getMsgDatabase(msgWindow);
msgdb.Commit(MSG_DB_LARGE_COMMIT);
// load the last used mail view for the folder...
var result = dbFolderInfo.getUint32Property("current-view", 0);
ViewChangeByValue(result);
// now that we have finished loading a virtual folder, scroll to the correct message
ScrollToMessageAfterFolderLoad(vFolder);
}
},
onNewSearch: function()
{
statusFeedback.showProgress(0);
statusFeedback.showStatusString(gSearchBundle.getString("searchingMessage"));
gStatusBar.setAttribute("mode","undetermined");
gSearchInProgress = true;
gNumTotalMessages = 0;
gNumUnreadMessages = 0;
}
}
function getDocumentElements()
{
gSearchBundle = document.getElementById("bundle_search");
gStatusBar = document.getElementById('statusbar-icon');
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)
{
getDocumentElements();
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()
{
viewDebug ("onEnterInSearchBar gSearchInput.value = " + gSearchInput.value + " showing criteria = " + gSearchInput.showingSearchCriteria +"\n");
if (gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
{
if (gSearchInput.searchMode == kQuickSearchHighlight)
removeHighlighting();
if (gDBView.viewType == nsMsgViewType.eShowQuickSearchResults
|| gDBView.viewType == nsMsgViewType.eShowVirtualFolderResults)
{
statusFeedback.showStatusString("");
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();
gSearchInput.showingSearchCriteria = true;
gQSViewIsDirty = false;
return;
}
if (gSearchInput.searchMode == kQuickSearchHighlight)
highlightMessage(true);
else
{
initializeSearchBar();
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 selectedFolder = GetThreadPaneFolder();
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
var termsArray = aTermsArray.QueryInterface(Components.interfaces.nsISupportsArray);
if (gXFVirtualFolderTerms)
{
var msgDatabase = selectedFolder.getMsgDatabase(msgWindow);
if (msgDatabase)
{
var dbFolderInfo = msgDatabase.dBFolderInfo;
var srchFolderUri = dbFolderInfo.getCharPtrProperty("searchFolderUri");
viewDebug("createSearchTermsWithList xf vf scope = " + srchFolderUri + "\n");
var srchFolderUriArray = srchFolderUri.split('|');
for (var i in srchFolderUriArray)
{
var realFolderRes = GetResourceFromUri(srchFolderUriArray[i]);
var realFolder = realFolderRes.QueryInterface(Components.interfaces.nsIMsgFolder);
if (!realFolder.isServer)
gSearchSession.addScopeTerm(getScopeToUse(termsArray, realFolder, ioService.offline), realFolder);
}
}
}
else
{
viewDebug ("in createSearchTermsWithList, adding scope term for selected folder\n");
gSearchSession.addScopeTerm(getScopeToUse(termsArray, selectedFolder, ioService.offline), selectedFolder);
}
// add each item in termsArray to the search session
for (var i = 0; i < termsArray.Count(); i++)
gSearchSession.appendTerm(termsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm));
}
function getScopeToUse(aTermsArray, aFolderToSearch, aIsOffline)
{
if (aIsOffline || aFolderToSearch.server.type != 'imap')
return nsMsgSearchScope.offlineMail;
var scopeToUse = gSearchInput.searchMode == kQuickSearchBody && !gSearchInput.showingSearchCriteria
? nsMsgSearchScope.onlineMail : nsMsgSearchScope.offlineMail;
// it's possible one of our search terms may require us to use an online mail scope (such as imap body searches)
for (var i = 0; scopeToUse != nsMsgSearchScope.onlineMail && i < aTermsArray.Count(); i++)
if (aTermsArray.GetElementAt(i).QueryInterface(Components.interfaces.nsIMsgSearchTerm).attrib == nsMsgSearchAttrib.Body)
scopeToUse = nsMsgSearchScope.onlineMail;
return scopeToUse;
}
function createSearchTerms()
{
var nsMsgSearchScope = Components.interfaces.nsMsgSearchScope;
var nsMsgSearchAttrib = Components.interfaces.nsMsgSearchAttrib;
var nsMsgSearchOp = Components.interfaces.nsMsgSearchOp;
// create an i supports array to store our search terms
var searchTermsArray = Components.classes["@mozilla.org/supports-array;1"].createInstance(Components.interfaces.nsISupportsArray);
var selectedFolder = GetThreadPaneFolder();
var searchAttrib = (IsSpecialFolder(selectedFolder, MSG_FOLDER_FLAG_SENTMAIL | MSG_FOLDER_FLAG_DRAFTS | MSG_FOLDER_FLAG_QUEUE, true)) ? nsMsgSearchAttrib.ToOrCC : nsMsgSearchAttrib.Sender;
// 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
if (!gSearchInput.showingSearchCriteria) // ignore the text box value if it's just showing the search criteria string
{
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;
var value;
// if our search criteria is subject or subject|sender then add a term for the subject
if (gSearchInput.searchMode == kQuickSearchSubject || gSearchInput.searchMode == kQuickSearchSenderOrSubject)
{
term = gSearchSession.createTerm();
value = term.value;
value.str = termList[i];
term.value = value;
term.attrib = nsMsgSearchAttrib.Subject;
term.op = nsMsgSearchOp.Contains;
term.booleanAnd = false;
searchTermsArray.AppendElement(term);
}
if (gSearchInput.searchMode == kQuickSearchBody)
{
// what do we do for news and imap users that aren't configured for offline use?
// in these cases the body search will never return any matches. Should we try to
// see if body is a valid search scope in this particular case before doing the search?
// should we switch back to a subject/sender search behind the scenes?
term = gSearchSession.createTerm();
value = term.value;
value.str = termList[i];
term.value = value;
term.attrib = nsMsgSearchAttrib.Body;
term.op = nsMsgSearchOp.Contains;
term.booleanAnd = false;
searchTermsArray.AppendElement(term);
}
// create, fill, and append the sender (or recipient) term
if (gSearchInput.searchMode == kQuickSearchSender || gSearchInput.searchMode == kQuickSearchSenderOrSubject)
{
term = gSearchSession.createTerm();
value = term.value;
value.str = termList[i];
term.value = value;
term.attrib = searchAttrib;
term.op = nsMsgSearchOp.Contains;
term.booleanAnd = false;
searchTermsArray.AppendElement(term);
}
// create, fill, and append the recipient
if (gSearchInput.searchMode == kQuickSearchRecipient)
{
term = gSearchSession.createTerm();
value = term.value;
value.str = termList[i];
term.value = value;
term.attrib = nsMsgSearchAttrib.ToOrCC;
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 onAdvancedSearch()
{
MsgSearchMessages();
}
function onSearchStop()
{
gSearchSession.interruptSearch();
}
function onSearchKeyPress(event)
{
if (gSearchInput.showingSearchCriteria)
gSearchInput.showingSearchCriteria = false;
// 13 == return
if (event && event.keyCode == 13)
onSearchInput(true);
}
function onSearchInputFocus(event)
{
// search bar has focus, ...clear the showing search criteria flag
if (gSearchInput.showingSearchCriteria)
{
gSearchInput.value = "";
gSearchInput.showingSearchCriteria = false;
}
if (gIgnoreFocus)
gIgnoreFocus = false;
else
gSearchInput.select();
}
function onSearchInputMousedown(event)
{
if (gSearchInput.hasAttribute("focused"))
gIgnoreClick = true;
else
{
gIgnoreFocus = true;
gIgnoreClick = false;
gSearchInput.setSelectionRange(0, 0);
}
}
function onSearchInputClick(event)
{
if (!gIgnoreClick && gSearchInput.selectionStart == gSearchInput.selectionEnd)
gSearchInput.select();
}
function onSearchInputBlur(event)
{
if (gQuickSearchFocusEl && gQuickSearchFocusEl.id == 'searchInput') // ignore the blur if we are in the middle of processing the clear button
return;
if (!gSearchInput.value)
gSearchInput.showingSearchCriteria = true;
if (gSearchInput.showingSearchCriteria)
gSearchInput.setSearchCriteriaText();
}
function onSearchInput(returnKeyHit)
{
if (gSearchInput.showingSearchCriteria && !(returnKeyHit && gSearchInput.value == ""))
return;
if (gSearchTimer) {
clearTimeout(gSearchTimer);
gSearchTimer = null;
}
// only select the text when the return key was hit
if (returnKeyHit) {
GetSearchInput();
gSearchInput.select();
onEnterInSearchBar();
}
else {
gSearchTimer = setTimeout("onEnterInSearchBar();", 800);
}
}
// temporary global used to make sure we restore focus to the correct element after clearing the quick search box
// because clearing quick search means stealing focus.
var gQuickSearchFocusEl = null;
function onClearSearch()
{
if (!gSearchInput.showingSearchCriteria) // ignore the text box value if it's just showing the search criteria string
{
gQuickSearchFocusEl = gLastFocusedElement; //save of the last focused element so that focus can be restored
Search("");
// this needs to be on a timer otherwise we end up messing up the focus while the Search("") is still happening
setTimeout("restoreSearchFocusAfterClear();", 0);
}
}
function restoreSearchFocusAfterClear()
{
gQuickSearchFocusEl.focus();
gSearchInput.clearButtonHidden = 'true';
gQuickSearchFocusEl = null;
}
function ClearQSIfNecessary()
{
GetSearchInput();
if (gSearchInput.value == "")
return;
Search("");
}
// called after the user switches folders while inside
// of a quick search view...
function clearQuickSearchAfterFolderChange()
{
gSearchInput.setSearchCriteriaText();
}
function Search(str)
{
viewDebug("in Search str = " + str + "gSearchInput.showingSearchCriteria = " + gSearchInput.showingSearchCriteria + "\n");
if (gSearchInput.showingSearchCriteria && str != "")
return;
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
onSearchInput(true);
}
// When the front end has finished loading a virtual folder, it calls openVirtualFolder
// to actually perform the folder search. We use this method instead of calling Search("") directly
// from FolderLoaded in order to avoid moving focus into the search bar.
function loadVirtualFolder()
{
// bit of a hack...if the underlying real folder is loaded with the same view value
// as the value for the virtual folder being searched, then ViewChangeByValue
// fails to change the view because it thinks the view is already correctly loaded.
// so set gCurrentViewValue back to All.
gCurrentViewValue = 0;
onEnterInSearchBar();
}
// this notification gets generated from layout when it finishes laying out a message
// in the message pane.
function onQuickSearchNewMsgLoaded()
{
// if we are in highlighting mode and we have highlight text in the search box then
// re-highlight this new message.
// Optimization: We'll special case Message Body quick searches and highlight those as well as find in message
// searches.
if ( (gSearchInput.searchMode == kQuickSearchHighlight || gSearchInput.searchMode == kQuickSearchBody)
&& gSearchInput.value && !gSearchInput.showingSearchCriteria)
{
highlightMessage(false);
}
}
// helper methods for the quick search drop down menu
function changeQuickSearchMode(aMenuItem)
{
viewDebug("changing quick search mode\n");
// extract the label and set the search input to match it
var oldSearchMode = gSearchInput.searchMode;
gSearchInput.searchMode = aMenuItem.value;
if (gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
{
gSearchInput.showingSearchCriteria = true;
if (gSearchInput.value) //
gSearchInput.setSearchCriteriaText();
}
// if the search box is empty, set showing search criteria to true so it shows up when focus moves out of the box
if (!gSearchInput.value)
gSearchInput.showingSearchCriteria = true;
else if (gSearchInput.showingSearchCriteria) // if we are showing criteria text and the box isn't empty, change the criteria text
gSearchInput.setSearchCriteriaText();
else if (oldSearchMode != gSearchInput.searchMode) // the search mode just changed so we need to redo the quick search
{
if (gHighlightedMessageText)
removeHighlighting(); // remove any existing highlighting in the message before switching gears
onEnterInSearchBar();
}
}
function saveViewAsVirtualFolder()
{
openNewVirtualFolderDialogWithArgs(gSearchInput.value, gSearchSession.searchTerms);
}
function InitQuickSearchPopup()
{
// disable the create virtual folder menu item if the current radio
// value is set to Find in message since you can't really create a VF from find
// in message
if (gSearchInput.searchMode == 4 /* find in page */ || gSearchInput.value == "" || gSearchInput.showingSearchCriteria)
document.getElementById('quickSearchSaveAsVirtualFolder').setAttribute('disabled', 'true');
else
document.getElementById('quickSearchSaveAsVirtualFolder').removeAttribute('disabled');
}
// Methods to support highlighting. Most of this was shamelessly copied from the mozdev google toolbar project
function highlightMessage(removeExistingHighlighting)
{
// remove any existing highlighting
if (removeExistingHighlighting)
removeHighlighting();
// wanted to use selection to extend to the word
// and compare with the found range, in order to match
// only whole words
// Save selection
// XXX: Note to self, we may want to break on | and white space here
// even though normal quick searches treat white spaces as signficant
var termList = gSearchInput.value.split("|");
for (var i = 0; i < termList.length; i++)
highlight(termList[i], gHighlightColors[i %10]);
gHighlightedMessageText = true;
}
function removeHighlighting()
{
if (!gHighlightedMessageText)
return;
var msgDocument = window.top.content;
var doc = msgDocument.document;
var elem = null;
while ((elem = doc.getElementById('mail-highlight-id')))
{
var child = null;
var docfrag = doc.createDocumentFragment();
var next = elem.nextSibling;
var parent = elem.parentNode;
while((child = elem.firstChild))
docfrag.appendChild(child);
parent.removeChild(elem);
parent.insertBefore(docfrag, next);
}
gHighlightedMessageText = false;
return;
}
function highlight(word, color)
{
var msgDocument = window.top.content;
var doc = msgDocument.document;
if (!doc)
return;
if (!("body" in doc))
return;
var body = doc.body;
var count = body.childNodes.length;
searchRange = doc.createRange();
startPt = doc.createRange();
endPt = doc.createRange();
var baseNode = doc.createElement("span");
baseNode.setAttribute("style", "background-color: " + color + ";");
baseNode.setAttribute("id", "mail-highlight-id");
searchRange.setStart(body, 0);
searchRange.setEnd(body, count);
startPt.setStart(body, 0);
startPt.setEnd(body, 0);
endPt.setStart(body, count);
endPt.setEnd(body, count);
highlightText(word, baseNode);
}
// search through the message looking for occurrences of word
// and highlighting them.
function highlightText(word, baseNode)
{
var retRange = null;
while((retRange = gFinder.Find(word, searchRange, startPt, endPt)))
{
// Highlight
var nodeSurround = baseNode.cloneNode(true);
var node = highlightRange(retRange, nodeSurround);
startPt = node.ownerDocument.createRange();
startPt.setStart(node, node.childNodes.length);
startPt.setEnd(node, node.childNodes.length);
}
}
function highlightRange(range, node)
{
var startContainer = range.startContainer;
var startOffset = range.startOffset;
var endOffset = range.endOffset;
var docfrag = range.extractContents();
var before = startContainer.splitText(startOffset);
var parent = before.parentNode;
node.appendChild(docfrag);
parent.insertBefore(node, before);
return node;
}