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
9 require("content-buffer.js");
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.
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')
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))
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);
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);
109 interactive("reddit-next-link",
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);
182 interactive("reddit-prev-link",
183 "Move the 'cursor' to the previous reddit entry.",
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);
202 interactive("reddit-open-comments",
203 "Open the comments-page associated with the currently selected link.",
204 alternates(reddit_open_comments,
205 reddit_open_comments_new_buffer,
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);
219 interactive("reddit-vote-up",
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);
234 interactive("reddit-vote-down",
235 "Vote the currently selected link down.",
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", $display_name = "reddit");
250 define_key(reddit_keymap, "j", "reddit-next-link");
251 define_key(reddit_keymap, "k", "reddit-prev-link");
252 define_key(reddit_keymap, ",", "reddit-vote-up");
253 define_key(reddit_keymap, ".", "reddit-vote-down");
254 define_key(reddit_keymap, "h", "reddit-open-comments");
257 var reddit_link_commands =
258 ["follow-current", "follow-current-new-buffer",
259 "follow-current-new-buffer-background",
260 "follow-current-new-window", "copy"];
263 var reddit_modality = {
264 normal: reddit_keymap
268 define_page_mode("reddit-mode",
269 build_url_regexp($domain = /([a-zA-Z0-9\-]*\.)*reddit/),
270 function enable (buffer) {
271 for each (var c in reddit_link_commands) {
272 buffer.default_browser_object_classes[c] =
273 browser_object_reddit_current;
275 buffer.content_modalities.push(reddit_modality);
277 function disable (buffer) {
278 for each (var c in reddit_link_commands) {
279 delete buffer.default_browser_object_classes[c];
281 var i = buffer.content_modalities.indexOf(reddit_modality);
283 buffer.content_modalities.splice(i, 1);
285 $display_name = "reddit",
286 $doc = "reddit page-mode: keyboard navigation for reddit.");
288 page_mode_activate(reddit_mode);