de713b0add54c573e100718f30509b38d420325a
[conkeror.git] / modules / page-modes / reddit.js
blobde713b0add54c573e100718f30509b38d420325a
1 /**
2  * (C) Copyright 2008 Martin Dybdal
3  * (C) Copyright 2009-2010,2012 John Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
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(
21     "data:text/css," +
22         escape (
23             "@-moz-document url-prefix(http://www.reddit.com/) {" +
24                 "body>.content .last-clicked {" +
25                 " background-color: #bfb !important;" +
26                 " border: 0px !important;"+
27                 "}}"));
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.
41  */
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");
48     var first = null;
49     var current = null;
50     var next = null;
51     for (var i = 0, llen = links.length; i < llen; i++) {
52         if (links[i].style.display == 'none')
53             continue;
54         if (! first)
55             first = links[i];
56         if (current) {
57             next = links[i];
58             break;
59         }
60         if (links[i].className.indexOf("last-clicked") >= 0)
61             current = links[i];
62     }
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))
67         return;
68     if (! next) {
69         if (current) {
70             if (reddit_end_behavior == 'stop')
71                 return;
72             if (reddit_end_behavior == 'wrap')
73                 next = first;
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))
78                 {
79                     let nextpage;
80                     if (xpr && (nextpage = xpr.iterateNext())) {
81                         dom_remove_class(current, "last-clicked");
82                         browser_object_follow(I.buffer, FOLLOW_DEFAULT, nextpage);
83                         return;
84                     }
85                 }
86             }
87         } else {
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.
93             //
94             next = first;
95         }
96     }
97     // ordinaries (highlight new, maybe dehighlight old)
98     if (current)
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.",
107             reddit_next);
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.
113  */
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;
121     var first = null;
122     var prev = null;
123     var current = null;
124     for (var i = 0; i < llen; i++) {
125         if (links[i].style.display == 'none')
126             continue;
127         if (! first)
128             first = links[i];
129         if (links[i].className.indexOf("last-clicked") >= 0) {
130             current = links[i];
131             break;
132         }
133         prev = links[i];
134     }
135     if (! first || // no links were found at all.
136         (!current && !complete)) // don't know where current is.
137         return;
138     if (! prev) {
139         // the first visible link is the `current' link.
140         // dispatch on reddit_end_behavior.
141         if (reddit_end_behavior == 'stop')
142             return;
143         else if (reddit_end_behavior == 'wrap') {
144             // need to get last link on page.
145             if (complete) {
146                 for (var i = 0; i < llen; i++) {
147                     if (links[i].style.display == 'none')
148                         continue;
149                     prev = links[i];
150                 }
151             }
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))
156             {
157                 let prevpage;
158                 if (xpr && (prevpage = xpr.iterateNext())) {
159                     dom_remove_class(current, "last-clicked");
160                     browser_object_follow(I.buffer, FOLLOW_DEFAULT, prevpage);
161                     return;
162                 }
163             }
164         }
165     }
166     // ordinaries (highlight new, maybe dehighlight old)
167     if (current)
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.",
176             reddit_prev);
179 function reddit_open_comments (I, target) {
180     var doc = I.buffer.document;
181     var link = doc.querySelector("body>.content .last-clicked a.comments");
182     if (link)
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");
202     if (link)
203         browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
205 interactive("reddit-vote-up",
206             "Vote the currently selected link up.",
207             reddit_vote_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");
214     if (link)
215         browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
217 interactive("reddit-vote-down",
218             "Vote the currently selected link down.",
219             reddit_vote_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);
227     });
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;
255         }
256         buffer.content_modalities.push(reddit_modality);
257     },
258     function disable (buffer) {
259         for each (var c in reddit_link_commands) {
260             delete buffer.default_browser_object_classes[c];
261         }
262         var i = buffer.content_modalities.indexOf(reddit_modality);
263         if (i > -1)
264             buffer.content_modalities.splice(i, 1);
265     },
266     $display_name = "reddit",
267     $doc = "reddit page-mode: keyboard navigation for reddit.");
269 page_mode_activate(reddit_mode);
271 provide("reddit");