Source for "GCal Event Color Codes"

By Matthew Jeffryes
Has no other scripts.


// 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 connecting the appropriate hooks
//colorhook search term: "eventowner"
//eventhook search term: "reldiv"
window.addEventListener("load", function() {
	rhook = function(){ unsafeWindow._EH_nav(1); unsafeWindow._EH_nav(-1); };
	ehook = function(str){ return unsafeWindow.Vv[str].F};
	vhook = function(){s=unsafeWindow._EH_dr();s=s[s.length-1];return (s!='2' && s!='3')};
	unsafeWindow.gccc = new GCalColorCoder('gccc',unsafeWindow.UB, rhook, ehook, vhook);
	unsafeWindow.UB = unsafeWindow.gccc.color_hook
}, false);

///////////////////////////////////////////////////////////////////////////////////////////
//GCalColorCoder class
function GCalColorCoder(name, color_hook, refresh_hook, events_hook,valid_hook){
	this.name = name;
	this.onehr_ms = 1000*3600;
	this.color_hook = function(a){ color_hook(a); unsafeWindow.gccc.run(); }
	this.refresh_hook = refresh_hook;
	this.events_hook = events_hook;
	this.valid_hook = valid_hook;
	this.icons = new Object();
	this.icons['color'] = { true: "images/icon_success.gif", false: "images/icon_r_no.gif"};
	this.icons['sum'] = { true: "images/opentriangle.gif", false: "images/triangle.gif"};
	//import on/off state
	//this.on = { 'color': GM_getValue("GCal_color_on", false), 'sum': GM_getValue("GCal_sum_on", true) };
	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")};
	//register toggle & get value
	this.tag_pfx = (makeMenuToggle("GCal_tag_sym",true,"Use '!'","Use '#'","Tag prefix"))?'!':'#';
	//insert the on/off button and run for the first time
	this.insert(document.getElementById("nb_0"));
	this.run();
}

GCalColorCoder.prototype = {
	//date helper
	dt_to_Date: function(dt,t){return new Date(dt.j,dt.f-1,dt.c,dt.m,dt.k,dt.s)},
	//load tags
	loadTags: function(){
		this.tags = new Object();
		this.orderedtags = new Array();
		this.subtags = new Object();
		for(var i = 0; i<tags.length;i++){
			this.orderedtags[i] = {name: this.tag_pfx+tags[i][0], total: 0, 
									border: tags[i][1], fill: tags[i][2]};
			this.tags[this.orderedtags[i].name] = this.orderedtags[i];
			if (tags[i][3]){
				this.subtags[tags[i][0]] = tags[i][0];
				for(var j=0; j<tags[i][3].length; j++){
					this.subtags[tags[i][3][j]] = tags[i][0];
				}
			}
		}
		//set bounds for the sumation
		datetext = document.getElementById("dateunderlay").innerHTML.split(" ");
		datetext = Array.concat(datetext.slice(0,2), datetext[datetext.length-1]).join(" ");
		this.maxs = new Date(datetext);
		this.maxe = new Date(this.maxs.getTime() + 7*this.onehr_ms*24);
	},
	//activated on calendar refresh, runs selected options
	run: function(){
		if(this.valid_hook()){
			this.loadTags();
			if (this.on['color'] || this.on['sum']){
				//main grid
				eventgrid = document.getElementById("eventowner");
				this.matchNCode(eventgrid, false);
				//all day grid
				alldaygrid = document.getElementById("alldayeventowner");
				this.matchNCode(alldaygrid, true);
				//print totals
				if(this.on['sum']) this.div.childNodes[1].innerHTML=this.printTotals();
			}
		}
	},
	//loop through grid to color code and/or sum events
	matchNCode: function(grid, allday){
		var tag_exp = new RegExp( this.tag_pfx + "[a-z]+");
		if (grid != null) {
			for(var i = grid.childNodes.length-1; i>=0; i--){
				ttl_txt = grid.childNodes[i].childNodes[(allday ? 1 : 2 )].innerHTML;
				match = ttl_txt.match(tag_exp);
				if(!match) { 
					match = this.tag_pfx + this.matchSubTag(ttl_txt);
				}
				if(this.on['color']) this.colorEvent(match,grid.childNodes[i],allday);
				if(this.on['sum'] && !allday) this.sumEvent(match, grid.childNodes[i]);
			}
		}
	},
	matchSubTag: function(text){
		for (x in this.subtags){
			if(text.match(new RegExp(">[^><]*"+x+"[^><]*<","i"))){
				return this.subtags[x];
			}
		}
		return "none";
	},
	//color an event 
	colorEvent: function(match, reldiv, allday){
			top1 = reldiv.childNodes[0];
			top2 = reldiv.childNodes[1];
			chipbody = reldiv.childNodes[2];
			bottom1 = reldiv.childNodes[3];
			bottom2 = reldiv.childNodes[4];
			
			top1.style.backgroundColor=this.tags[match].border;
			top2.style.backgroundColor=this.tags[match].border;//or allday body div
			if (allday){
				chipbody.style.backgroundColor=this.tags[match].border;//bottom border
				return
			}
			chipbody.style.backgroundColor=this.tags[match].fill;
			chipbody.childNodes[0].style.borderColor=this.tags[match].border;
			chipbody.childNodes[0].childNodes[0].style.backgroundColor=this.tags[match].border; //header test background
			bottom1.style.backgroundColor=this.tags[match].fill;
			bottom1.style.borderColor=this.tags[match].border;
			bottom2.style.backgroundColor=this.tags[match].border;
	},
	//function to summarize time spent by tag
	sumEvent: function(match, reldiv){
		if(!this.tags[match].total) this.tags[match].total = 0;
		var s = this.dt_to_Date(this.events_hook(reldiv.id).start,true);
		var e = this.dt_to_Date(this.events_hook(reldiv.id).n,true);
		this.tags[match].total += ((this.maxe > e ? e : this.maxe)-(s > this.maxs ? s : this.maxs))/this.onehr_ms;
		this.maxe.setTime(s.getTime());
	},
	//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]);
		if(btnid == 'sum'){
			this.div.childNodes[1].style.display = (this.on[btnid] ? 'block' : 'none');
		}
		this.refresh_hook();
	},
	//insert the visual elements
	insert: function(loc){
		this.div = document.createElement('div');
		this.div.id = 'nb_'+this.name;
		this.div.style.paddingTop = '8px';
		this.div.style.paddingRight = '6px';
		this.div.innerHTML = 
		'<div id="nt_0" style="background-color: rgb(195, 217, 255); height: 21px;' +
				'-moz-border-radius-topleft: 4px; -moz-border-radius-topright: 4px;"></div>' +
		'<div id="nt_1" style="background-color: rgb(195, 217, 255); padding: 3px;"></div>' +
		'<div id="nt_2" style="background-color: rgb(195, 217, 255); height: 3px;' +
				'-moz-border-radius-bottomleft: 4px; -moz-border-radius-bottomright: 4px;"></div>';
		loc.parentNode.insertBefore(this.div, loc);
		title_div = single_xpath("//div[@id='"+this.div.id+"']//div[@id='nt_0']");
		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='CC:<img src="' + this.icons[btnid][this.on[btnid]] + '"/>';
		this.buttons[btnid].setAttribute('style','margin: 0.3em 0pt 0pt 0.3em;float:left;');
		this.buttons[btnid].setAttribute('onclick', this.name+'.toggle(\''+btnid+'\')');
		loc.appendChild(this.buttons[btnid]);
	},
	sortTotals: function(){
		this.orderedtags.sort(function(a,b){
			return ((a.total < b.total) ? 1 : ((a.total > b.total) ? -1 : 0));
		}); 
	},
	printTotals: function(){
	this.sortTotals();
		out = '<table style="background-color: rgb(255, 255, 255); width:100%;">';
		for (x in this.orderedtags){
			out += '<tr><td>' + this.orderedtags[x].name.slice(1) + ':</td><td> ' + this.orderedtags[x].total + '</td></tr>'
		}
		return out + '</table>';
	}
};

//////////////////////////////////////////////////////////////////////////////////////////
//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);
}