gecko-dev/mailnews/base/resources/content/msgViewNavigation.js

592 lines
17 KiB
JavaScript

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* The contents of this file are subject to the Netscape 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/NPL/
*
* 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 Netscape are
* Copyright (C) 1998-1999 Netscape Communications Corporation. All
* Rights Reserved.
*/
/* This file contains the js functions necessary to implement view navigation within the 3 pane. */
function GoMessage(message)
{
return true;
}
function ResourceGoMessage(message)
{
return true;
}
function GoUnreadMessage(message)
{
var isUnread = message.getAttribute('IsUnread');
return(isUnread == 'true');
}
function ResourceGoUnreadMessage(message)
{
var isUnreadValue = GetMessageValue(message, "http://home.netscape.com/NC-rdf#IsUnread");
return(isUnreadValue == 'true');
}
function GoFlaggedMessage(message)
{
var flagged = message.getAttribute('Flagged');
return(flagged == 'flagged');
}
function ResourceGoFlaggedMessage(message)
{
var flaggedValue = GetMessageValue(message, "http://home.netscape.com/NC-rdf#Flagged");
return(flaggedValue == 'flagged');
}
function GoNewMessage(message)
{
var status = message.getAttribute('Status');
return(status == 'new');
}
function ResourceGoNewMessage(message)
{
var StatusValue = GetMessageValue(message, "http://home.netscape.com/NC-rdf#Status");
return(StatusValue == 'new');
}
function GetMessageValue(message, propertyURI)
{
var db = GetThreadTree().database;
var propertyResource = RDF.GetResource(propertyURI);
var node = db.GetTarget(message, propertyResource, true);
var literal = node.QueryInterface(Components.interfaces.nsIRDFLiteral);
if(literal)
{
return literal.Value;
}
return null;
}
function GoUnreadThread(messageElement)
{
var messageuri = messageElement.getAttribute('id');
var messageResource = RDF.GetResource(messageuri);
var message = messageResource.QueryInterface(Components.interfaces.nsIMessage);
var folder = message.GetMsgFolder();
var thread = folder.getThreadForMessage(message);
return(thread.numUnreadChildren != 0);
}
/*GoNextMessage finds the message that matches criteria and selects it.
nextFunction is the function that will be used to detertime if a message matches criteria.
It must take a node and return a boolean.
nextResourceFunction is the function that will be used to determine if a message in the form of a resource
matches criteria. Takes a resource and returns a boolean
nextThreadFunction is an optional function that can be used to optimize whether or not a thread will have a
message that matches the criteria. Takes the top level message in the form of a node and returns a boolean.
startFromBeginning is a boolean that states whether or not we should start looking at the beginning
if we reach the end
*/
function GoNextMessage(nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning)
{
var tree = GetThreadTree();
var selArray = tree.selectedItems;
var length = selArray.length;
if ( selArray && ((length == 0) || (length == 1)) )
{
var currentMessage;
if(length == 0)
currentMessage = null;
else
currentMessage = selArray[0];
var nextMessage = GetNextMessage(tree, currentMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning);
//Only change the selection if there's a valid nextMessage
if(nextMessage && (nextMessage != currentMessage))
ChangeSelection(tree, nextMessage);
}
}
function GetNextMessage(tree, currentMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning)
{
var nextMessage;
if(messageView.showThreads)
{
nextMessage = GetNextMessageInThreads(tree, currentMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning);
}
else
{
nextMessage = GetNextMessageUnthreaded(tree, currentMessage, nextFunction, startFromBeginning);
}
return nextMessage;
}
/*GetNextMessageUnthreaded does the iterating for the Next menu item.
currentMessage is the message we are starting from.
nextFunction is the function that will be used to detertime if a message matches criteria.
It must take a node and return a boolean.
startFromBeginning is a boolean that states whether or not we should start looking at the beginning
if we reach then end
*/
function GetNextMessageUnthreaded(tree, currentMessage, nextFunction, startFromBeginning)
{
var foundMessage = false;
var nextMessage;
if(currentMessage)
nextMessage = currentMessage.nextSibling;
else
nextMessage = FindFirstMessage(tree);
//In case we are currently the bottom message
if(!nextMessage && startFromBeginning)
{
dump('no next message\n');
var parent = currentMessage.parentNode;
nextMessage = parent.firstChild;
}
while(nextMessage && (nextMessage != currentMessage))
{
if(nextFunction(nextMessage))
break;
nextMessage = nextMessage.nextSibling;
/*If there's no nextMessage we may have to start from top.*/
if(!nextMessage && (nextMessage!= currentMessage) && startFromBeginning)
{
dump('We need to start from the top\n');
var parent = currentMessage.parentNode;
nextMessage = parent.firstChild;
}
}
if(nextMessage)
{
var id = nextMessage.getAttribute('id');
dump(id + '\n');
}
else
dump('No next message\n');
return nextMessage;
}
function GetNextMessageInThreads(tree, currentMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning)
{
var checkStartMessage = false;
//In the case where nothing is selected
if(currentMessage == null)
{
currentMessage = FindFirstMessage(tree);
checkStartMessage = true;
}
return FindNextMessageInThreads(currentMessage, currentMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning, checkStartMessage);
}
function FindNextMessageInThreads(startMessage, originalStartMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning, checkStartMessage)
{
var nextMessage;
var nextChildMessage;
//First check startMessage if we are supposed to
if(checkStartMessage)
{
if(nextFunction(startMessage))
return startMessage;
}
//if we're on the top level and a thread function has been passed in, we might be able to search faster.
if(startMessage.parentNode.parentNode.nodeName != "treeitem" && nextThreadFunction)
{
var nextTopMessage = FindNextThread(startMessage, nextThreadFunction, startFromBeginning, true);
nextMessage = GetNextInThread(nextTopMessage, nextFunction, nextResourceFunction);
if(nextMessage)
{
return nextMessage;
}
}
//Next, search the current messages children.
nextChildMessage = FindNextInChildren(startMessage, originalStartMessage, nextFunction, nextResourceFunction);
if(nextChildMessage)
return nextChildMessage;
//Next we need to search the current messages siblings
nextMessage = startMessage.nextSibling;
while(nextMessage)
{
//In case we've already been here before
if(nextMessage == originalStartMessage)
return nextMessage;
if(nextFunction(nextMessage))
return nextMessage;
var nextChildMessage = FindNextInChildren(nextMessage, originalStartMessage, nextFunction, nextResourceFunction);
if(nextChildMessage)
return nextChildMessage;
nextMessage = nextMessage.nextSibling;
}
//Finally, we need to find the next of the start message's ancestors that has a sibling
var parentMessage = startMessage.parentNode.parentNode;
while(parentMessage.nodeName == 'treeitem')
{
if(parentMessage.nextSibling != null)
{
nextMessage = FindNextMessageInThreads(parentMessage.nextSibling, originalStartMessage, nextFunction, nextResourceFunction, nextThreadFunction, startFromBeginning, true);
return nextMessage;
}
parentMessage = parentMessage.parentNode.parentNode;
}
//otherwise it's the tree so we need to stop and potentially start from the beginning
if(startFromBeginning)
{
nextMessage = FindNextMessageInThreads(FindFirstMessage(parentMessage), originalStartMessage, nextFunction, nextResourceFunction, nextThreadFunction, false, true);
return nextMessage;
}
return null;
}
//Searches children messages in thread navigation.
function FindNextInChildren(parentMessage, originalStartMessage, nextFunction, nextResourceFunction)
{
var isParentOpen = parentMessage.getAttribute('open') == 'true';
//First we'll deal with the case where the parent is open. In this case we can use DOM calls.
if(isParentOpen)
{
//In this case we have treechildren
if(parentMessage.childNodes.length == 2)
{
var treechildren = parentMessage.childNodes[1];
var childMessages = treechildren.childNodes;
var numChildMessages = childMessages.length;
for(var i = 0; i < numChildMessages; i++)
{
var childMessage = childMessages[i];
//If we're at the original message again then stop.
if(childMessage == originalStartMessage)
return childMessage;
if(nextFunction(childMessage))
return childMessage;
else
{
//if this child isn't the message, perhaps one of its children is.
var nextChildMessage = FindNextInChildren(childMessage, originalStartMessage, nextFunction);
if(nextChildMessage)
return nextChildMessage;
}
}
}
}
else
{
//We need to traverse the graph in rdf looking for the next resource that fits what we're searching for.
var parentUri = parentMessage.getAttribute('id');
var parentResource = RDF.GetResource(parentUri);
//If we find one, then we get the id and open up the parent and all of it's children. Then we find the element
//with the id in the document and return that.
if(parentResource)
{
var nextResource = FindNextInChildrenResources(parentResource, nextResourceFunction);
if(nextResource)
{
OpenThread(parentMessage);
var nextUri = nextResource.Value;
var nextChildMessage = document.getElementById(nextUri);
return nextChildMessage;
}
}
}
return null;
}
function FindNextInChildrenResources(parentResource, nextResourceFunction)
{
var db = GetThreadTree().database;
var childrenResource = RDF.GetResource("http://home.netscape.com/NC-rdf#MessageChild");
var childrenEnumerator = db.GetTargets(parentResource, childrenResource, true);
if(childrenEnumerator)
{
while(childrenEnumerator.HasMoreElements())
{
var childResource = childrenEnumerator.GetNext().QueryInterface(Components.interfaces.nsIRDFResource);
if(childResource)
{
if(nextResourceFunction(childResource))
return childResource;
var nextMessageResource = FindNextInChildrenResources(childResource, nextResourceFunction);
if(nextMessageResource)
return nextMessageResource;
}
}
}
return null;
}
/*GoPreviousMessage finds the message that matches criteria and selects it.
previousFunction is the function that will be used to detertime if a message matches criteria.
It must take a node and return a boolean.
startFromEnd is a boolean that states whether or not we should start looking at the end
if we reach the beginning
*/
function GoPreviousMessage(previousFunction, startFromEnd)
{
var tree = GetThreadTree();
var selArray = tree.selectedItems;
if ( selArray && (selArray.length == 1) )
{
var currentMessage = selArray[0];
var previousMessage = GetPreviousMessage(currentMessage, previousFunction, startFromEnd);
//Only change selection if there's a valid previous message.
if(previousMessage && (previousMessage != currentMessage))
ChangeSelection(tree, previousMessage);
}
}
/*GetPreviousMessage does the iterating for the Previous menu item.
currentMessage is the message we are starting from.
previousFunction is the function that will be used to detertime if a message matches criteria.
It must take a node and return a boolean.
startFromEnd is a boolean that states whether or not we should start looking at the end
if we reach then beginning
*/
function GetPreviousMessage(currentMessage, previousFunction, startFromEnd)
{
var foundMessage = false;
var previousMessage = currentMessage.previousSibling;
//In case we're already at the top
if(!previousMessage && startFromEnd)
{
var parent = currentMessage.parentNode;
previousMessage = parent.lastChild;
}
while(previousMessage && (previousMessage != currentMessage))
{
if(previousFunction(previousMessage))
break;
previousMessage = previousMessage.previousSibling;
if(!previousMessage && startFromEnd)
{
var parent = currentMessage.parentNode;
previousMessage = parent.lastChild;
}
}
if(previousMessage)
{
var id = previousMessage.getAttribute('id');
dump(id + '\n');
}
else
dump('No previous message\n');
return previousMessage;
}
function FindFirstMessage(tree)
{
//getElementsByTagName is too slow which is why I'm using this loop. Just find the first
//child of the tree that has the 'treechildren' tag and return it's first child. This will
//be the first message.
var children = tree.childNodes;
var numChildren = children.length;
for(var i = 0; i < numChildren; i++)
{
if(children[i].nodeName == 'treechildren')
{
return children[i].firstChild;
}
}
return null;
}
// nextThreadFunction is the function that determines whether a top level message is part of a thread that fits criteria.
// nextMessageFunction is the function that would be used to find the next message in a thread if gotoNextInThread is true
// nextResourceFunction is the function that would be used to find the next message in a thread as a resource if gotoNextInThread is true
// startFromBeginning is true if we should start looking from the beginning after we get to the end of the thread pane.
// gotoNextInThread is true if once we find an unrad thread we should select the first message in that thread that fits criteria
function GoNextThread(nextThreadFunction, nextMessageFunction, nextResourceFunction, startFromBeginning, gotoNextInThread)
{
if(messageView.showThreads)
{
var tree = GetThreadTree();
var selArray = tree.selectedItems;
var length = selArray.length;
if ( selArray && ((length == 0) || (length == 1)) )
{
var currentMessage;
if(length == 0)
currentMessage = null;
else
currentMessage = selArray[0];
var nextMessage;
var currentTopMessage;
var checkCurrentTopMessage;
//Need to get the parent message for the current selection to begin to find thread
if(currentMessage)
{
//need to find its top level message and we don't want it to be checked for criteria
currentTopMessage = FindTopLevelMessage(currentMessage);
checkCurrentTopMessage = false;
}
else
{
//currentTopmessage is the first one in the tree and we want it to be checked for criteria.
currentTopMessage = FindFirstMessage(tree);
checkCurrentTopMessage = true;
}
var nextTopMessage = FindNextThread(currentTopMessage, nextThreadFunction, startFromBeginning, checkCurrentTopMessage);
var changeSelection = (nextTopMessage != null && ((currentTopMessage != nextTopMessage) || checkCurrentTopMessage));
if(changeSelection)
{
if(gotoNextInThread)
{
nextMessage = GetNextInThread(nextTopMessage, nextMessageFunction, nextResourceFunction);
ChangeSelection(tree, nextMessage);
}
else
ChangeSelection(tree, nextTopMessage);
}
}
}
}
//Given the top level message of a thread and the searching functions, this returns the first message in that thread that matches
//the criteria
function GetNextInThread(topMessage, nextMessageFunction, nextResourceFunction)
{
var nextMessage;
if(nextMessageFunction(topMessage))
nextMessage = topMessage;
else
{
nextMessage = FindNextInChildren(topMessage, null, nextMessageFunction, nextResourceFunction);
}
return nextMessage;
}
function FindTopLevelMessage(startMessage)
{
var currentTop = startMessage;
var parent = startMessage.parentNode.parentNode;
while(parent.nodeName == 'treeitem')
{
currentTop = parent;
parent = parent.parentNode.parentNode;
}
return currentTop;
}
function FindNextThread(startThread, nextThreadFunction, startFromBeginning, checkStartThread)
{
if(checkStartThread)
{
if(nextThreadFunction(startThread))
return startThread;
dump("start thread doesn't match\n");
}
var nextThread = startThread.nextSibling;
//In case we are currently the bottom message
if(!nextThread && startFromBeginning)
{
var parent = startThread.parentNode;
nextThread = parent.firstChild;
}
while(nextThread && (nextThread != startThread))
{
if(nextThreadFunction(nextThread))
{
break;
}
nextThread = nextThread.nextSibling;
/*If there's no nextMessage we may have to start from top.*/
if(!nextThread && (nextThread!= startThread) && startFromBeginning)
{
var parent = startThread.parentNode;
nextThread = parent.firstChild;
}
}
return nextThread;
}
function ScrollToFirstNewMessage()
{
var tree = GetThreadTree();
var newMessage = GetNextMessage(tree, null, GoNewMessage, ResourceGoNewMessage, null, false)
if(newMessage)
{
tree.ensureElementIsVisible(newMessage);
}
}