8
9
require("content-buffer.js");
10
/* Initially based on the greasemonkey userscript "Reddit keyboard
11
* shortcuts", found on userscripts.org. But much has changed because of
11
define_variable("reddit_end_behavior", "stop",
12
"Controls the behavior of the commands reddit-next-link and "+
13
"reddit-prev-link when at the last or first link, respectively. "+
14
"Given as a string, the supported values are 'stop', 'wrap', "+
15
"and 'page'. 'stop' means to not move the highlight in any "+
16
"way. 'wrap' means to wrap around to the first (or last) "+
17
"link. 'page' means to navigate the buffer to the next (or "+
18
"previous) page on reddit.");
20
register_user_stylesheet(
23
"@-moz-document url-prefix(http://www.reddit.com/) {" +
25
" background-color: #bfb !important;" +
26
" border: 0px !important;"+
30
/* Scroll, if necessary, to make the given element visible */
31
function reddit_scroll_into_view (window, element) {
32
var rect = element.getBoundingClientRect();
33
if (rect.top < 0 || rect.bottom > window.innerHeight)
34
element.scrollIntoView();
38
/* Move select the next link down from the currently highlighted one.
39
* When the end of the page is reached, the behavior is controlled by
40
* the variable reddit_end_behavior.
14
var reddit_highlight_color = "#BFB";
17
function reddit_highlight(elem) {
18
elem.highlighted = true;
19
elem.style.backgroundColor = reddit_highlight_color;
22
function reddit_dehighlight(elem) {
23
elem.style.backgroundColor = elem.originalBackgroundColor;
24
elem.highlighted = false;
27
function reddit_toggleHighlight(elem) {
30
reddit_dehighlight(elem);
32
reddit_highlight(elem);
36
function reddit_addGlobalStyle(document, css) {
38
head = document.getElementsByTagName('head')[0];
39
if (!head) { return; }
40
style = document.createElement('style');
41
style.type = 'text/css';
42
style.innerHTML = css;
43
head.appendChild(style);
47
/* Sets up the reddit-mode for the given buffer. */
48
function reddit_mode_setup(buffer) {
49
var document = buffer.document;
50
if(document.reddit_mode_loaded) return;
51
else document.reddit_mode_loaded = true;
52
var siteTable = document.getElementById("siteTable");
54
/* siteTable not found, abort. This happens e.g. when browsing the
59
// Get all divs that have a id that starts with "thingrow"
60
var links = siteTable.getElementsByTagName("div");
61
links = Array.filter(links, function (element) {
62
var start = element.className.substr(0, 12);
63
if (start === "thing id-t3_") {
64
element.articleId = element.className.substr(12, 5);
65
element.highlighted = false;
66
if(element.style.backgroundColor == "")
67
element.originalBackgroundColor = "transparent";
69
element.originalBackgroundColor = element.style.backgroundColor;
42
function reddit_next (I) {
43
var doc = I.buffer.document;
44
// the behavior of this command depends on whether we have downloaded
45
// enough of the page to include all of the article links.
46
var complete = doc.getElementsByClassName('footer').length > 0;
47
var links = doc.getElementsByClassName('link');
51
for (var i = 0, llen = links.length; i < llen; i++) {
52
if (links[i].style.display == 'none')
75
/* Get the last links on the page titled "next" and "prev" */
76
var anchors = document.getElementsByTagName("a");
77
var nextLinks = Array.filter(anchors, function (element) {
78
return element.textContent && "next" === element.textContent;
80
var previousLinks = Array.filter(anchors, function (element) {
81
return element.textContent && "prev" === element.textContent;
83
document.redditNextPage = nextLinks[nextLinks.length-1];
84
document.redditPrevPage = previousLinks[previousLinks.length-1];
86
if (!links || !links.length) {
60
if (links[i].className.indexOf("last-clicked") >= 0)
63
// The following situations are null-ops:
64
// 1) there are no links on the page.
65
// 2) page is incomplete and the current link is the last link.
66
if (!first || (current && !next && !complete))
89
// remove ugly white background (error in their css)
90
reddit_addGlobalStyle(document, ".linkcompressed .entry .buttons li { background-color: inherit !important; }");
93
var beforeLinkRegex = /^https?:\/\/([a-zA-Z0-9\-]*\.)*reddit\.com\/.*before=.*/;
94
if(beforeLinkRegex.test(buffer.current_URI.spec)) {
95
current = links.length-1;
96
reddit_showElement(buffer, links[current]);
98
reddit_toggleHighlight(links[current]);
100
document.redditCurrent = current;
101
document.redditLinkDivs = links;
104
/* Scroll the buffer down to the specified element */
105
function reddit_showElement(buffer, element) {
106
function getElementY(element) {
109
for (parent = element; parent; parent = parent.offsetParent) {
110
if (parent.offsetTop) {
111
offsetY += parent.offsetTop;
70
if (reddit_end_behavior == 'stop')
72
if (reddit_end_behavior == 'wrap')
74
if (reddit_end_behavior == 'page') {
75
let (xpr = doc.evaluate(
76
'//p[@class="nextprev"]/a[text()="next"]', doc, null,
77
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null))
80
if (xpr && (nextpage = xpr.iterateNext())) {
81
dom_remove_class(current, "last-clicked");
82
browser_object_follow(I.buffer, FOLLOW_DEFAULT, nextpage);
118
var position = getElementY(element);
119
var height = buffer.document.body.offsetHeight;
120
var scrollPosition = buffer.top_frame.pageYOffset;
121
if ((height + scrollPosition - position) < 10 || (position - scrollPosition) < 10) {
122
buffer.top_frame.scroll(0, position);
127
/* Go to the next link (eventually the next page).
129
function reddit_next(I) {
130
var document = I.buffer.document;
131
var links = document.redditLinkDivs;
132
var current = document.redditCurrent;
133
if(links !== undefined && current !== undefined) {
134
if (current < (links.length - 1)) {
135
reddit_toggleHighlight(links[current]);
137
reddit_toggleHighlight(links[current]);
138
reddit_showElement(I.buffer, links[current]);
140
else if (document.redditNextPage) {
141
I.buffer.window.content.location.href = document.redditNextPage.href;
143
document.redditCurrent = current;
147
/* Go to the previous link (above). Change to the previous page if we
150
function reddit_prev(I) {
151
var document = I.buffer.document;
152
var links = document.redditLinkDivs;
153
var current = document.redditCurrent;
154
if(links !== undefined && current !== undefined) {
156
reddit_toggleHighlight(links[current]);
158
reddit_toggleHighlight(links[current]);
159
reddit_showElement(I.buffer, links[current]);
161
else if (document.redditPrevPage) {
162
I.buffer.window.content.location.href = document.redditPrevPage.href;
164
document.redditCurrent = current;
168
function reddit_getArticleId(buffer) {
169
if(buffer.document.redditLinkDivs !== undefined &&
170
buffer.document.redditCurrent !== undefined)
171
return buffer.document.redditLinkDivs[buffer.document.redditCurrent].articleId;
177
function reddit_open(I, target) {
178
var articleId = reddit_getArticleId(I.buffer);
180
var dest = "http://reddit.com/goto?id=" + articleId;
181
browser_object_follow(I.buffer, target || OPEN_CURRENT_BUFFER, dest);
184
function reddit_open_new_buffer (I) {
185
reddit_open(I, OPEN_NEW_BUFFER);
187
function reddit_open_new_window (I) {
188
reddit_open(I, OPEN_NEW_WINDOW);
191
function reddit_open_comments(I, target) {
192
var articleId = reddit_getArticleId(I.buffer);
194
var dest = "http://reddit.com/info/" + articleId + "/comments/";
195
browser_object_follow(I.buffer, target || OPEN_CURRENT_BUFFER, dest);
198
function reddit_open_comments_new_buffer (I) {
199
reddit_open_comments(I, OPEN_NEW_BUFFER);
201
function reddit_open_comments_new_window (I) {
202
reddit_open_comments(I, OPEN_NEW_WINDOW);
205
function reddit_mod_up (I) {
206
var articleId = reddit_getArticleId(I.buffer);
208
I.buffer.top_frame.wrappedJSObject.mod("t3_" + articleId, 1);
211
function reddit_mod_down(I) {
212
var articleId = reddit_getArticleId(I.buffer);
214
I.buffer.top_frame.wrappedJSObject.mod("t3_" + articleId, 0);
88
// Page may or may not be complete. If the page is not
89
// complete, it is safe to assume that there is no current
90
// link because a current link can only persist on a
91
// cached page, which would load instantaneously, not
92
// giving the user the opportunity to run this command.
97
// ordinaries (highlight new, maybe dehighlight old)
99
dom_remove_class(current, "last-clicked");
100
dom_add_class(next, "last-clicked");
101
let (anchor = doc.evaluate(
102
'//*[contains(@class,"last-clicked")]//a[contains(@class,"title")]',
103
next, null, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null))
105
browser_set_element_focus(I.buffer, anchor.singleNodeValue);
107
reddit_scroll_into_view(I.buffer.focused_frame, next);
217
109
interactive("reddit-next-link",
218
110
"Move the 'cursor' to the next reddit entry.",
114
/* Select the link before the currently highlighted one. When the
115
* beginning of the page is reached, behavior is controlled by the
116
* variable reddit_end_behavior.
118
function reddit_prev (I) {
119
var doc = I.buffer.document;
120
// the behavior of this command depends on whether we have downloaded
121
// enough of the page to include all of the article links.
122
var complete = doc.getElementsByClassName('footer').length > 0;
123
var links = doc.getElementsByClassName('link');
124
var llen = links.length;
128
for (var i = 0; i < llen; i++) {
129
if (links[i].style.display == 'none')
133
if (links[i].className.indexOf("last-clicked") >= 0) {
139
if (! first || // no links were found at all.
140
(!current && !complete)) // don't know where current is.
143
// the first visible link is the `current' link.
144
// dispatch on reddit_end_behavior.
145
if (reddit_end_behavior == 'stop')
147
else if (reddit_end_behavior == 'wrap') {
148
// need to get last link on page.
150
for (var i = 0; i < llen; i++) {
151
if (links[i].style.display == 'none')
156
} else if (reddit_end_behavior == 'page') {
157
let (xpr = doc.evaluate(
158
'//p[@class="nextprev"]/a[text()="prev"]', doc, null,
159
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null))
162
if (xpr && (prevpage = xpr.iterateNext())) {
163
dom_remove_class(current, "last-clicked");
164
browser_object_follow(I.buffer, FOLLOW_DEFAULT, prevpage);
170
// ordinaries (highlight new, maybe dehighlight old)
172
dom_remove_class(current, "last-clicked");
173
dom_add_class(prev, "last-clicked");
174
let (anchor = doc.evaluate(
175
'//*[contains(@class,"last-clicked")]//a[contains(@class,"title")]',
176
prev, null, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null))
178
browser_set_element_focus(I.buffer, anchor.singleNodeValue);
180
reddit_scroll_into_view(I.buffer.focused_frame, prev);
221
182
interactive("reddit-prev-link",
222
183
"Move the 'cursor' to the previous reddit entry.",
225
interactive("reddit-open-current",
226
"Open the currently selected link.",
227
alternates(reddit_open,
228
reddit_open_new_buffer,
229
reddit_open_new_window));
187
function reddit_open_comments (I, target) {
188
var xpr = I.buffer.document.evaluate(
189
'//*[contains(@class,"last-clicked")]/descendant::a[@class="comments"]',
190
I.buffer.document, null,
191
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
193
if (xpr && (link = xpr.iterateNext()))
194
browser_object_follow(I.buffer, target || FOLLOW_DEFAULT, link);
196
function reddit_open_comments_new_buffer (I) {
197
reddit_open_comments(I, OPEN_NEW_BUFFER);
199
function reddit_open_comments_new_window (I) {
200
reddit_open_comments(I, OPEN_NEW_WINDOW);
231
202
interactive("reddit-open-comments",
232
203
"Open the comments-page associated with the currently selected link.",
233
204
alternates(reddit_open_comments,
234
205
reddit_open_comments_new_buffer,
235
206
reddit_open_comments_new_window));
209
function reddit_vote_up (I) {
210
// get the current article and send a click to its vote-up button.
211
var xpr = I.buffer.document.evaluate(
212
'//*[contains(@class,"last-clicked")]/div[contains(@class,"midcol")]/div[contains(@class,"up")]',
213
I.buffer.document, null,
214
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
216
if (xpr && (link = xpr.iterateNext()))
217
browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
237
219
interactive("reddit-vote-up",
238
220
"Vote the currently selected link up.",
224
function reddit_vote_down (I) {
225
// get the current article and send a click to its vote-down button.
226
var xpr = I.buffer.document.evaluate(
227
'//*[contains(@class,"last-clicked")]/div[contains(@class,"midcol")]/div[contains(@class,"down")]',
228
I.buffer.document, null,
229
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
231
if (xpr && (link = xpr.iterateNext()))
232
browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
241
234
interactive("reddit-vote-down",
242
235
"Vote the currently selected link down.",
245
/* Creating the keymap */
246
define_keymap("reddit_keymap", $parent = content_buffer_normal_keymap);
239
define_browser_object_class("reddit-current", null,
240
function (I, prompt) {
241
var xpr = I.buffer.document.evaluate(
242
'//*[contains(@class,"last-clicked")]/*[contains(@class,"entry")]/p[@class="title"]/a',
243
I.buffer.document, null,
244
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
245
yield co_return(xpr.iterateNext());
249
define_keymap("reddit_keymap");
247
250
define_key(reddit_keymap, "j", "reddit-next-link");
248
251
define_key(reddit_keymap, "k", "reddit-prev-link");
249
252
define_key(reddit_keymap, ",", "reddit-vote-up");
250
253
define_key(reddit_keymap, ".", "reddit-vote-down");
252
define_key(reddit_keymap, "o", "reddit-open-current");
253
254
define_key(reddit_keymap, "h", "reddit-open-comments");
255
/* Setting up and tearing down the mode */
257
function enable_reddit_mode(buffer) {
258
var doc = buffer.document;
259
if(doc.redditCurrent != null)
260
reddit_highlight(doc.redditLinkDivs[doc.redditCurrent]);
261
buffer.local_variables.content_buffer_normal_keymap = reddit_keymap;
262
add_hook.call(buffer, "content_buffer_finished_loading_hook", reddit_mode_setup);
265
function disable_reddit_mode(buffer) {
266
var doc = buffer.document;
268
if(doc.redditCurrent != null)
269
reddit_dehighlight(doc.redditLinkDivs[doc.redditCurrent]);
270
remove_hook.call(buffer, "content_buffer_finished_loading_hook", reddit_mode_setup);
273
define_page_mode("reddit_mode", "reddit",
274
$enable = enable_reddit_mode,
275
$disable = disable_reddit_mode,
256
function reddit_modality (buffer, element) {
257
// terse but hacky way to get the effect we want. the current "correct"
258
// way would be to write an entire long dispatcher like that used for the
259
// basic content-buffer modality in content-buffer-input.js. we really
260
// need some abstraction to let us tersely express when to push keymaps.
261
if (! buffer.input_mode)
262
buffer.keymaps.push(reddit_keymap);
265
define_page_mode("reddit_mode",
266
$display_name = "reddit",
267
$enable = function (buffer) {
268
let (cmds = ["follow-current",
269
"follow-current-new-buffer",
270
"follow-current-new-buffer-background",
271
"follow-current-new-window",
273
for each (var c in cmds) {
274
buffer.default_browser_object_classes[c] =
275
browser_object_reddit_current;
278
buffer.modalities.push(reddit_modality);
280
$disable = function (buffer) {
281
var i = buffer.modalities.indexOf(reddit_modality);
283
buffer.modalities.splice(i, 1);
276
285
$doc = "reddit page-mode: keyboard navigation for reddit.");
278
var reddit_re = build_url_regex($domain = /([a-zA-Z0-9\-]*\.)*reddit/);
279
auto_mode_list.push([reddit_re, reddit_mode]);
287
let (re = build_url_regex($domain = /([a-zA-Z0-9\-]*\.)*reddit/)) {
288
auto_mode_list.push([re, reddit_mode]);