2 * (C) Copyright 2008 Martin Dybdal
3 * (C) Copyright 2009-2010 John Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
11 require("content-buffer.js");
13 define_variable("reddit_end_behavior", "stop",
14 "Controls the behavior of the commands reddit-next-link and "+
15 "reddit-prev-link when at the last or first link, respectively. "+
16 "Given as a string, the supported values are 'stop', 'wrap', "+
17 "and 'page'. 'stop' means to not move the highlight in any "+
18 "way. 'wrap' means to wrap around to the first (or last) "+
19 "link. 'page' means to navigate the buffer to the next (or "+
20 "previous) page on reddit.");
22 register_user_stylesheet(
25 "@-moz-document url-prefix(http://www.reddit.com/) {" +
27 " background-color: #bfb !important;" +
28 " border: 0px !important;"+
32 /* Scroll, if necessary, to make the given element visible */
33 function reddit_scroll_into_view (window, element) {
34 var rect = element.getBoundingClientRect();
35 if (rect.top < 0 || rect.bottom > window.innerHeight)
36 element.scrollIntoView();
40 /* Move select the next link down from the currently highlighted one.
41 * When the end of the page is reached, the behavior is controlled by
42 * the variable reddit_end_behavior.
44 function reddit_next (I) {
45 var doc = I.buffer.document;
46 // the behavior of this command depends on whether we have downloaded
47 // enough of the page to include all of the article links.
48 var complete = doc.getElementsByClassName('footer').length > 0;
49 var links = doc.getElementsByClassName('link');
53 for (var i = 0, llen = links.length; i < llen; i++) {
54 if (links[i].style.display == 'none')
62 if (links[i].className.indexOf("last-clicked") >= 0)
65 // The following situations are null-ops:
66 // 1) there are no links on the page.
67 // 2) page is incomplete and the current link is the last link.
68 if (!first || (current && !next && !complete))
72 if (reddit_end_behavior == 'stop')
74 if (reddit_end_behavior == 'wrap')
76 if (reddit_end_behavior == 'page') {
77 let (xpr = doc.evaluate(
78 '//p[@class="nextprev"]/a[text()="next"]', doc, null,
79 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null))
82 if (xpr && (nextpage = xpr.iterateNext())) {
83 dom_remove_class(current, "last-clicked");
84 browser_object_follow(I.buffer, FOLLOW_DEFAULT, nextpage);
90 // Page may or may not be complete. If the page is not
91 // complete, it is safe to assume that there is no current
92 // link because a current link can only persist on a
93 // cached page, which would load instantaneously, not
94 // giving the user the opportunity to run this command.
99 // ordinaries (highlight new, maybe dehighlight old)
101 dom_remove_class(current, "last-clicked");
102 dom_add_class(next, "last-clicked");
103 let (anchor = doc.evaluate(
104 '//*[contains(@class,"last-clicked")]//a[contains(@class,"title")]',
105 next, null, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null))
107 browser_set_element_focus(I.buffer, anchor.singleNodeValue);
109 reddit_scroll_into_view(I.buffer.focused_frame, next);
111 interactive("reddit-next-link",
112 "Move the 'cursor' to the next reddit entry.",
116 /* Select the link before the currently highlighted one. When the
117 * beginning of the page is reached, behavior is controlled by the
118 * variable reddit_end_behavior.
120 function reddit_prev (I) {
121 var doc = I.buffer.document;
122 // the behavior of this command depends on whether we have downloaded
123 // enough of the page to include all of the article links.
124 var complete = doc.getElementsByClassName('footer').length > 0;
125 var links = doc.getElementsByClassName('link');
126 var llen = links.length;
130 for (var i = 0; i < llen; i++) {
131 if (links[i].style.display == 'none')
135 if (links[i].className.indexOf("last-clicked") >= 0) {
141 if (! first || // no links were found at all.
142 (!current && !complete)) // don't know where current is.
145 // the first visible link is the `current' link.
146 // dispatch on reddit_end_behavior.
147 if (reddit_end_behavior == 'stop')
149 else if (reddit_end_behavior == 'wrap') {
150 // need to get last link on page.
152 for (var i = 0; i < llen; i++) {
153 if (links[i].style.display == 'none')
158 } else if (reddit_end_behavior == 'page') {
159 let (xpr = doc.evaluate(
160 '//p[@class="nextprev"]/a[text()="prev"]', doc, null,
161 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null))
164 if (xpr && (prevpage = xpr.iterateNext())) {
165 dom_remove_class(current, "last-clicked");
166 browser_object_follow(I.buffer, FOLLOW_DEFAULT, prevpage);
172 // ordinaries (highlight new, maybe dehighlight old)
174 dom_remove_class(current, "last-clicked");
175 dom_add_class(prev, "last-clicked");
176 let (anchor = doc.evaluate(
177 '//*[contains(@class,"last-clicked")]//a[contains(@class,"title")]',
178 prev, null, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null))
180 browser_set_element_focus(I.buffer, anchor.singleNodeValue);
182 reddit_scroll_into_view(I.buffer.focused_frame, prev);
184 interactive("reddit-prev-link",
185 "Move the 'cursor' to the previous reddit entry.",
189 function reddit_open_comments (I, target) {
190 var xpr = I.buffer.document.evaluate(
191 '//*[contains(@class,"last-clicked")]/descendant::a[@class="comments"]',
192 I.buffer.document, null,
193 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
195 if (xpr && (link = xpr.iterateNext()))
196 browser_object_follow(I.buffer, target || FOLLOW_DEFAULT, link);
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);
204 interactive("reddit-open-comments",
205 "Open the comments-page associated with the currently selected link.",
206 alternates(reddit_open_comments,
207 reddit_open_comments_new_buffer,
208 reddit_open_comments_new_window));
211 function reddit_vote_up (I) {
212 // get the current article and send a click to its vote-up button.
213 var xpr = I.buffer.document.evaluate(
214 '//*[contains(@class,"last-clicked")]/div[contains(@class,"midcol")]/div[contains(@class,"up")]',
215 I.buffer.document, null,
216 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
218 if (xpr && (link = xpr.iterateNext()))
219 browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
221 interactive("reddit-vote-up",
222 "Vote the currently selected link up.",
226 function reddit_vote_down (I) {
227 // get the current article and send a click to its vote-down button.
228 var xpr = I.buffer.document.evaluate(
229 '//*[contains(@class,"last-clicked")]/div[contains(@class,"midcol")]/div[contains(@class,"down")]',
230 I.buffer.document, null,
231 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
233 if (xpr && (link = xpr.iterateNext()))
234 browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
236 interactive("reddit-vote-down",
237 "Vote the currently selected link down.",
241 define_browser_object_class("reddit-current", null,
242 function (I, prompt) {
243 var xpr = I.buffer.document.evaluate(
244 '//*[contains(@class,"last-clicked")]/*[contains(@class,"entry")]/p[@class="title"]/a',
245 I.buffer.document, null,
246 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
247 yield co_return(xpr.iterateNext());
251 define_keymap("reddit_keymap", $display_name = "reddit");
252 define_key(reddit_keymap, "j", "reddit-next-link");
253 define_key(reddit_keymap, "k", "reddit-prev-link");
254 define_key(reddit_keymap, ",", "reddit-vote-up");
255 define_key(reddit_keymap, ".", "reddit-vote-down");
256 define_key(reddit_keymap, "h", "reddit-open-comments");
259 var reddit_modality = {
260 normal: reddit_keymap
264 define_page_mode("reddit-mode",
265 build_url_regex($domain = /([a-zA-Z0-9\-]*\.)*reddit/),
266 function enable (buffer) {
267 let (cmds = ["follow-current",
268 "follow-current-new-buffer",
269 "follow-current-new-buffer-background",
270 "follow-current-new-window",
272 for each (var c in cmds) {
273 buffer.default_browser_object_classes[c] =
274 browser_object_reddit_current;
277 buffer.content_modalities.push(reddit_modality);
279 function disable (buffer) {
280 var i = buffer.content_modalities.indexOf(reddit_modality);
282 buffer.content_modalities.splice(i, 1);
284 $display_name = "reddit",
285 $doc = "reddit page-mode: keyboard navigation for reddit.");
287 page_mode_activate(reddit_mode);