var xpath = { 'node' : { // any node i.e. thread or subthread 'desc' : 'Thread or sub-thread', 'p' : { // key to enter/browse 'Replies' collection 'desc' : 'Replies', xpath : function () { return "//tr[@class='reply']" }, 'l' : { 'desc' : 'go to node title links', xpath : function (i) { return }, }, 'b' : { 'desc' : 'go to node body links', xpath : function (i) { ... }, }, }, } }; #### 'id-3628' : { 'desc' : 'Newest Nodes', 'p' : { 'desc' : 'Collections of Postings', xpath : function () { return "//html/body/center/table/tbody/tr/td[1]/h3/a" }, 'hl' : function (t) { return t.parentNode }, // different highlighting 'l' : { // list of postings - left column 'desc' : 'posting titles', xpath : function (i) { return "//html/body/center/table/tbody/tr/td[1]/table["+i+"]/tbody/tr/td[1]/a" }, 'sticky': 'u', }, 'u' : { // list of users - right column 'desc' : 'authors', xpath : function (i) { return "//html/body/center/table/tbody/tr/td[1]/table["+i+"]/tbody/tr/td[2]/a" }, 'sticky': 'l', }, } }, #### // $Id: keypress.js,v 0.6 2008/07/27 12:16:05 shmem Exp $ // . // Key Driven PerlMonks // For [blazar] and all ye monks function $ (id) { return document.getElementById(id) } function xq (query) { return document.evaluate(query,document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null); } function xl (query) { var r = xq(query); var a = new Array; for ( var i=0 ; i < r.snapshotLength; i++ ) a.push(r.snapshotItem(i)); return a; } // globals var context; // current context var cursnum; // current prefix number (as string) var curchild; // current child var curindex; // current index into collection var hl = 'border'; // how to highlight var hc = '1px solid #F00'; // highlight attributes var cursor = new Object; // all of PerlMonks (well, some day, hopefully...) var xpath = { 'node' : { // any node i.e. thread or subthread 'desc' : 'Thread or sub-thread', 'p' : { 'desc' : 'Replies', xpath : function () { return "//tr`[@class='reply']" }, 'hl' : function (t) { return t.childNodes`[0] }, 'sticky' : 'r', 'l' : { 'desc' : 'go to node title links', xpath : function (i) { var j = i * 2; return "//table`[@id='replies_table']/tbody/tr`["+j+"]/td//a`[@href]" }, }, 'b' : { 'desc' : 'go to node body links', xpath : function (i) { var j = i * 2 + 1; return "//table`[@id='replies_table']/tbody/tr`["+j+"]/td//a`[@href]" }, }, }, 'r' : { 'desc' : '`[reply] link', xpath : function (i) { return "//table/tbody/tr/td`[1]/form/center`[1]/table`[@id='replies_table']/tbody/tr/td`[2]/font/a" }, 'sticky' : 'p', //html/body/center/table/tbody/tr/td`[1]/form/center`[1]/table`[@id='replies_table']/tbody/tr`[5]/td`[2]/font/a }, 'C' : { 'desc' : 'Comment on', xpath : function () { return "//table`[@id='replies_table']/tbody/tr`[1]/th/a" }, }, }, 'id-131' : { 'desc' : 'The Monastery Gates', 'p' : { 'desc' : 'Front-paged Posts', xpath : function () { return "//tr`[@class='post_head']" }, 'sticky' : 'rl', 'hl' : function (t) { return t.childNodes`[1] }, }, 'r' : { 'desc' : 'Offer your reply', xpath : function () { return "//td`[2]/a/font" }, 'sticky' : 'pl', }, 'l' : { 'desc' : 'Link to thread', xpath : function () { return "//td/table/tbody/tr/td`[1]/a`[@id]"}, 'sticky' : 'rp', }, }, 'id-479' : { 'desc' : 'SoPW, Meditations, PM Discussion, ...', 'p' : { 'desc' : 'Questions', xpath : function () { return "//tr`[@class='post_head']" }, 'sticky' : 'rl', 'hl' : function (t) { return t.childNodes`[1] }, }, 'r' : { 'desc' : 'Link `[Offer your reply]', xpath : function () { return "//td`[2]/a/font" }, 'sticky' : 'pl', }, 'l' : { 'desc' : 'Link to thread', xpath : function () { return "//td/table/tbody/tr/td`[1]/a`[@id]"}, 'sticky' : 'rp', }, }, 'id-3628' : { 'desc' : 'Newest Nodes', 'p' : { 'desc' : 'Collections of Postings', xpath : function () { return "//html/body/center/table/tbody/tr/td`[1]/h3/a" }, 'hl' : function (t) { return t.parentNode }, 'l' : { // list of postings - left column 'desc' : 'posting titles', xpath : function (i) { return "//html/body/center/table/tbody/tr/td`[1]/table`["+i+"]/tbody/tr/td`[1]/a" }, 'sticky': 'u', }, 'u' : { // list of users - right column 'desc' : 'authors', xpath : function (i) { return "//html/body/center/table/tbody/tr/td`[1]/table`["+i+"]/tbody/tr/td`[2]/a" }, 'sticky': 'l', }, } }, 'id-9066' : { 'desc' : 'Best Nodes', 'p' : { 'desc' : 'Collections of Postings', xpath : function () { return "/html/body/center/table/tbody/tr/td/h4" }, 'l' : { 'desc' : 'posting titles', xpath : function (i) { return "/html/body/center/table/tbody/tr/td`[1]/table`["+i+"]/tbody/tr/td`[2]/a" }, 'sticky' : 'u', }, 'u' : { 'desc' : 'authors', xpath : function (i) { return "/html/body/center/table/tbody/tr/td`[1]/table`["+i+"]/tbody/tr/td`[3]/a" }, 'sticky' : 'l', }, }, }, 'n' : { // nodelets 'desc' : 'Nodelets', xpath : function () { return "//table`[@id='nodelet_container']/tbody/tr`[1]/th" }, 'l' : { 'desc' : 'links in nodelet', xpath :function (i) { return "//table`[@id='nodelet_container']/tbody`["+i+"]/tr/td//a" }, }, }, 'T' : { 'desc' : 'Title Bar', xpath : function () { return "/html/body/table`[@id='titlebar-top']/tbody/tr/td`[2]/font/span/a" }, }, 'R' : { 'desc' : 'root & parent link (if applicable)', xpath : function () { return "/html/body/center/table/tbody/tr/td`[1]/form/div`[2]/p`[1]/a" }, }, 'c' : { 'desc' : 'Chatterbox', xpath : function () { return "//input`[@id='talkbox']" }, focus : function () { setFocus(talkbox) }, // chatterbox }, 't' : { 'desc' : 'Composition textbox (if any)', // xpath : function () { return "//table[3]/tbody/tr[2]/td//input[@name='node']" }, xpath : function () { return "//textarea[contains(@name,'doctext')]" }, focus : function () { setFocus(context`['colls']`[0]) }, }, }; var bodyid = xq('//body').snapshotItem(0).id; // nodes that resemble SoPW (#479), i.e. main sections var javascriptisstupid = new Array(480,1040,1044,1590,1597,21144,23771); //for (i in Array(480,1040,1044,1590,1597,21144,23771)) { for (i in javascriptisstupid) { var j = javascriptisstupid`[i]; if(bodyid == 'id-' + j) xpath`['id-' + j] = xpath`['id-479']; } var context = xpath`[bodyid] || xpath`['node']; // these are present in all nodes... context`['T'] = xpath`['T']; // Monkbar context`['R'] = xpath`['R']; // link to root|parent node context`['n'] = xpath`['n']; // nodelets context`['c'] = xpath`['c']; // Chatterbox // ...well, except these context`['t'] = xpath`['t']; // Composition textbox title var root = context; // save context root // keys for all contexts and subcontexts (where apropriate) var keys = { 'j' : { 'desc' : 'move down', value : 1, }, 'k' : { 'desc' : 'move up', value : -1, }, '+' : { 'desc' : 'vote ++', func : function () { vote(0) }, }, '0' : { 'desc' : 'reset vote', func : function () { vote(1) }, }, '-' : { 'desc' : 'vote --', func : function () { vote(2) }, }, 'v' : { 'desc' : 'jump to "vote!" button', func : function () { document.forms`[1]`['sexisgreat'].focus() }, }, 'h' : { 'desc' : 'Hide the body of a post', func : hideBody, }, 'm' : { 'desc' : 'go to page top', func : function () { window.scroll(0,0); if (curchild) curchild.style`[hl] = curchild`['oldstyle']; context = root } } }; // scroll element to top of page function setScroll (i) { var child = context`['coll']`[i]; if (! child) return; var helem = child; if (context`['hl']) helem = context`['hl'](child); if (curchild) { // unhighlight curchild.style`[hl] = curchild`['oldstyle']; } helem`['oldstyle'] = helem.style`[hl]; helem.style`[hl] = hc; if(child.nodeName == 'A') child.focus(); else if (child.parentNode.nodeName == 'A') child.parentNode.focus(); else window.scroll(0,getOffset(child)); curchild = helem; } // cast vote on a post in collection // XXX use xpath proper function vote(n) { var t = curchild; var nm; if (! t || context['desc'] == 'Thread or sub-thread') { // HACK var id = 'vote__'+bodyid.split('-')[1]; if(document.forms[1][id]) document.forms[1][id][n].checked = 'checked'; return; } if (t.childNodes[0]) // post + replies var nm = t.childNodes[0].name; if (! nm) { // section page, e.g. meditations try { nm = t.childNodes[1].childNodes[1].href; nm = nm.slice(nm.indexOf('=')+1); } catch (ex) { nm = bodyid.split('-')[1]; if(! document.forms[1]['vote__'+nm]) nm = ''; } if(! nm) { // HACK try { nm = curchild.childNodes[1].href.split('=')[1]; } catch (ex) {} } } var id = 'vote__' + nm; if(document.forms[1][id]) document.forms[1][id][n].checked = 'checked'; } function setFocus (t) { if (! t) return; document.onkeypress = ''; t.onblur = function () { document.onkeypress = keyDriven }; t.focus(); } // for threads function hideBody () { if (! curchild) return; var n = curchild.parentNode.nextSibling.nextSibling; var v = n.style.display; if (v == 'none') n.style.display = 'table-row'; else n.style.display = 'none'; } // get textareas, text inputs to disable keyDriven var talkbox = document.getElementById('talkbox'); // get offset of an element function getOffset (foo) { if (! foo) return; var offset = 0; if (foo.nodeName != 'A') offset = foo.offsetTop; while(foo.parentNode != foo) { if(foo.parentNode) { var p = foo.parentNode; var o = p.offsetTop; foo = p; if (p.nodeName == 'BODY') break; if (p.nodeName == 'CENTER') continue; // if (p.nodeName == 'TD') continue; if (p.nodeName == 'A') continue; if (p.nodeName == 'TR') continue; if (p.nodeName == 'TBODY') continue; if (p.nodeName == 'FORM') continue; offset += p.offsetTop; } else break; } return offset; } // Event handler function keyDriven (evt) { evt = evt || window.event; // don't mess up with the browser shortcuts if (evt.ctrlKey) return; if (evt.modifiers) if (evt.modifiers == 2) return var k = evt.keyCode ? evt.keyCode : evt.charCode ? evt.charCode : evt.which; var s = String.fromCharCode(k); var num = parseInt(cursnum,10); var found = 0; if (! num) num = 0; if (k > 47 && k < 59) { // 0 - 9 if ( cursnum || s != '0') {// leading 0 not allowed - used elsewhere. cursnum += s; return; } } var rel = 0; // select context if(s == '?') { s = 'Current context: '+context`['desc']+"\n\n"; for (var k in context) { if (k.length == 1) { s += k +' - '+ context`[k]`['desc'] + "\n"; if (context`[k]`['sticky']) { ary = context`[k]`['sticky'].split(''); s += ' index linked with key(s) ' + ary.join(', ') + "\n"; } } } if (context.parent) { for (var k in context.parent) { if (k.length == 1) s += k +' - '+ context.parent`[k]`['desc']+"\n"; } } s+= "\nGlobal keys:\n\n"; for (var k in keys) s += k + ' - ' + keys`[k]`['desc'] + "\n"; s += "\nMovement commands may be preceeded with a number,\n" + "e.g. 12j - go 12 items down\n"; alert(s); } else if (keys`[s]) { // global browsing key pressed var n = 0; if(keys`[s].value) { rel = 1; n = keys`[s].value; if(num && n) num *= n; else num = n found = 1; } else { try { keys`[s].func(); } catch (ex){ alert("invocation of\n"+keys`[s]+"\nfailed:\n"+ex); } } } else if (context`[s]) { // sub-context or function try { context`[s](); } catch (ex) { context`[s].parent = context; var i = 1; if (context`['index']) i = context`['index']+1; context = context`[s]; context`['coll'] = xl(context.xpath(i)); found = 1; } } else if (context.parent) { if (context.parent`[s]) { if(context.parent`[s] == context) { // next item in collection if(!num) num = 1; rel = 1; } else { // adjacent collection context.parent context.parent`[s].parent = context.parent; if (context`['sticky']) { var ary = context`['sticky'].split(''); for (var i in ary) { //alert(ary`[i]); if(ary`[i] == s) { context.parent`[s]`['index'] = context`['index']; } } } var i = 1; if (context.parent`['index']) i = context.parent`['index']+1; context = context.parent`[s]; context`['coll'] = xl(context.xpath(i)); } found = 1; } else if (context.parent.parent) { if (context.parent.parent`[s]) { context.parent.parent`[s].parent = context.parent.parent; context = context.parent.parent`[s]; found = 1; } } } if (! found) { // invalid key pressed | key not defined if (k < 48 || k > 58) // reset number string cursnum = ''; return; } if (! context`['index']) context`['index'] = 0; if (! context`['coll']) { var i = 1; if (context.parent) { i = context.parent`['index']+1; // alert(context.parent`['desc']+' index = '+context.parent`['index']); } context`['coll'] = xl(context.xpath(i)); } var coll = context`['coll']; var index = context`['index']; if (rel) num += index; else num = index; if (num > context`['coll'].length-1) num = context`['coll'].length-1; if (num < 0) num = 0; setScroll(num); context`['index'] = num; if (context.focus) setFocus(curchild); if (k < 48 || k > 58) // reset number string cursnum = ''; } // get all textareas and disable keyDriven while they have focus. // XXX use xpath for that function setSuspendKeyMode (t) { t.onblur = function () { document.onkeypress = keyDriven }; t.onfocus = function () { document.onkeypress = '' }; } function applyFuncOnTree(e,f,tag,type) { if (e.tagName == tag) { if(type != '') { if (e.type == type) f(e); } else { f(e); } } if (e.childNodes) { for(var i = 0; i##