2 * (C) Copyright 2008 Martin Dybdal
3 * (C) Copyright 2009-2010,2012 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/) {" +
24 "body>.content .last-clicked {" +
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.querySelectorAll("body>.content .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 var anchor = doc.querySelector("body>.content .last-clicked a.title");
102 browser_set_element_focus(I.buffer, anchor);
103 reddit_scroll_into_view(I.buffer.focused_frame, next);
105 interactive("reddit-next-link",
106 "Move the 'cursor' to the next reddit entry.",
110 /* Select the link before the currently highlighted one. When the
111 * beginning of the page is reached, behavior is controlled by the
112 * variable reddit_end_behavior.
114 function reddit_prev (I) {
115 var doc = I.buffer.document;
116 // the behavior of this command depends on whether we have downloaded
117 // enough of the page to include all of the article links.
118 var complete = doc.getElementsByClassName('footer').length > 0;
119 var links = doc.querySelectorAll("body>.content .link");
120 var llen = links.length;
124 for (var i = 0; i < llen; i++) {
125 if (links[i].style.display == 'none')
129 if (links[i].className.indexOf("last-clicked") >= 0) {
135 if (! first || // no links were found at all.
136 (!current && !complete)) // don't know where current is.
139 // the first visible link is the `current' link.
140 // dispatch on reddit_end_behavior.
141 if (reddit_end_behavior == 'stop')
143 else if (reddit_end_behavior == 'wrap') {
144 // need to get last link on page.
146 for (var i = 0; i < llen; i++) {
147 if (links[i].style.display == 'none')
152 } else if (reddit_end_behavior == 'page') {
153 let (xpr = doc.evaluate(
154 '//p[@class="nextprev"]/a[text()="prev"]', doc, null,
155 Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null))
158 if (xpr && (prevpage = xpr.iterateNext())) {
159 dom_remove_class(current, "last-clicked");
160 browser_object_follow(I.buffer, FOLLOW_DEFAULT, prevpage);
166 // ordinaries (highlight new, maybe dehighlight old)
168 dom_remove_class(current, "last-clicked");
169 dom_add_class(prev, "last-clicked");
170 var anchor = doc.querySelector("body>.content .last-clicked a.title");
171 browser_set_element_focus(I.buffer, anchor);
172 reddit_scroll_into_view(I.buffer.focused_frame, prev);
174 interactive("reddit-prev-link",
175 "Move the 'cursor' to the previous reddit entry.",
179 function reddit_open_comments (I, target) {
180 var doc = I.buffer.document;
181 var link = doc.querySelector("body>.content .last-clicked a.comments");
183 browser_object_follow(I.buffer, target || FOLLOW_DEFAULT, link);
185 function reddit_open_comments_new_buffer (I) {
186 reddit_open_comments(I, OPEN_NEW_BUFFER);
188 function reddit_open_comments_new_window (I) {
189 reddit_open_comments(I, OPEN_NEW_WINDOW);
191 interactive("reddit-open-comments",
192 "Open the comments-page associated with the currently selected link.",
193 alternates(reddit_open_comments,
194 reddit_open_comments_new_buffer,
195 reddit_open_comments_new_window));
198 function reddit_vote_up (I) {
199 // get the current article and send a click to its vote-up button.
200 var doc = I.buffer.document;
201 var link = doc.querySelector("body>.content .last-clicked .midcol .up");
203 browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
205 interactive("reddit-vote-up",
206 "Vote the currently selected link up.",
210 function reddit_vote_down (I) {
211 // get the current article and send a click to its vote-down button.
212 var doc = I.buffer.document;
213 var link = doc.querySelector("body>.content .last-clicked .midcol .down");
215 browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
217 interactive("reddit-vote-down",
218 "Vote the currently selected link down.",
222 define_browser_object_class("reddit-current", null,
223 function (I, prompt) {
224 var doc = I.buffer.document;
225 var link = doc.querySelector("body>.content .last-clicked .entry p.title a");
226 yield co_return(link);
230 define_keymap("reddit_keymap", $display_name = "reddit");
231 define_key(reddit_keymap, "j", "reddit-next-link");
232 define_key(reddit_keymap, "k", "reddit-prev-link");
233 define_key(reddit_keymap, ",", "reddit-vote-up");
234 define_key(reddit_keymap, ".", "reddit-vote-down");
235 define_key(reddit_keymap, "h", "reddit-open-comments");
238 var reddit_link_commands =
239 ["follow-current", "follow-current-new-buffer",
240 "follow-current-new-buffer-background",
241 "follow-current-new-window", "copy"];
244 var reddit_modality = {
245 normal: reddit_keymap
249 define_page_mode("reddit-mode",
250 build_url_regexp($domain = /([a-zA-Z0-9\-]*\.)*reddit/),
251 function enable (buffer) {
252 for each (var c in reddit_link_commands) {
253 buffer.default_browser_object_classes[c] =
254 browser_object_reddit_current;
256 buffer.content_modalities.push(reddit_modality);
258 function disable (buffer) {
259 for each (var c in reddit_link_commands) {
260 delete buffer.default_browser_object_classes[c];
262 var i = buffer.content_modalities.indexOf(reddit_modality);
264 buffer.content_modalities.splice(i, 1);
266 $display_name = "reddit",
267 $doc = "reddit page-mode: keyboard navigation for reddit.");
269 page_mode_activate(reddit_mode);