root > codebin > yumyum.user.js

yumyum.user.js

text/javascript, 25153 bytes (load raw)
// Yumyum, UI enhancements for del.icio.us
// Copyright: Daniel Kinzler, brightbyte.de, 2007
// Version: 0.3, 20070319
// License: GPL
//
// ==UserScript==
// @name          yumyum
// @namespace     http://brightbyte.de/gm/
// @description   bulk-editing for your del.icio.us account
// @include       http://del.icio.us/*
// ==/UserScript==

//FIXME: when checking many link to the same server, insert a delay; otherwise, timeouts may occur; -> group work-queue by target host.
//TODO: process items in-sequence (xpath seems to return the snapshot in a strange order?!)
//TODO: use Their code to make tag suggestions in input field
//TODO: play nicely with bottom bar. if there are *few* links, the bar is pushed off-screen.

var yumyumDebug = 0; // 0 = no debug, 10 = trace (live), 20 = late intercept (simulate), 30 = early intercept (check)
var yumyumDelay = 2 * 1000; // 2 seconds delay between requests (not counting response time)
var yumyumLinkCheckDelay = 500; // 0.5 seconds delay between requests when checking URLs. Ugly workaround, should use by-host grouping.
var yumyumLinkCheckTimeout = 23 * 1000; // 23 seconds before giving up
var yumyumDepink = true; // apply depinkification to "saved by x other people"

////////////////////////////////////////////////////////////////////////////////////////////////////////

var yumyumAPI = "https://api.del.icio.us/v1/";
var yumyumURI = 'http://brightbyte.de/gm/yumyum';
var yumyumUA = yumyumURI + ' using Greasemonkey on ' + navigator.userAgent;

////////////////////////////////////////////////////////////////////////////////////////////////////////

var yumyumUser = null;
var yumyumHome = false;
var yumyumAtHome = false;

var yumyumJobs = [];
var yumyumActive = false;
var yumyumEnabled = true;

var yumyumControls = '<div class="yumyum-controls" id="yumyum-ctrl{n}" style="background-color:#DDDDFF; border:1px solid #888888; padding:0.5ex; margin:0.5ex; font-size:80%;">'
+ ' <div style="float:right; font-size:72%; text-align:right; background-repeat:no-repeat; background-position:right center;" id="yumyum-ctrl-deco{n}">[<a id="yumyum-toolbox-toggle{n}" href="javascript:void(0)">^</a>]'
+ '                                         <br/><a href="'+yumyumURI+'">YumYum</a></div>'
+ ' <button id="yumyum-select{n}" title="select all">select</button> <button id="yumyu-unselect{n}" title="unselect all">unselect</button> all | '
+ ' <button id="yumyum-check{n}" title="check selected links">check</button> <button id="yumyum-delete{n}" title="DELETE selected posts" onmouseover="this.style.backgroundColor=\'#AA3333\'" onmouseout="this.style.backgroundColor=null">delete</button>'
+ ' <button id="yumyum-private{n}" title="conceal (unshare) selected posts">conceal</button> <button id="yumyum-public{n}" title="share selected posts">share</button> selected | '
+ ' <button id="yumyum-add{n}" title="add tags to the selected posts">add</button> <button id="yumyum-remove{n}" title="remove tags from the selected posts">remove</button>'
+ ' <input id="yumyum-ctrl{n}-tags" type="text" class="tags" title="tags to add or remove"/> tags'
//+ ' <input id="yumyum-ctrl{n}-tags" type="text" class="tags" onfocus="showSuggest()" onblur="hideSuggest()" onkeypress="keypress()" onkeyup="keyup()" onkeydown="keydown()" /> tags'
+ ' <!--<button id="yumyum-fix{n}">fix</button>-->'
+ '</div>';

function yumyumInitControls(n) {
        document.getElementById('yumyum-toolbox-toggle'+n).addEventListener('click', function() { yumyumSetToolboxVisible(true); }, false);

        document.getElementById('yumyum-select'+n).addEventListener('click', function() { yumyumSelect(true); }, false);
        document.getElementById('yumyu-unselect'+n).addEventListener('click', function() { yumyumSelect(false); }, false);

        document.getElementById('yumyum-check'+n).addEventListener('click', function() { yumyumCheckLinks(); }, false);
        document.getElementById('yumyum-delete'+n).addEventListener('click', function() { yumyumDelete(); }, false);
        document.getElementById('yumyum-private'+n).addEventListener('click', function() { yumyumShare("no"); }, false);
        document.getElementById('yumyum-public'+n).addEventListener('click', function() { yumyumShare("yes"); }, false);

        var field = document.getElementById('yumyum-ctrl'+n+'-tags');
        document.getElementById('yumyum-add'+n).addEventListener('click', function() { yumyumMangleTags(field, false); }, false);
        document.getElementById('yumyum-remove'+n).addEventListener('click', function() { yumyumMangleTags(field, true); }, false);

        var fix = document.getElementById('yumyum-fix'+n);
        if (fix) fix.addEventListener('click', function() { yumyumDecorateAllItems(); }, false);
}


function yumyumTrace(msg) {
        if (yumyumDebug <= 0) return;
        if (GM_log) GM_log(msg);
        //if (yumyumDebug >= 20) document.body.innerHTML = msg + "<br/>" + document.body.innerHTML;
}

function yumyumDelete () {
        mangler = function( post ) {
                return { 'url': post['url'] }; //only use url, ignore other properties
        };

        yumyumRun('delete', mangler, "\n****** DELETE ******\n them");
}

function yumyumCheckLinks(url, descr, e) {
        yumyumRun('checklinks', null, "check their URLs");
}

function yumyumShare(shared) {
        mangler = function( post ) {
                if (post['shared'] == shared) return null; //skip item
                post['shared'] = shared;
                return post;
        };

        yumyumRun('add', mangler, shared == "yes" ? "SHARE them" : "CONCEAL them");
}


function yumyumMangleTags(field, remove) {
        var v = field.value.replace(/^ +| +$/, '').replace(/  +/, ' '); //notemalize spaces
        var tt = v == "" ? [] : v.split(' ');

        if (tt.length == 0) {
                alert("no tags specified!");
                return;
        }

        mangler = function( post ) {
                var t = post['+tagarr'];

                var nt = t.filter( function(x) { return tt.indexOf(x) < 0; } ); //note: always remove, to avoid duplicates
                if (!remove) nt = nt.concat(tt);

                if (nt.length == t.length) return null; //nothing to do!

                post['+tagarr'] = nt;
                post['tags'] = nt.join(' ');
                return post;
        };

        yumyumRun('add', mangler, ( remove ? "REMOVE" : "ADD" ) + " these tags: " + tt.join(' '));
}

function yumyumRun(command, mangler, desc) {
        if (yumyumActive) {
                alert("YumYum is still busy. Please be patient!");
                return;
        }

        var posts = yumyumCollect();
        if (posts.length == 0) {
                alert("no posts selected!");
                return;
        }
        else {
                if (!confirm(posts.length + " posts selected.\nDo you want to " + desc + "?"))
                        return;
        }

        var cmd = yumyumAPI + "posts/" + command + "?";
        var mth = 'GET';

        for(var i=0; i<posts.length; i++) {
                var p = posts[i];

                var a = {};
                for(var k in p) a[k] = p[k];
                if (mangler) a = mangler(a);
                if (!a) continue; //mangler returned null, so skip it

                var u = cmd + "&" + yumyumArgs(a);
                var headers = {};
                var timeout = null;
                var delay = yumyumDelay;

                if (command == 'checklinks') {
                        //mth = 'HEAD';
                        u = p['url'];
                        headers['Range'] = '0-1'; //one byte is enough
                        headers['If-Modified-Since'] = new Date().toUTCString(); //probably not modified since now.
                        timeout = yumyumLinkCheckTimeout;
                        delay = yumyumLinkCheckDelay;
                }

                if (yumyumDebug >= 30) {
                        yumyumTrace(mth+" "+u);
                }
                else {
                        var job = {
                                'command': command,
                                'method': mth,
                                'action': u,
                                'headers': headers,
                                'data': a,
                                'item': p['+item'],
                                'name': p['url'],
                                'url': p['url'],
                                'key': p['+item'].id,
                                'timeout': timeout,
                                'delay': delay,
                        };

                        yumyumSchedule(job);
                }
        }
}

function yumyumSchedule(job) {
        yumyumJobs.push(job);

        if (!yumyumActive) {
                yumyumTrace("activating worker");
                yumyumActive = true;
                yumyumRunNextJob();
        }

        yumyumSetEnabled(false);
}

/*
function yumyumCall(job){
        alert("YumYUm!");
        type = 'edit';
        var post = job['item'];
        var data = getPostData(post);
        if (!data.link) return true;
        var form = makePostForm(data, post, type);

        alert(data);
        alert(form);
}*/


function yumyumCall(job) {
        var item = job['item'];
        var complete = false;
        //var throbber = job['throbber'];

        var callback = function(response) {
                if (complete) {
                        yumyumTrace("WARNING: job "+job['name']+" was already canceled by watchdog!");
                        if (job['command']!='checklinks') return; //in checklinks-mode, accept the response anyway. may look odd, but no har is done.
                }

                //item.removeChild(throbber);
                complete = true;
                item.style.backgroundColor = job['oldbg'];
                item.style.backgroundImage = null;
                yumyumApplyResponse(job, response);
        }
       
        if (yumyumDebug >= 20) {
                yumyumTrace("SIMULATING "+job['method']+" "+job['action']);

                response = {
                        readyState : 4,
                        status : 200,
                        statusText : 'OK',
                        responseHeaders : [],
                        responseText : '<result code="done"/>',
                };

                callback(response);
        }
        else {
                yumyumTrace(job['method']+" "+job['action']);

                var headers = job['headers'];
                headers['User-Agent'] = yumyumUA;

                var request = new GM_xmlhttpRequest({
                        'method': job['method'],
                        'url': job['action'],
                        'headers': headers,
                        'onload': callback,
                        'onerror': callback, //doesn't handle TCP errors :(  ...or any errors, it seems.
                });
        }       

        if (job['timeout']) {
                yumyumTrace("starting watchdog with "+job['timeout']+"ms delay");

                var dog = function() {
                        if (complete) return;

                        response = {
                                readyState : -1, //error state
                                status : 510,    //timeout
                                statusText : 'Timeout',
                                responseHeaders : [],
                                responseText : '<result code="timeout"/>',
                        };
       
                        callback(response);
                };

                window.setTimeout(dog, job['timeout']);
        }
}

function yumyumRunNextJob() {
        yumyumTrace("job queue: " + yumyumJobs.length);

        job = yumyumJobs.pop();
        if (!job) {
                yumyumActive = false;
                yumyumSetEnabled(true);
                return;
        }

        yumyumTrace("Running job: " + job['name']);

        var item = job['item'];
        job['oldbg'] = item.style.backgroundColor;
        item.style.backgroundColor = '#CCCCCC';
        item.style.backgroundImage = 'url(chrome://browser/skin/Throbber.gif)';
        item.style.backgroundRepeat = 'no-repeat';
        item.style.backgroundPosition = 'right center';

        //item.insertBefore(throbber, item.firstChild);

        try {   
                yumyumCall(job);
        }
        finally {
                yumyumTrace("setting next timer");
                window.setTimeout(yumyumRunNextJob, job['delay']);
        }
}

function yumyumArgs(args) {
        var a = '';
        var k;
        for (k in args) {
                var v = args[k];
                if (v==null) continue;
                if (typeof(v)!="string") continue;
                if (v.match(/^\+/)) continue;

                if (a!='') a+= "&";
                a+= k;
                a+= "=";
                a+= encodeURIComponent(v);
        }
        return a;
}

function yumyumSetEnabled(enabled) {
        yumyumTrace("Enabled: " + (enabled ? "yes" : "no"));
        yumyumEnabled = enabled;

        yumyumSetControlsEnabled(1, enabled);
        yumyumSetControlsEnabled(2, enabled);
        yumyumSetControlsEnabled(3, enabled);
}

function yumyumSetControlsEnabled(n, enabled) {
        document.getElementsByTagName("body")[0].style.backgroundColor = (enabled || !yumyumActive ? 'inherit' : '#F0F0F0');
        document.getElementById("main").style.backgroundColor = (enabled || !yumyumActive ? 'inherit' : '#F0F0F0');

        //document.getElementById("yumyum-ctrl"+n).style.backgroundColor = (enabled ? '#EEEEEE' : 'blue');
        document.getElementById("yumyum-ctrl-deco"+n).style.backgroundImage = (enabled || !yumyumActive ? null : 'url(chrome://browser/skin/Throbber.gif)');

        document.getElementById('yumyum-select'+n).disabled= !enabled;
        document.getElementById('yumyu-unselect'+n).disabled= !enabled;

        document.getElementById('yumyum-check'+n).disabled= !enabled;
        document.getElementById('yumyum-delete'+n).disabled= !enabled;
        document.getElementById('yumyum-private'+n).disabled= !enabled;
        document.getElementById('yumyum-public'+n).disabled= !enabled;

        var tags = document.getElementById('yumyum-ctrl'+n+'-tags');
        tags.disabled= !enabled;
        if (enabled) tags.value = ' ';

        document.getElementById('yumyum-add'+n).disabled= !enabled;
        document.getElementById('yumyum-remove'+n).disabled= !enabled;

        var fix = document.getElementById('yumyum-fix'+n);
        if (fix) fix.disabled= !enabled;
}

function yumyumApplyResponse(job, response) {
        yumyumTrace("Apply: " + job['name'] + " [" + response.status + "] " + response.responseText.substring(0,127));

        var xml = null;
        var code = null;

        if (job['command'] == 'checklinks') {
                var desc =  document.evaluate('./*[@class="desc"]/a', job['item'], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);

                if (response.status == 200 || response.status == 304) {
                        desc.style.color = 'green';
                }
                else {
                        desc.style.color = 'red';
                }

                return;
        }
       
        if (response.status == 200) {
                var parser = new DOMParser();
                xml = parser.parseFromString(response.responseText, "application/xml");

                var r = xml.evaluate('/result', xml, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

                if (!r) code = 'bad response: ' + response.responseText;
                else {
                        r = r.snapshotItem(0);
                        code = r.getAttribute('code');
                }
        }
        else {
                //TODO: handle 503 (Blocked/overloaded) specially
                code = "HTTP "+response.status+" "+response.statusText;
                document.cookie = 'yumyum_ping=error'; //NOTE: force destroy cookie, to re-force ping
        }

        var item = job['item'];

        if (code != 'done') {
                var msg = document.createElement('div');
                msg.className = 'yumyum-error';
                msg.style.color = 'red';
                msg.appendChild( document.createTextNode('failed: '+code) );
                item.appendChild(msg);
                return;
        }

        //all is well
        if (job['command'] == 'delete') {
                item.parentNode.removeChild(item); //throw away.
        }
        else {
                yumyumUpdateItem(job['item'], job['data']);
        }
}

function yumyumUpdateItem(item, data) {
        var desc =  document.evaluate('./*[@class="desc"]/a', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
        var notes = document.evaluate('./*[@class="notes"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
        var meta =  document.evaluate('./*[@class="meta"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
        var tags =  document.evaluate('./*[@class="meta"]/*[@class="tag"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var private =  document.evaluate('./*[@class="meta"]/*[@class="private"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
        var date =  document.evaluate('./*[@class="meta"]/*[@class="date"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);

        /*
        if (data['url']) desc.href = data['url']; //FIXME: update edit/delete links (and key, too?)

        if (data['description']) {
                while (desc.firstChild) desc.removeChild(desc.firstChild);
                desc.appendChild(document.createTextNode(data['description']));
        }

        if (data['notes']) {
                if (!notes) {
                        notes = document.createElement('p');
                        notes.className = 'notes';
                        notes.appendChild( document.createTextNode(data['notes']) );
                }

                while (desc.firstChild) desc.removeChild(firstChild);
                desc.appendChild(document.createTextNode(data['description']));
        }
        */



        if (data['+tagarr']) {
                var j = 0;
                while ( (e = tags.snapshotItem(j++) ) !=null ) {
                        e.parentNode.removeChild(e);
                        //TODO: remove trainling spaces too
                }

                if (meta.firstChild.nodeName != '#text' || meta.firstChild.nodeValue != 'to ') //unstable - make sure the "to" text is there.
                        meta.insertBefore(document.createTextNode('to '), meta.firstChild);

                var before = meta.firstChild.nextSibling; //unstable - points between "to" and "..."

                var tt = data['+tagarr'];
                for (var i = 0; i<tt.length; i++) {
                        var t = tt[i];
                       
                        var a = document.createElement('a');
                        a.className = 'tag';
                        a.href = yumyumUser + "/" + encodeURIComponent(t);
                        a.appendChild( document.createTextNode(t) );

                        meta.insertBefore(a, before);
                        meta.insertBefore(document.createTextNode(' '), before);
                }
        }

        if (data['shared']) {
                //TODO: also add/remove "share"-command
                if (data['shared']=='no' && !private) {
                        var privateText = document.createElement('span');
                        privateText.style.color = 'red';
                        privateText.appendChild( document.createTextNode('not shared') );

                        private = document.createElement('span');
                        private.className = 'private';
                        private.appendChild( document.createTextNode('...') );
                        private.appendChild( privateText );

                        meta.insertBefore(private, date);
                }
                else if (data['shared']=='yes' && private) {
                        private.parentNode.removeChild(private);
                }
        }
}

/*
//reload item, just like they do... how? WOuld be an alternative to yumyumUpdateItem
function yumyumReloadItem(data) {
        var e = yumyumLoadItem(data['url']);
        yumyumDecorateItem(e);

        var item = data['item'];
        item.parentNode.rempaceChild(item, e);
}
*/


function yumyumGetItemData(item) {
        var boxes = document.evaluate('.//input[@class="yumyum-select"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var chk= boxes.snapshotItem(0);
        var u = decodeURIComponent(chk.name);

        var desc =  document.evaluate('./*[@class="desc"]/a', item, null, XPathResult.STRING_TYPE, null);
        var notes = document.evaluate('./*[@class="notes"]', item, null, XPathResult.STRING_TYPE, null);
        var tags =  document.evaluate('./*[@class="meta"]/*[@class="tag"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var private =  document.evaluate('./*[@class="meta"]/*[@class="private"]', item, null, XPathResult.STRING_TYPE, null);
        var date =  document.evaluate('./*[@class="meta"]/*[@class="date"]', item, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

        var description = desc.stringValue;
        var extended = notes.stringValue;
       
        var tagarr = [];
        var j = 0;
        while ( (t = tags.snapshotItem(j++) ) !=null ) {
                tagarr.push(t.firstChild.nodeValue);
        }

        var shared = private.stringValue ? "no" : "yes";
        var dt = date.snapshotItem(0).title;

        var p = {
                "url": u,
                "description": description,
                "extended": extended,
                "+tagarr": tagarr,
                "tags": tagarr.join(' '),
                "shared": shared,
                "dt": dt,
                "+item": item,
        };

        return p;
}

function yumyumCollect() {
        var posts = [];

        var boxes = document.evaluate('//input[@class="yumyum-select"]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var i = 0;
        var e;
        while ( (e = boxes.snapshotItem(i++) ) !=null ) {
                if (!e.checked) continue; //note selected, ignore

                var item = e;
                while (item && !item.className.match(/(^| )post($| )/)) item = item.parentNode;
                if (!item) continue; //something is wrong
       
                var p = yumyumGetItemData(item);
                posts.push(p);
        }

        return posts;
}

function yumyumSelect(chk) {
        var boxes = document.evaluate('//input[@class="yumyum-select"]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var i = 0;
        var e;
        while ( (e = boxes.snapshotItem(i++) ) !=null ) {
                e.checked = chk;
        }
}

function yumyumZap(e) {
        while (e && !e.className.match(/(^| )post($| )/)) e = e.parentNode;
        if (e && e.parentNode) e.parentNode.removeChild(e);
}

function yumyumDecorateItem(e) {
        if (yumyumAtHome) {
                if (e.className && !e.className.match(/(^| )post($| )/)) return; //make sure there's something to decorate
                if (e.firstChild && e.firstChild.className == "yumyum-select") return; //already decorate
       
                var links = document.evaluate('.//*[@class="desc"]/a', e, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                var a = links.snapshotItem(0);
                if (!a) return;
               
                var url = a.href;
                var descr = a.firstChild.nodeValue; //unstable!
       
                var chk = document.createElement("input");
                chk.type = "checkbox";
                chk.className = "yumyum-select";
                chk.name = encodeURIComponent(url);
       
                e.insertBefore(chk, e.firstChild);
                e.id = e.getAttribute('key'); //for easy access. Why the hell do they use "key" anyway?!
       
                var divs = document.evaluate('.//*[@class="commands"]', e, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
                var cmds = divs.snapshotItem(0);
                if (!cmds) return;
       
                var btn = document.createElement("a");
                btn.href = "javascript:void(0)";
                btn.className = "yumyum-zap";
                btn.title = "ignore temporarily";
                btn.style.fontSize = "110%";
                btn.style.fontWeight = "bold";
                btn.style.color = "black ! important";
                btn.appendChild(document.createTextNode(">.<"));
                btn.addEventListener('click', function(ev) { yumyumZap(ev.target); }, false);
                cmds.appendChild( document.createTextNode("  ("));
                cmds.appendChild(btn);
                cmds.appendChild( document.createTextNode(")  "));
        }

        if (yumyumDepink) {
                var pop = document.evaluate('.//*[@class="pop"]', e, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
                if (pop) {
                        pop.style.backgroundColor = null;
                        var n = pop.firstChild.nodeValue.replace(/^saved by (\d+) other people$/, '$1');
                        if (n.match(/^\d+$/)) {
                                if (n > 10000) pop.style.fontSize = '130%';
                                else if (n > 1000) pop.style.fontSize = '120%';
                                else if (n > 100) pop.style.fontSize = '110%';
                                else if (n > 10) pop.style.fontSize = '100%';
                                else if (n > 0) pop.style.fontSize = '90%';
       
                                if (n > 0) pop.style.fontWeight = 'bold';
                        }
                }
        }
}

function yumyumDecorateAllItems() {
        var entries = document.evaluate('//li[starts-with(@class,"post")]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var i = 0;
        var e;
        while ( (e = entries.snapshotItem(i++) ) !=null ) {
                yumyumDecorateItem(e);
        }
}

function yumyumPingAPI() {
                if (document.cookie.match('(^|; *)yumyum_ping=ok(; *|$)')) {
                        return;
                }

                var u = yumyumAPI + 'posts/update';

                var error = function(details) {
                        yumyumTrace("ping failed, code "+details.status);
                        document.cookie = "yumyum_ping=failed";
                        yumyumSetEnabled(false);
                        alert("Failed to access del.icio.us API, YumYum will not work.\n(code: "+details.status+" "+details.statusText+")\n"+u);
                }

                var complete = function(details) {
                        document.cookie = "yumyum_ping=ok";
                        yumyumTrace("received ping response, code "+details.status);
                }

                //just load something, to trigger http auth
                var request = new GM_xmlhttpRequest({
                        method: 'GET',
                        url: u,
                        headers: {
                                'User-agent': yumyumUA,
                        },
                        onload: function(details) {
                                if (details.status==200) complete(details);
                                else error(details);
                        },

                        onerror: error,
                });
       
}

function yumyumSetToolboxVisible(visible) {
        document.getElementById('yumyum-ctrl1').style.display = visible ? 'none' : 'block';
        document.getElementById('yumyum-ctrl2').style.display = visible ? 'none' : 'block';
        document.getElementById('yumyum-ctrl3').style.display = visible ? 'block' : 'none';

        var exp = new Date();
        exp.setMonth( (exp.month +1) % 12);
        document.cookie = 'yumyum_toolbox=' + ( visible ? 'yes' : 'no' ) + '; expires=' + exp.toGMTString();
}

function yumyumInit() {
        yumyumTrace("init()");
        yumyumAtHome = false;

        if ( document.getElementById("yumyum-ctrl1") ) {  //already initialized
                yumyumTrace("init aborted: already initialized");
                return;
        }

        if (!GM_xmlhttpRequest) {
                yumyumTrace("init aborted: GM_xmlhttpRequest not found");
                alert('GM_xmlhttpRequest not found, yumyum will not work! Please update GreaseMonkey');
                return;
        }

        //find out the user name
        var links = document.evaluate('//*[@id="header-user-links"]//a', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var i = 0;
        while ( (a = links.snapshotItem(i++) ) !=null ) {
                yumyumUser = decodeURIComponent( a.href.replace(/^.*\/([^\/?]+)([?#].*)?$/, '$1') );
                if (yumyumUser != '') break;
        }

        if (!yumyumUser || yumyumUser=='') { //user name not detected. probably not logged in
                yumyumTrace("init aborted: not logged in");
                return;
        }

        yumyumHome = location.protocol + '//' + location.host + '/' + decodeURIComponent(yumyumUser);
        yumyumAtHome = ( location.href.indexOf(yumyumHome) == 0 || location.href.match(/^https?:\/\/[^\/]+\/search\/?\?.*&type=user/));

        if (!yumyumAtHome) { //we are on another user's turf
                yumyumTrace("init aborted: not your home");
                return;
        }

        //inject control panels
        var divs = document.evaluate('//*[@id="main"]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var main = divs.snapshotItem(0);
        if (main && main.className != 'postui') {
                main.innerHTML = yumyumControls.replace(/{n}/g, '3').replace(/ \| /g, '<br/>')
                                + yumyumControls.replace(/{n}/g, '1')
                                + main.innerHTML
                                + yumyumControls.replace(/{n}/g, '2');

                var box = document.getElementById('yumyum-ctrl3');
                box.style.position = 'fixed';
                box.style.top = '0.5ex';
                box.style.right = '0.5ex';
                box.style.width = '28%';
                box.style.border = '3px solid #888888';
                box.style.padding = '0.5ex';
                box.style.fontSize = '80%;';

                yumyumInitControls(1);
                yumyumInitControls(2);
                yumyumInitControls(3);

                var toggle = document.getElementById('yumyum-toolbox-toggle3');
                toggle.addEventListener('click', function() { yumyumSetToolboxVisible(false) }, false);
                toggle.removeChild(toggle.firstChild);
                toggle.appendChild(document.createTextNode('x'));

                var visible = document.cookie.match('yumyum_toolbox=yes');
                yumyumSetToolboxVisible(visible);
        }

        //listen for dom events, so we can decorate items comming from ajax calls
        var postlists = document.evaluate('//*[@class="posts"]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
        var i = 0;
        while ( (p = postlists.snapshotItem(i++) ) !=null ) {
                p.addEventListener('DOMNodeInserted', function(ev) { yumyumDecorateItem(ev.target); } , false);
        }

        //check API access (async)
        yumyumPingAPI();
}

yumyumInit();
yumyumDecorateAllItems();