Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eligrey/419226 to your computer and use it in GitHub Desktop.
Save eligrey/419226 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Reddit Keyboard Navigation
// @namespace http://andrewdupont.net/greasemonkey
// @description Navigate Reddit comments. Expand, collapse, and reply
// without having to pinpoint tiny links with your mouse.
// @author Andrew Dupont
// @include http://reddit.com/*
// @include https://reddit.com/*
// @include http://*.reddit.com/*
// @include https://*.reddit.com/*
// @run-at document-start
// ==/UserScript==
(function() {
var $ = window.jQuery;
var KEY = {
ESC: 27,
A: 65,
Z: 90,
R: 82,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
};
var MEANINGFUL_KEYS = [];
for (var i in KEY) {
MEANINGFUL_KEYS.push(KEY[i]);
}
var ACTIVE_CLASS = 'keyboard-active';
var MAX;
function _isMeaningfulEvent(event) {
var ret = $.inArray(event.keyCode, MEANINGFUL_KEYS);
var meaningful = (ret > -1);
return meaningful && !event.metaKey && !event.ctrlKey && !event.altKey;
}
function _init() {
$comments = $('.comment');
if ($comments.length === 0) return;
MAX = $comments.length - 1;
$(document).keydown(_keydown)
$(document).keyup(_keyup);
}
var $comments = null;
var $activeComment = null;
var _index = 0;
function _clamp(num, min, max) {
if (num > max) return max;
if (num < min) return min;
return num;
}
function _isExpandedComment($comment) {
return $comment.find('.noncollapsed').css('display') !== 'none';
}
function _isVisibleComment($comment) {
return $comment.height() > 0;
}
function _inTextarea(event) {
console.log('_inTextarea', event.target, event.target.nodeName);
return event.target.nodeName.toUpperCase() === 'TEXTAREA';
}
function _setActiveComment($comment) {
if ($activeComment) $activeComment.removeClass(ACTIVE_CLASS);
$activeComment = $comment;
$activeComment.addClass(ACTIVE_CLASS);
// If the index doesn't match up, do a search.
var $c = $($comments.get(_index));
if ($c !== $comment) {
_index = $.inArray($comment[0], $comments);
}
var top = $activeComment.offset().top;
document.body.scrollTop = top - 5;
}
function _moveToComment(delta) {
var $comment = $activeComment;
if (_index === 0 && !$activeComment) {
$comment = $($comments.get(_index));
} else {
_index = _clamp(_index + delta, 0, MAX);
$comment = $($comments.get(_index));
}
if (!_isVisibleComment($comment)) {
while ($comment) {
_index = _clamp(_index + delta, 0, MAX);
$comment = $($comments.get(_index));
if (_isVisibleComment($comment)) break;
}
}
if ($comment) _setActiveComment($comment);
return $comment;
}
function _moveToParent() {
var $comment = $($activeComment[0].parentNode.parentNode.parentNode);
if ($comment && $comment.hasClass('comment')) {
_setActiveComment($comment);
}
}
function _collapseActive() {
var elem = $activeComment.find('.noncollapsed a.expand')[0];
hidecomment(elem);
}
function _expandActive() {
var elem = $activeComment.find('.collapsed a.expand')[0];
showcomment(elem);
}
function _replyToComment() {
var elem = $activeComment.find('ul.flat-list.buttons li:last')[0];
console.log(elem);
reply(elem);
$activeComment.find('form.usertext textarea').focus();
}
function _voteOnComment(delta) {
var selector = delta > 0 ? ".arrow.up, .arrow.upmod" :
".arrow.down, .arrow.downmod";
$activeComment.find('.midcol:eq(0)').find(selector)[0].onclick();
}
var _doublePressFlag = false;
var _doublePressTimeout;
function _keydown(event) {
if (_inTextarea(event)) return;
// Intercept relevant events on keydown so that we also receive
// their keyup.
if (_isMeaningfulEvent(event)) return false;
}
function _keyup(event) {
if (_inTextarea(event)) return;
if (!_isMeaningfulEvent(event)) return;
// If you need to use the arrow keys for conventional purposes (e.g.,
// typing a reply), two quick presses of Escape will unload the script.
if (event.keyCode === KEY.ESC) {
if (_doublePressFlag) {
_teardown();
} else {
_doublePressFlag = true;
_doublePressTimeout = window.setTimeout(
function() { _doublePressFlag = false; }, 600);
}
return false;
}
switch (event.keyCode) {
case KEY.UP:
event.shiftKey ? _moveToParent() : _moveToComment(-1);
break;
case KEY.DOWN: _moveToComment(1); break;
case KEY.LEFT: _collapseActive(); break;
case KEY.RIGHT: _expandActive(); break;
case KEY.A: _voteOnComment(1); break;
case KEY.Z: _voteOnComment(-1); break;
case KEY.R: _replyToComment(); break;
}
return false;
}
function _teardown() {
window.clearTimeout(_doublePressTimeout);
$(document).unbind('keyup', _keyup);
$(document).unbind('keydown', _keydown);
$activeComment.removeClass(ACTIVE_CLASS);
}
window.addEventListener('load', _init, false);
var css = "." + ACTIVE_CLASS + "{" +
" border-color: #039 !important;" +
"}";
if (typeof GM_addStyle != "undefined") {
GM_addStyle(css);
} else if (typeof PRO_addStyle != "undefined") {
PRO_addStyle(css);
} else if (typeof addStyle != "undefined") {
addStyle(css);
} else {
var heads = document.getElementsByTagName("head");
if (heads.length > 0) {
var node = document.createElement("style");
node.type = "text/css";
node.appendChild(document.createTextNode(css));
heads[0].appendChild(node);
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment