GCal Event Color Codes

By Matthew Jeffryes Last update Sep 6, 2009 — Installed 2,095 times. Daily Installs: 1, 0, 1, 2, 0, 0, 0, 2, 0, 4, 0, 1, 2, 2, 2, 0, 1, 3, 0, 4, 5, 7, 0, 2, 4, 7, 8, 4, 2, 3, 2, 3

There are 2 previous versions of this script.

// GCal Event Color Codes
// This scripts allows you to activate and alternate set of color codes
// for events in your Google Calendars. Define a category name starting with !
// and give the colors you want for the background and border of the event.
// The color coding is enabled and disabled by clicking on the little calendar icon
// added to the corner of the main calendar frame. Any questions can be directed to
// mjeffryes+userscripts@gmail.com. Enjoy!
// ==UserScript==
// @name        GCal Event Color Codes
// @namespace   http://www.hmc.edu/~mjeffryes
// @description Color codes GCal events using tags
// @include     http://www.google.tld/calendar/*
// @include     https://www.google.tld/calendar/*
// ==/UserScript==

///////////////////////////////////////////////////////////////////////////////////////////
//color-tag lookup:
//["tag",		"border color"		"fill color", "extra tag list"]	

tags = [
["travel",		"rgb(108,148,118)",	"rgb(148,188,158)", ["trip","flight"] ],
["sleep",		"rgb(0,32,113)",	"rgb(50,82,168)", ["nap"]],
["fun",			"rgb(51,163,163)",	"rgb(92,217,217)", ["comics"]],
["quiet",		"rgb(79,118,54)",	"rgb(119,158,94)", ["pondering","reading","review"]],
["people",		"rgb(153,72,153)",	"rgb(197,120,197)", ["sam & max","hanging"]],
["hw",			"rgb(103,51,0)",	"rgb(143,86,40)", []],
["food",		"rgb(0,170,0)",		"rgb(46,216,42)", ["dinner","lunch","bfast","breakfast"]],
["admin",		"rgb(185,20,20)",	"rgb(225,53,53)", ["email","store", "finance"]],
["class",		"rgb(36,106,136)",	"rgb(76,146,176)", ["VLSI","infocom","clinic"]],
["maint",		"rgb(185,114,83)",	"rgb(225,154,123)", ["wakeup","prep for bed","exercise"]],
["church",		"rgb(165,165,0)",	"rgb(205,205,0)", ["bible study"]],
["projects",	"rgb(0,90,5)",		"rgb(16,136,32)", []],
["work",		"rgb(50,50,59)",	"rgb(90,90,90)", ["Rhizome","RS"]],
["service",		"rgb(110,40,70)",	"rgb(150,80,110)", ["IV setup"]],
["none",		"rgb(0,0,0)",		"rgb(0,0,0)"], []]; //special tag, do not remove

///////////////////////////////////////////////////////////////////////////////////////////
//load a new ColorCoder object on start 
window.addEventListener("load", function() { unsafeWindow.gccc = new GCalColorCoder(); }, false);

///////////////////////////////////////////////////////////////////////////////////////////
//GCalColorCoder class

function GCalColorCoder()
{
	//register toggles
	this.tag_prefix = (makeMenuToggle("GCal_tag_sym",true,"Use '!'","Use '#'","Tag prefix"))?'!':'#';
	this.on = {'color': makeMenuToggle("GCal_color_on",true,"GCCC Colors","Default Colors","On Page Load"),
				'sum' : makeMenuToggle("GCal_sum_on",true,"Sums Off","Sums On","On Page Load")};
	//initialize event tags	
	this.loadTags();
	this.xsearch = document.createExpression(
		'//dl[@class="cbrd" and not(@id)] | //div[contains(@class,"ca-evp") and (contains(@class,"rb-n") or contains(@class,"te")) and not(@id)]'
		, null);
	document.body.addEventListener("DOMSubtreeModified", function(){unsafeWindow.gccc.tagNodes();}, false);  
	//Add stylesheet and UI elements
	this.style = addGlobalStyle(this.makeCSS());
	this.insert(single_xpath('//div[@class="nb_0"]'));
	//sync
	this.update();
}

GCalColorCoder.prototype = {
	name: "gccc",
	
	icons: {
	'color': { true: "images/icon_success.gif", false: "images/icon_r_no.gif"},
	'sum'  : { true: "images/opentriangle.gif", false: "images/triangle.gif"},
	},
	
	//create a stylesheet for tagged nodes
	makeCSS: function(){
		style = "";
		for (i in tags) {
			style += 'dl#' + tags[i][0] + '.cbrd { ' +
				'border-color: ' + tags[i][1] + ' !important; ' +
				'background-color: ' + tags[i][2] + ' !important; } ' +
				'dl#' + tags[i][0] + '.cbrd dt, div#' + tags[i][0] + '.rb-n' +
				' { background-color: ' + tags[i][1] + ' !important; } ' +
				'div#' + tags[i][0] + '.te { color: ' + tags[i][1] + ' !important; }';
		}
		return style;
	},
	
	//load tags
	loadTags: function(){
		this.tags =  new Array();
		for(var i = 0; i<tags.length;i++){
			pattern = [tags[i][0]].concat(tags[i][3]).join('|');
			this.tags[i]= { tag: tags[i][0], regexp: new RegExp( pattern,'i') };
		}
		this.tag_regexp = new RegExp( this.tag_prefix + "[a-z]+");
	},
	
	// sync page state with object state
	update: function(){ this.style.disabled = !this.on['color']; this.tagNodes(); },
	
	// search for events that don't have an id field and set it to the right tag
	tagNodes: function(){
		xresult = this.xsearch.evaluate(document, XPathResult.ANY_UNORDERED_NODE_TYPE, null);
		if(node = xresult.singleNodeValue)
		{
			node.setAttribute('id', this.matchTag(node.textContent));
		}
	},
	
	matchTag: function(text){
		if(match = this.tag_regexp.exec(text))
			text = String(match).substring(1);
		for (x in this.tags){
			if(this.tags[x].regexp.exec(text))
				return this.tags[x].tag;
		}
		return "none";
	},
	
	//toggle color codes on and off
	toggle: function(btnid){
		this.on[btnid] = !this.on[btnid];
		this.buttons[btnid].childNodes[1].src = this.icons[btnid][this.on[btnid]];
		window.setTimeout(GM_setValue, 0, "GCal_"+btnid+"_on", this.on[btnid]);
		this.update();
	},
	
	//insert the visual elements
	insert: function(loc){
		this.div = document.createElement('div');
		this.div.setAttribute('class','nb_0');
		this.div.setAttribute('style', 'padding-top: 12px;');
		this.div.innerHTML = 
			'<div style="width: 100%; height: 2px;">' +
				'<div class="t1 chromeColor"></div><div class="t2 chromeColor"></div> </div>' +
			'<h2 id="'+ this.name +'" class="calHeader normalText"></h2>';
		loc.parentNode.insertBefore(this.div, loc);
		title_div = single_xpath("//div//h2[@id='"+this.name+"']");
		this.buttons = new Object();
		//this.insertbutton(title_div, 'sum');
		this.insertbutton(title_div, 'color');
	},
	
	//insert a toggle button
	insertbutton: function(loc, btnid){
		this.buttons[btnid] = document.createElement('div');
		this.buttons[btnid].innerHTML='Color Codes: <img src="' + this.icons[btnid][this.on[btnid]] + '"/>';
		this.buttons[btnid].setAttribute('onclick', this.name+'.toggle(\''+btnid+'\')');
		loc.appendChild(this.buttons[btnid]);
	},
};

//////////////////////////////////////////////////////////////////////////////////////////
//Helper code for adding global css rules (http://diveintogreasemonkey.org/patterns/add-css.html)
function addGlobalStyle(css) {
    var head, style;
    head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
    return style;
}

//////////////////////////////////////////////////////////////////////////////////////////
//Helper code snippit for adding menu toggle items (http://wiki.greasespot.net/Code_snippets)
function makeMenuToggle(key, defaultValue, toggleOn, toggleOff, prefix) {
  // Add menu toggle and return value
  GM_registerMenuCommand((prefix ? prefix+": " : "") + (window[key] ? toggleOff : toggleOn), function() {
    GM_setValue(key, !window[key]);
  });
  return GM_getValue(key, defaultValue);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//XPath Helpers
function xpath(query) {
  return document.evaluate(query, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
}
function single_xpath(query) {
  return xpath(query).snapshotItem(0);
}