http://qs1969.pair.com?node_id=700377

Well, I have bitten... maybe the following makes life a bit easier for our fellow monk blazar (and other keyboard-centric monks like me :-)

- the problem with key driven navigation via browser key defaults or HTML access keys is that these mechanisms don't provide for contexts and collections, e.g. the list of links on a page is a single flat list, which can be browsed with the <Tab> key, but navigation doesn't follow necessarily the page layout, if the order in which they were found at page loading is different. -

With XPath (thanks to Corion, who gave me the hint) it is easy to divide every PM Page into a tree of collections and lists, which can be browsed via JavaScript's document.onkeypress event handler.

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 <XPath expr selecting links based on 'Replies' collection index> }, }, 'b' : { 'desc' : 'go to node body links', xpath : function (i) { ... }, }, }, } };

(Key 'p' selects the 'Replies' section (or advances the cursor in it), 'l' selects the node title links; while inside 'node title links', 'p' brings you back to 'Replies'. A selected item (except links) will be relocated to the window top.)

Keying those on the page's body-id allows for tweaks for different pages:

'id-3628' : { 'desc' : 'Newest Nodes', 'p' : { 'desc' : 'Collections of Postings', xpath : function () { return "//html/body/center/table/tbody/t +r/td[1]/h3/a" }, 'hl' : function (t) { return t.parentNode }, // different hig +hlighting 'l' : { // list of postings - left column 'desc' : 'posting titles', xpath : function (i) { return "//html/body/center/table/tbod +y/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/tbod +y/tr/td[1]/table["+i+"]/tbody/tr/td[2]/a" }, 'sticky': 'l', }, } },

That's it, basically. The 'sticky' key lets link indexes of adjacent collections together, e.g. posting titles and their authors on Newest Nodes or Best Nodes. The descriptions are also used for an alert box for keys available in each section or collection.

The complete code ready to be pasted into Free Nodelet Settings (see Free Nodelet freed for other cool hacks):

// $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_N +ODE_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 "//tab +le`[@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/tb +ody/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_tab +le']/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/t +body/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/ta +ble/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/ta +ble/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/tb +ody/tr/td/h4" }, 'l' : { 'desc' : 'posting titles', xpath : function (i) { return "/html/body/center/ta +ble/tbody/tr/td`[1]/table`["+i+"]/tbody/tr/td`[2]/a" }, 'sticky' : 'u', }, 'u' : { 'desc' : 'authors', xpath : function (i) { return "/html/body/center/ta +ble/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_cont +ainer']/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/t +r/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//inpu +t[@name='node']" }, xpath : function () { return "//textarea[contains(@name,'doct +ext')]" }, 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) curch +ild.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 els +ewhere. 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 collec +tion 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`['i +ndex']; } } } var i = 1; if (context.parent`['index']) i = context.parent`['ind +ex']+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.pare +nt; 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.paren +t`['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<e.childNodes.length; i++) { applyFuncOnTree(e.childNodes`[i],f,tag,type); } } } // find all textarea elements and applyFuncOnTree on them for (var i = 0; i<document.forms.length; i++) { f = document.forms`[i]; applyFuncOnTree(f,setSuspendKeyMode,'TEXTAREA',''); applyFuncOnTree(f,setSuspendKeyMode,'INPUT','text'); } document.onkeypress = keyDriven;

Note: if you want to save the above code elsewhere to tweak it, and reference it via <script> tags, you have to unescape the brackets (`[)

But to save perlmonks a bit of precious bandwith and storage - I urge you to either paste the link

<script type="text/javascript" src="http://cruft.de/keypress.js"></scr +ipt>

into Free Nodelet Settings, which ensures you always have the newest version (and the latest breakages, too ;-) <update> - if you trust me, that is (I could steal your cookie) - or place the code on a webserver of your confidence, and let the file be served from there. </update>
Bug reports, suggestions, improvements (i.e. patches) and new page sub-hashes welcome!

Enjoy,

--shmem

_($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                              /\_¯/(q    /
----------------------------  \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

Replies are listed 'Best First'.
Re: Better keyboard-driven navigation, any? - yes... (cautions)
by tye (Sage) on Jul 27, 2008 at 18:42 UTC

    You don't have to escape the brackets (in the Free Nodelet) if you just put the javascript inside of an HTML comment.

    Be a bit cautious when putting large amounts of data into settings such as free nodelet, signature, node template, etc. All settings get encoded into text and stored in a node which will (at least as things are currently implemented) silently be truncated if the result is longer than 64kB, perhaps losing all of your settings.

    I've updated Free Nodelet Settings to report how much space is being used, including a note as to why that is important.

    You should disclaim that doing as you "urge" also means that anybody who does so is subject not just to bugs but also to potential maliciousness. For example, it isn't particularly hard to set up a web server such that keypress.js will rarely serve up a hacked version that forwards the person's PerlMonks cookie to you in a private message.

    Several potential ways to address such risks come to mind, with varying trade-offs. But I don't have time at the moment to go into those, so I just wanted to make sure people were aware of the risk, however small (given shmem's long history).

    - tye        

      Of course it is risky to load javascript from unknown servers, and even though that particular one is under my sway and I am the only one with root access, bad things might happen, worse than bugs - some evildoer in the hosting company... then, although I have not so bad a reputation inside the monastery, I could be a dork outside ;-)

      Pointing out the risk is fair, well done. The safest thing to do is to grab the source, review it and place it on a server of one's own trust; I updated the OP accordingly. - I myself included in my 'Free Nodelet' some JavaScript of Corion's craft, and to be honest, I only wrote this piece to be able to retaliate if he is ever going to steal my cookie... :-D

      --shmem

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Better keyboard-driven navigation, any? - yes...
by Gavin (Archbishop) on Jul 27, 2008 at 18:22 UTC

    shmen ++ a small recompense for an inordinate amount of work, I'm sure it will be much appreciated by others.

Re: Better keyboard-driven navigation, any? - yes...
by blazar (Canon) on Jul 28, 2008 at 08:43 UTC
    Well, I have bitten... maybe the following makes life a bit easier for our fellow monk blazar (and other keyboard-centric monks like me :-)

    I personally believe I should first of all thank you very much: indeed, I also received your /msg and I wanted to reply to it, but I'm doing it here instead. Specifically, I confirm that your code is already usable! (Whether it's also maintainable, which you also claim, is something I cannot judge...) Actually, I've been using it these days with experimental evidence of advantage over the "old" keyboard driven mechanisms, and in conjunction with them as well as -after all- some minimal caress to the touchpad! (Should I feel guilty? ;) But... there are issues! And I:

    1. somewhat regret you announced it so in a hurry;
    2. feel guilty (really, in this case) for possibly having pushed you to do so by being so slow to provide you with feedback.

    Hopefully in a few hours (I have something else, and very important to do first) I'll complete the review I started in my scratchpad. BTW: people, please don't go and check it now for it contains virtually no interesting info; I'll post an update to this very post when it will be finished.

    To put it forth, though: some of the issues may be regarded as feature requests or gotchas in terms of UI; but I believe that some others are actual bugs. Please be patient and wait...

    Update: I have completed the first draft of review. Other than in the scratchpad I'm also pasting it as a reply to shmem's own comment to this post, so that he can see the notification.

    --
    If you can't understand the incipit, then please check the IPB Campaign.
      I somewhat regret you announced it so in a hurry;

      That's not something you have to regret, but I - if so. But I don't. The earlier the prototype is out, the earlier bug reports or feature requests come in, which could question the overall design.

      So if it's public almost after start of writing, it doesn't happen that after finishing the audience says

      "not only is this all incomprehensible, but the ink is ugly and the paper is from the wrong kind of tree."

      And hey, you said it yourself - it's already usable ;-)

      --shmem

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

        I personally believe that since shmem's script has been here publicized, I'd better also publicly copy here my first draft of review on his code. This has taken some time due to my own lazyness and thus may not be completely consistent over time, but I'm confident that it mostly is. I'm copying it here out of my own scratchpad.


        I've tried the beast: all in all, I already like it!

        Key driven navigation must be turned off whenever a textarea is entered, or a text field. Sadly, there's no uniform id for composition textareas or title textboxes...

        Is this the case? If it were, then indeed it would be extremely annoying. But then I've tried e.g. to go to the node consideration text field, both with the hot key and scrolling with the mouse. Then I did not consider the node and move out of it say, with a Tab. And I could start using the keybindings again - seamlessly. Or did I misunderstood anything?

        To disable, enter document.onkeypress = ''; in a suitable javascript console (e.g.firebug). Oh.. that reminds me... Firefox only, I guess. Haven't checked IE yet.

        I believe that the javascript: pseudo-protocol may do. Again, FF has it: don't know 'bout IE, let alone other browsers.

        all sections:
        • p - go to posts / replies section
        • [...]
        • p - go to posts / replies section: at first I found this behaviour counter-intituitive. On a second thought, it still is counter-intuitive: (the difference being that on the first one my reaction was along the line of "this damn thing doesn't work!" while now I know "I have to press p first...") I have a list of nodes, and I do not necessarily view the first one as having a quite different nature from the replies. Specifically, the p key is useful and should stay, but if possible j should bring to the first reply as well, and k on the first reply should be an alias with m. If possible!
        • n - go to nodelets: hadn't even thought of that, but works fine for me!
        • c - set focus and jump to chatterbox entry box: ditto!
        • m - jump to main node: I have found this not to work when switching to some other navigation mode. I would expect that once I tab a few times or click in an "empty" point, it would do. By contrast, v always brings me to the vote button, which means that it should be roughly doable
        • ? - show alert box with key bindings: very good!
        main section:
        • + - set ++ vote for current post
        • [...]

        I'm giving you my impression in terms of UI:

        posts:
        • + - set ++ vote for current post: very good! One annoying detail with the partial keyboard driven navigation I was using before is that I left the radio button selected and while moving down I would occasionally change a ++ into a --. Of course the cure is simply a tab away or a click out of the button, which is, after all twice the effort, or 100% more. Yours is definitely clearer and faster.
        • - - set -- vote for current post: here, I'm afraid I must tell you of the first actual bug, although the behaviour has changed over the past few days. As of the last time I tried, i.e. this morning, - plainly doesn't work with an error alert. Yesterday . did the trick instead: I'm sure that this is in connection with me not having selected the null vote in my preferences. Today, pressing . doesn't vote -- but doesn't do anything either. I wonder if a solution is possible at all, that would work with both kinds of user settings...
        • . - set 0 vote for current post: ditto, I don't have it. But, as a feature request and if possible at all, I'd like it to clean the vote anyway.
        • v - set focus and jump to "vote!" button: this is also perfect; much faster than moving to in any other way I have access to.
        • t - jump to note composition text area: to be fair, I'm not sure what this is for.
        posts and nodelets:
        • j - down one post/nodelet
        • [...]
        • j - down one post/nodelet: in terms of UI, there's another gotcha similar to that with the main post. Precisely, one gets the feeling that repeatedly pressing j she will get "to the end." At some point, however, posts do not scroll down any more: of course that's when you've hit the last one. And you do realize it, but you have that fraction of second of psychological unesiness with it, if you know what I mean: I believe that hitting it one more time on the last node should either give a clear visual indication that there no more posts, or more simply and IMHO intuitively act as an alias for v. Vice versa, hitting k on v "should" bring back to the last reply. I'm also afraid to have spotted another possible bug. Namely, when for some reason I switch to some other navigation method (and I was forced to, because of the aforementioned difficulties with +, . and -) j and k ended up not moving between posts but between links within a post, and no way to "escape."
        • k - up one post/nodelet: ditto!
        • l - set focus on first link in current post/nodelet: good!
        • From there, access links with the <Tab> key: ISTR having had some difficulties with this too, within posts, not nodelettes, but as a general rule I've not toyed too much with it.
        posts:
        • r - set focus on reply link
        • [...]
        • r - set focus on reply link: I must say that while for v it is very sensible to only set focus on the vote button, because one may change his mind, I would consider a very good idea to have this key actually open the reply link in a new window/tab and switch to it. If one changes her mind, then she can always close it.
        --
        If you can't understand the incipit, then please check the IPB Campaign.
Re: Better keyboard-driven navigation, any? - yes...
by Argel (Prior) on Jul 28, 2008 at 19:24 UTC
    Thank you shmem!! This is a perfect example of the great community we already have established here at PerlMonks!

    I hope EvanCarroll reads this thread and starts to grasp why his recent node suggesting we use off-topic subjects such as movie reviews to "help build [the] community" was so misguided.

    Again, great job!

Re: Better keyboard-driven navigation, any? - yes...
by Anonymous Monk on Jul 29, 2008 at 18:28 UTC