Label To Top
By Ryan Hagan
—
Last update Dec 14, 2007
—
Installed
793 times.
// Copyright 2007 Ryan Hagan <ryan@ryanhagan.net>.
// All rights Reserved.
//
//
// RELEASE NOTES
// =============
// v1.1 (12/14/2007)
// * Added ability to control behavior through settings in script.
// * Added code to make unread count on labels appear in front of label instead of behind.
//
// v1.0 (11/25/2007)
// * Initial release of script.
//
//
// KNOWN ISSUES
// ============
// * Only works with new Gmail interface.
// * Will not work in conjunction with Folders4Gmail.
// * Rewriting unread count on labels breaks the 'Go To Label' macro in Gmail Macros script which
// is included with the Better Gmail Firefox Addon.
//
//
//
// ==UserScript==
// @name Label To Top
// @namespace ryanhagan.net
// @include http://mail.google.com/mail/*
// @include https://mail.google.com/mail/*
// ==/UserScript==
// Program configuration
var UPDATE_LABEL_INTERVAL = 2000;
var REWRITE_LABEL_BOX_WITH_UNREAD_AT_TOP = true;
var REWRITE_LABELS_WITH_UNREAD_COUNT_IN_FRONT = true;
// Which version of gMonkey does this script use?
var GMONKEY_VER = "1.0";
// XPath searches used in code
var USER_LABELS_ROOT_SEARCH = ".//div[@class='XoqCub XPj4ef']//div[@class='XoqCub C9Pn8c']";
var USER_LABELS_TABLE_SEARCH = ".//table";
var USER_LABELS_TABLE_THEAD_SEARCH = ".//table/thead";
var UNREAD_LABELS_SEARCH = ".//tr//div[@class='yyT6sf PQmvpb']";
var READ_LABELS_SEARCH = ".//tr//div[@class='yyT6sf']";
// Regular Expressions used in code
var BREAK_LABEL_TEXT_AND_UNREAD_COUNT_APART_REGEXP = /^([\[\]\-\_\\\/\.\d\w\s]+?) \(([\d]+)\)$/;
var UNREAD_COUNT_IN_FRONT_REGEXP = /^\([\d]+?\) /;
var UNREAD_COUNT_IN_BACK_REGEXP = / \([\d]+?\)$/;
// Other constants
var LABEL_TEXT_TD_CELL_ATTRIBUTE = '';
var LABEL_COLOR_TD_CELL_ATTRIBUTE = 'BFvfre';
window.addEventListener('load', function() {
unsafeWindow.gmonkey.load(GMONKEY_VER, function( gmail ) {
// create intervalId variable
var intervalId = null;
// get reference to the user labels in the DOM
navPaneElem = gmail.getNavPaneElement();
var userLabelsElem = _XPathSearch(USER_LABELS_ROOT_SEARCH, navPaneElem).snapshotItem(0);
// create an empty thead element on the user labels table where
// we'll store the labels with unread content.
labelTableElem = _XPathSearch(USER_LABELS_TABLE_SEARCH, userLabelsElem).snapshotItem(0);
var newTHeadElement = document.createElement('thead');
labelTableElem.insertBefore(newTHeadElement, labelTableElem.firstChild);
// convenience references to keep from having to scan all labels each time
// function is called.
var unreadTableElem = labelTableElem.childNodes[0];
var readTableElem = labelTableElem.childNodes[1];
function _LabelsToTop()
{
// clear any interval callbacks to this function
window.clearInterval(intervalId);
if ( REWRITE_LABEL_BOX_WITH_UNREAD_AT_TOP )
{
// move unread items to the unread container
unreadLabels = _XPathSearch(UNREAD_LABELS_SEARCH, readTableElem);
_MoveLabelsToContainer(unreadLabels, unreadTableElem);
// move read items to the normal container
readLabels = _XPathSearch(READ_LABELS_SEARCH, unreadTableElem);
_MoveLabelsToContainer(readLabels, readTableElem);
}
if ( REWRITE_LABELS_WITH_UNREAD_COUNT_IN_FRONT )
{
unreadLabels = _XPathSearch(UNREAD_LABELS_SEARCH, labelTableElem);
_RewriteLabelsWithCountAtFront(unreadLabels);
}
// call this function again on a regular interval
intervalId = window.setInterval(_LabelsToTop, UPDATE_LABEL_INTERVAL);
}
// run this script periodically
gmail.registerViewChangeCallback(_LabelsToTop);
_LabelsToTop();
});
}, true);
function _XPathSearch( xpathExpression, contextNode ) {
if (!xpathExpression) xpathExpression = document;
return document.evaluate(xpathExpression, contextNode, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}
function _MoveLabelsToContainer(labels, toContainerElem)
{
// loop over results, pull the <tr> out of the DOM
labelsCount = labels.snapshotLength;
for ( labelIndex = 0; labelIndex < labelsCount; labelIndex++ )
{
// first, back up the DOM tree until we get to a <tr>
currElem = labels.snapshotItem(labelIndex);
while ( currElem.tagName.toLowerCase() != 'tr')
{
currElem = currElem.parentNode;
}
// walk the toContainer and insert element at first space available
toContainerCount = toContainerElem.childNodes.length;
if ( toContainerCount == 0 )
{
toContainerElem.appendChild( currElem );
}
else
{
// Insert the label in alphabetical order. This assumes that the list is already sorted in
// alphabetical order. If it is not, then this won't work at all. Unless another script
// is messing with the order of your labels, this shouldn't be a problem.
for ( containerLabelIndex = 0; containerLabelIndex < toContainerCount; containerLabelIndex++ )
{
if ( _GetLabelText(currElem).toLowerCase() < _GetLabelText(toContainerElem.childNodes[containerLabelIndex]).toLowerCase() )
{
toContainerElem.insertBefore(currElem, toContainerElem.childNodes[containerLabelIndex]);
break;
}
}
// if we got all the way through the list and haven't put the label anywhere, throw it down
// at the end of the label list.
if ( containerLabelIndex == toContainerCount && toContainerElem.lastChild != currElem )
toContainerElem.appendChild( currElem );
}
}
}
function _RewriteLabelsWithCountAtFront(labels)
{
// loop over results, pull the <tr> out of the DOM
labelsCount = labels.snapshotLength;
for ( labelIndex = 0; labelIndex < labelsCount; labelIndex++ )
{
// first, back up the DOM tree until we get to a <tr>
currElem = labels.snapshotItem(labelIndex);
while ( currElem.tagName.toLowerCase() != 'tr')
{
currElem = currElem.parentNode;
}
// rewrite the label
if ( _GetLabelText(currElem).match(UNREAD_COUNT_IN_BACK_REGEXP) )
_SetLabelText(currElem, _RewriteLabelWithCountAtFront(_GetLabelText(currElem)));
}
}
function _GetLabelText(elem)
{
// since we're mostly working with <tr> tags here, I needed a helper function to grab the actual
// label text from the container. First, we should check to make sure we've really been passed
// a <tr>, because if we haven't been, who knows what this would return?
var labelText = '';
if ( elem.tagName.toLowerCase() == 'tr' )
{
labelText = elem.firstChild.firstChild.firstChild.firstChild.innerHTML;
if ( labelText.match(UNREAD_COUNT_IN_FRONT_REGEXP) )
{
labelText = labelText.replace(UNREAD_COUNT_IN_FRONT_REGEXP, '');
}
}
else
{
labelText = '[invalid reference]';
}
return labelText;
}
function _SetLabelText(elem, text)
{
// since we're mostly working with <tr> tags here, I needed a helper function to set the
// label text in the container. First, we should check to make sure we've really been passed
// a <tr>, because if we haven't been, who knows what this would do?
if ( elem.tagName.toLowerCase() == 'tr' )
elem.firstChild.firstChild.firstChild.firstChild.innerHTML = text;
}
function _RewriteLabelWithCountAtFront(labelText)
{
// find the unread count on the label
var newLabelText = '';
var unreadConvMatch = labelText.match(BREAK_LABEL_TEXT_AND_UNREAD_COUNT_APART_REGEXP);
// if we get something that looks like a good match, then prepend the label with the unread count
if ( unreadConvMatch && unreadConvMatch.length == 3 && parseInt(unreadConvMatch[2]) > 0 )
newLabelText = '(' + unreadConvMatch[2] + ') ' + unreadConvMatch[1];
else
newLabelText = labelText;
return newLabelText;
}