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