From Chickenfoot Script Repository
// ==UserScript==
// @extensionName iGoogleBar
// @extensionAuthor Michael Bolin
// @extensionGUID 91b46d4b-dd86-4994-af2d-4a3fa0c18db8
// @version 0.5
// @updateURL http://www.bolinfest.com/igooglebar/
// @name iGoogleBar
// @when Pages Match
// @includes *
// ==/UserScript==
/** The HTMLDocument for the current page */
var myDocument;
/** The product whose popup is currently displayed */
var displayedProduct;
/** The product whose page the user is currently on */
var googleProduct;
/** DIV in the DOM that contains a product popup */
var div;
/** Username of the user (may be useful in constructing feed URLs */
var username;
/** Preference branch that contains the pref for the iGoogle bar */
var prefBranch;
/** <productName,divThatHoldsProductIframe */
var productToDivMap = {};
/**
* When the page is closed, nullify all global refrences
* to prevent memory leaks.
*/
function releaseAll() {
myDocument = null;
div = null;
displayedProduct = null;
username = null;
googleProduct = null;
prefBranch = null;
for (var product in productToDivMap) {
delete productToDivMap[product];
}
}
/**
* Synchronously fetch a URL
* @param {string} url
* @return {XMLHttpRequest}
*/
function wget(url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send(null);
return xhr;
}
/**
* Create HTML to display in a popup from an Atom feed
* @param {string} atom URL to an Atom feed
* @return {string.<HTML>} an HTML representation of the feed suitable
* to display in a popup
*/
function getHtmlForAtom(atom) {
var xhr = wget(atom);
var feedTitle = xhr.responseXML.getElementsByTagName('title')[0].
firstChild.nodeValue;
var entries = xhr.responseXML.getElementsByTagName('entry');
var html = ['<h3 style="padding-bottom: 2px; margin: 0">',
feedTitle, '</h3>'];
if (entries.length == 0) {
html.push('<i>Sorry, no entries at this time.</i>');
} else {
// For some reason, I was having problems with document.evalute(),
// so I have to use these awful loops instead of XPath.
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
var textNode = entry.getElementsByTagName('title')[0].firstChild;
var title = textNode ? textNode.nodeValue : '<i>no subject</i>';
var summaries = entry.getElementsByTagName('summary');
var summary =
summaries.length ? summaries[0].firstChild.nodeValue : null;
var links = entry.getElementsByTagName('link');
var alternate;
for (var j = 0; j < links.length; ++j) {
var link = links[j];
if (link.getAttribute('rel').toLowerCase() == 'alternate') {
alternate = link.getAttribute('href');
break;
}
}
html.push(alternate
? '<a href="' + alternate + '" target="_blank">' + title + '</a>'
: title,
'<br>',
summary ? summary : '',
'<div style="height:6px;font-size:6px;line-height:6px;clear:both">',
' </div>');
}
}
return '<div style="padding: 2px 4px">' + html.join('') + '</div>';
}
var UNREADCOUNT_SUFFIX_RE = /\/state\/com\.google\/reading-list$/;
/**
* If appropriate, return the "unread count" for a product.
* If no count is available, return a number less than 0.
* @param {string} product
* @return {number.<int>}
*/
function getUnreadCount(product) {
try {
if (product == 'Gmail') {
var xhr = wget('http://mail.google.com/mail/feed/atom');
return parseInt(xhr.responseXML.getElementsByTagName('fullcount')[0].firstChild.nodeValue);
} else if (product == 'Reader') {
var xhr = wget('http://www.google.com/reader/api/0/unread-count?all=true&output=json');
var unreadcounts = eval('(' + xhr.responseText + ')').unreadcounts;
for (var i = 0; i < unreadcounts.length; ++i) {
var unreadcount = unreadcounts[i];
if (UNREADCOUNT_SUFFIX_RE.test(unreadcount.id)) {
return unreadcount.count;
}
}
return -1;
}
} catch (e) {
// honestly, who knows what could go wrong
// the feed format could change, authentication could be bad, etc.
}
return -1;
}
var PRODUCTS = {
Gmail : {
icon : '//mail.google.com/mail/images/favicon.ico',
url : '//mail.google.com/mail',
pattern : /^https?:\/\/mail\.google\.com\/mail\//,
atom : 'http://mail.google.com/mail/feed/atom',
quirks : true,
refreshable : true,
},
Calendar : {
icon : '//calendar.google.com/googlecalendar/images/favicon.ico',
url : '//www.google.com/calendar',
pattern : /^https?:\/\/www\.google\.com\/calendar\//,
gadget : 'http://www.google.com/calendar/fullscreen?mode=AGENDA',
height : 420,
},
Documents : {
icon : '//docs.google.com/favicon.ico',
url : '//docs.google.com/',
pattern : /^https?:\/\/docs\.google\.com\//,
gadget : 'http://docs.google.com/API/IGoogle',
quirks : true,
},
Photos : {
icon : 'http://picasa.google.com/assets/picasa.ico',
url : '//picasaweb.google.com/home',
pattern : /^https?:\/\/picasaweb\.google\.com\//,
atom : 'http://picasaweb.google.com/data/feed/base/user/__USERNAME__?alt=atom',
},
Groups : {
icon : '//groups.google.com//groups/img/3/favicon.ico',
url : '//groups.google.com/',
pattern : /^https?:\/\/groups\.google\.com\//,
gadget : 'http://groups.google.com/?lnk=igg',
},
Reader : {
icon : '//www.google.com/reader/ui/favicon.ico',
url : '//www.google.com/reader/view/',
pattern : /https?:\/\/www\.google\.com\/reader\//,
gadget : 'http://www.google.com/reader/ui/standalone-module.html',
height: 407,
refreshable : true,
},
Notebook : {
icon : '//www.google.com/notebook/images/3406433090-favicon.ico',
url : '//www.google.com/notebook/',
pattern : /http:\/\/www\.google\.com\/notebook\//,
gadget : 'http://www.google.com/notebook/ig',
noHttps : true,
},
};
var DEFAULT_HEIGHT = 400;
function getProduct() {
var location = window.location.toString();
for (var product in PRODUCTS) {
var pattern = PRODUCTS[product].pattern;
if (pattern.test(location)) return product;
}
return false;
};
/**
* @param {string} branch
* @return {nsIPrefBranch2}
*/
function getPrefBranch(branch) {
if (!prefBranch) {
prefBranch = Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefService).
getBranch(branch).
QueryInterface(Components.interfaces.nsIPrefBranch2);
}
return prefBranch;
}
/** default pref value for bolinfest.igooglebar */
var DEFAULT_PREF = [
'Gmail',
'Calendar',
'Documents',
'Reader',
'Notebook',
'Photos',
// 'Groups',
];
/**
* Get the list of product names, in order, that should
* be displayed in the user's iGoogle bar.
* This is based off of the user's bolinfest.igooglebar pref in about:config
* @return {Array.<string>}
*/
function getMyProducts() {
var branch = getPrefBranch('bolinfest.');
if (branch.getPrefType('igooglebar') == 0) return DEFAULT_PREF;
var pref = branch.getCharPref('igooglebar');
if (!pref) {
return DEFAULT_PREF;
} else {
var products = pref.split(',');
for (var i = 0; i < products.length; ++i) {
products[i] = Chickenfoot.trim(products[i]);
}
return products;
}
}
var CALLBACK_NAME = 'iGoogleBarCallback';
var IMG_CLASS_NAME = 'iGoogleBarImageClass';
var ID_PREFIX = 'iGoogleBarId-';
var IMAGE_ID_PREFIX = 'iGoogleBarImageId-';
/**
* Create the HTML for the iGoogle bar
* @return {string.<HTML>} HTML for the iGoogleBar
*/
function createHtml() {
var html = [];
var tagName = PRODUCTS[googleProduct].useDiv ? 'div' : 'span';
var myProducts = getMyProducts();
for (var i = 0; i < myProducts.length; ++i) {
var name = myProducts[i];
var product = PRODUCTS[name];
if (!product) continue;
var icon = product.icon;
if (product.noHttps) icon = 'http:' + icon;
var label = createLabel(name);
html.push(
'<img class="', IMG_CLASS_NAME, '" ',
'id="', IMAGE_ID_PREFIX, name, '" ',
'src="', icon, '" style="cursor: pointer" ',
'onclick="', CALLBACK_NAME, '(\'', name, '\', this)"> ',
'<', tagName, ' class="gb1" id="', ID_PREFIX, name, '">',
label, '</', tagName, '>');
}
return html.join('');
}
/**
* @param {string} name Product name
* @return {string.<HTML>}
*/
function createLabel(name) {
var count = getUnreadCount(name);
var label;
if (count > 0 && name != googleProduct) {
label = '<span style="font-weight: bold">' + name + ' (' + count + ')</span>';
} else {
label = name;
}
var product = PRODUCTS[name];
var url = product.url;
if (product.noHttps) url = 'http:' + url;
return ((name == googleProduct) ? label
: '<a href="' + url + '" target="_blank">' + label + '</a>');
}
function refreshLabels() {
// if document or document.location is null,
// then the page has been closed so stop refreshing
if (!myDocument || !myDocument.location) {
// remove all references in hopes of preventing a memory leak!
releaseAll();
return;
}
for (var name in PRODUCTS) {
var product = PRODUCTS[name];
if (!product.refreshable) continue;
var id = ID_PREFIX + name;
var element = myDocument.getElementById(id);
if (!element) continue;
element.innerHTML = createLabel(name);
}
scheduleRefresh();
}
function scheduleRefresh() {
// refresh again in 5 minutes
if (!myDocument) myDocument = document;
setTimeout(refreshLabels, 5 * 60 * 1000);
}
function createDiv() {
var doc = document.wrappedJSObject;
var div = doc.createElement('div');
div.style.top = '24px';
div.style.left = '0';
div.style.width = '320px';
div.style.position = 'absolute';
div.style.zIndex = 100000;
if (PRODUCTS[googleProduct].quirks) {
div.style.fontSize = 'small';
}
doc.body.appendChild(div);
return div;
}
function getGbar() {
if (!document) return null;
return document.getElementById('gbar');
}
function getUsername() {
if (username == null) {
var gbar = getGbar();
var email = find(gbar.parentNode).find(/[\w]+@gmail\.com/);
if (email.hasMatch) {
username = email.text.substring(0, email.text.length - '@gmail.com'.length);
} else {
// our heuristic failed. sad :(
// admittedly, this won't work if the user is not using a Gmail account
email = '';
}
}
return username;
}
function callback(productName, image) {
// get div for product
var div;
var isNewDiv = !(productName in productToDivMap);
if (isNewDiv) {
div = createDiv();
productToDivMap[productName] = div;
} else {
div = productToDivMap[productName];
if (!PRODUCTS[productName].gadget) {
div.innerHTML = '';
}
}
// position div
div.style.left = document.getBoxObjectFor(image).x + 'px'
// either create the IFRAME or hide it
if (productName == displayedProduct) {
hideIframe(productName);
} else if (isNewDiv || !PRODUCTS[productName].gadget) {
var product = PRODUCTS[productName];
var gadget = product.gadget;
var style = 'background-color: white; border: 1px solid #ccc; height: 420px; width: 100%;';
style += 'height: ' + (product.height ? product.height : DEFAULT_HEIGHT) + 'px';
if (gadget) {
div.innerHTML = '<iframe src="' + gadget + '" style="' + style + '"></iframe>';
} else if (product.atom) {
var atom = product.atom;
var username = getUsername();
atom = atom.replace(/__USERNAME__/g, username);
div.innerHTML = '<div style="' + style + ' padding: 2px; overflow: auto">' +
getHtmlForAtom(atom) + '</div>';
} else {
div.innerHTML = '<div style="border: 1px solid red; background-color: #FF9; padding: 2px">' +
'Sorry, ' + productName + ' does not have an iGoogle Gadget that can be displayed here</div>';
}
displayedProduct = productName;
} else {
displayedProduct = productName;
}
}
/**
* @param {Node} node
* @param {Node} ancestor
* @return {boolean} true if node is a descendant of ancestor; false otherwise
*/
function isDescendant(node, ancestor) {
if (!node) return false;
if (node === ancestor || node === ancestor.wrappedJSObject) return true;
return isDescendant(node.parentNode, ancestor);
}
function clearIframe(event) {
var target = event.target;
if (target.wrappedJSObject) target = target.wrappedJSObject;
if (target.className == IMG_CLASS_NAME) {
var productName = target.id.substring(IMAGE_ID_PREFIX.length);
var differentProduct = (productName != displayedProduct)
if (differentProduct) hideIframe(displayedProduct);
} else {
var div = productToDivMap[displayedProduct];
if (div && !isDescendant(target, div)) {
hideIframe(displayedProduct);
}
}
}
function hideIframe(productName) {
var div = productToDivMap[productName];
if (!div) return;
var style = div.style;
style.left = '-1000px';
displayedProduct = '';
}
function main() {
googleProduct = getProduct();
if (!googleProduct) return;
mainCallback();
}
var gbarAttempts = 0;
function mainCallback() {
var gbar = getGbar();
if (!gbar) {
// some products, such as Gmail, do not appear to have
// the bar readily available upon loading it, so we poll
// once a second for 20 seconds for the bar in that case
if (++gbarAttempts < 20) {
setTimeout(mainCallback, 1 * 1000);
}
return;
}
var html = createHtml();
gbar.innerHTML = '<nobr>' + html + '</nobr>';
var win = window.wrappedJSObject;
win[CALLBACK_NAME] = callback;
win.document.addEventListener('mousedown', clearIframe, false);
scheduleRefresh();
}
main();