~ubuntu-branches/ubuntu/lucid/conkeror/lucid

« back to all changes in this revision

Viewing changes to modules/page-modes/reddit.js

  • Committer: Bazaar Package Importer
  • Author(s): Micah Gersten
  • Date: 2010-03-26 18:55:20 UTC
  • mfrom: (3.1.3 sid)
  • Revision ID: james.westby@ubuntu.com-20100326185520-ysgua8nfa2lxr9m4
Tags: 0.9.1+git100220-1ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/control: Change Depends to xulrunner-1.9.2 only (LP #537900)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/**
2
2
 * (C) Copyright 2008 Martin Dybdal
 
3
 * (C) Copyright 2009 John Foerch
3
4
 *
4
5
 * Use, modification, and distribution are subject to the terms specified in the
5
6
 * COPYING file.
7
8
 
8
9
require("content-buffer.js");
9
10
 
10
 
/* Initially based on the greasemonkey userscript "Reddit keyboard
11
 
 * shortcuts", found on userscripts.org. But much has changed because of
12
 
 * a reddit-remake.
 
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.");
 
19
 
 
20
register_user_stylesheet(
 
21
    "data:text/css," +
 
22
        escape (
 
23
            "@-moz-document url-prefix(http://www.reddit.com/) {" +
 
24
                ".last-clicked {" +
 
25
                " background-color: #bfb !important;" +
 
26
                " border: 0px !important;"+
 
27
                "}}"));
 
28
 
 
29
 
 
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();
 
35
}
 
36
 
 
37
 
 
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.
13
41
 */
14
 
var reddit_highlight_color = "#BFB";
15
 
 
16
 
 
17
 
function reddit_highlight(elem) {
18
 
    elem.highlighted = true;
19
 
    elem.style.backgroundColor = reddit_highlight_color;
20
 
}
21
 
 
22
 
function reddit_dehighlight(elem) {
23
 
    elem.style.backgroundColor = elem.originalBackgroundColor;
24
 
    elem.highlighted = false;
25
 
}
26
 
 
27
 
function reddit_toggleHighlight(elem) {
28
 
    if(elem) {
29
 
      if(elem.highlighted)
30
 
        reddit_dehighlight(elem);
31
 
      else
32
 
        reddit_highlight(elem);
33
 
    }
34
 
}
35
 
 
36
 
function reddit_addGlobalStyle(document, css) {
37
 
    var head, style;
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);
44
 
}
45
 
 
46
 
 
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");
53
 
    if (!siteTable) {
54
 
        /* siteTable not found, abort. This happens e.g. when browsing the
55
 
       preferences */
56
 
        return;
57
 
    }
58
 
 
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";
68
 
            else
69
 
                element.originalBackgroundColor = element.style.backgroundColor;
70
 
            return true;
 
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');
 
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;
71
59
        }
72
 
        return false;
73
 
    });
74
 
 
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;
79
 
    });
80
 
    var previousLinks = Array.filter(anchors, function (element) {
81
 
        return element.textContent && "prev" === element.textContent;
82
 
    });
83
 
    document.redditNextPage = nextLinks[nextLinks.length-1];
84
 
    document.redditPrevPage = previousLinks[previousLinks.length-1];
85
 
 
86
 
    if (!links || !links.length) {
 
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))
87
67
        return;
88
 
    }
89
 
    // remove ugly white background (error in their css)
90
 
    reddit_addGlobalStyle(document, ".linkcompressed .entry .buttons li { background-color: inherit !important; }");
91
 
 
92
 
    var current = 0;
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]);
97
 
    }
98
 
    reddit_toggleHighlight(links[current]);
99
 
 
100
 
    document.redditCurrent = current;
101
 
    document.redditLinkDivs = links;
102
 
}
103
 
 
104
 
/* Scroll the buffer down to the specified element */
105
 
function reddit_showElement(buffer, element) {
106
 
    function getElementY(element) {
107
 
        var offsetY = 0;
108
 
        var parent;
109
 
        for (parent = element; parent; parent = parent.offsetParent) {
110
 
            if (parent.offsetTop) {
111
 
                offsetY += parent.offsetTop;
 
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
                }
112
86
            }
113
 
        }
114
 
        return offsetY;
115
 
    }
116
 
 
117
 
    if(element) {
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);
123
 
        }
124
 
    }
125
 
}
126
 
 
127
 
/* Go to the next link (eventually the next page).
128
 
 */
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]);
136
 
            current++;
137
 
            reddit_toggleHighlight(links[current]);
138
 
            reddit_showElement(I.buffer, links[current]);
139
 
        }
140
 
        else if (document.redditNextPage) {
141
 
            I.buffer.window.content.location.href = document.redditNextPage.href;
142
 
        }
143
 
        document.redditCurrent = current;
144
 
    }
145
 
}
146
 
 
147
 
/* Go to the previous link (above). Change to the previous page if we
148
 
 * are at the top.
149
 
 */
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) {
155
 
        if (current > 0) {
156
 
            reddit_toggleHighlight(links[current]);
157
 
            current--;
158
 
            reddit_toggleHighlight(links[current]);
159
 
            reddit_showElement(I.buffer, links[current]);
160
 
        }
161
 
        else if (document.redditPrevPage) {
162
 
            I.buffer.window.content.location.href = document.redditPrevPage.href;
163
 
        }
164
 
        document.redditCurrent = current;
165
 
    }
166
 
}
167
 
 
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;
172
 
    else
173
 
        return false;
174
 
}
175
 
 
176
 
 
177
 
function reddit_open(I, target) {
178
 
    var articleId = reddit_getArticleId(I.buffer);
179
 
    if(articleId) {
180
 
        var dest = "http://reddit.com/goto?id=" + articleId;
181
 
        browser_object_follow(I.buffer, target || OPEN_CURRENT_BUFFER, dest);
182
 
    }
183
 
}
184
 
function reddit_open_new_buffer (I) {
185
 
    reddit_open(I, OPEN_NEW_BUFFER);
186
 
}
187
 
function reddit_open_new_window (I) {
188
 
    reddit_open(I, OPEN_NEW_WINDOW);
189
 
}
190
 
 
191
 
function reddit_open_comments(I, target) {
192
 
    var articleId = reddit_getArticleId(I.buffer);
193
 
    if(articleId) {
194
 
        var dest = "http://reddit.com/info/" + articleId + "/comments/";
195
 
        browser_object_follow(I.buffer, target || OPEN_CURRENT_BUFFER, dest);
196
 
    }
197
 
}
198
 
function reddit_open_comments_new_buffer (I) {
199
 
    reddit_open_comments(I, OPEN_NEW_BUFFER);
200
 
}
201
 
function reddit_open_comments_new_window (I) {
202
 
    reddit_open_comments(I, OPEN_NEW_WINDOW);
203
 
}
204
 
 
205
 
function reddit_mod_up (I) {
206
 
    var articleId = reddit_getArticleId(I.buffer);
207
 
    if(articleId)
208
 
        I.buffer.top_frame.wrappedJSObject.mod("t3_" + articleId, 1);
209
 
}
210
 
 
211
 
function reddit_mod_down(I) {
212
 
    var articleId = reddit_getArticleId(I.buffer);
213
 
    if(articleId)
214
 
        I.buffer.top_frame.wrappedJSObject.mod("t3_" + articleId, 0);
215
 
}
216
 
 
 
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
    let (anchor = doc.evaluate(
 
102
        '//*[contains(@class,"last-clicked")]//a[contains(@class,"title")]',
 
103
        next, null, Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null))
 
104
    {
 
105
        browser_set_element_focus(I.buffer, anchor.singleNodeValue);
 
106
    }
 
107
    reddit_scroll_into_view(I.buffer.focused_frame, next);
 
108
}
217
109
interactive("reddit-next-link",
218
110
            "Move the 'cursor' to the next reddit entry.",
219
111
            reddit_next);
220
112
 
 
113
 
 
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.
 
117
 */
 
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;
 
125
    var first = null;
 
126
    var prev = null;
 
127
    var current = null;
 
128
    for (var i = 0; i < llen; i++) {
 
129
        if (links[i].style.display == 'none')
 
130
            continue;
 
131
        if (! first)
 
132
            first = links[i];
 
133
        if (links[i].className.indexOf("last-clicked") >= 0) {
 
134
            current = links[i];
 
135
            break;
 
136
        }
 
137
        prev = links[i];
 
138
    }
 
139
    if (! first || // no links were found at all.
 
140
        (!current && !complete)) // don't know where current is.
 
141
        return;
 
142
    if (! prev) {
 
143
        // the first visible link is the `current' link.
 
144
        // dispatch on reddit_end_behavior.
 
145
        if (reddit_end_behavior == 'stop')
 
146
            return;
 
147
        else if (reddit_end_behavior == 'wrap') {
 
148
            // need to get last link on page.
 
149
            if (complete) {
 
150
                for (var i = 0; i < llen; i++) {
 
151
                    if (links[i].style.display == 'none')
 
152
                        continue;
 
153
                    prev = links[i];
 
154
                }
 
155
            }
 
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))
 
160
            {
 
161
                let prevpage;
 
162
                if (xpr && (prevpage = xpr.iterateNext())) {
 
163
                    dom_remove_class(current, "last-clicked");
 
164
                    browser_object_follow(I.buffer, FOLLOW_DEFAULT, prevpage);
 
165
                    return;
 
166
                }
 
167
            }
 
168
        }
 
169
    }
 
170
    // ordinaries (highlight new, maybe dehighlight old)
 
171
    if (current)
 
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))
 
177
    {
 
178
        browser_set_element_focus(I.buffer, anchor.singleNodeValue);
 
179
    }
 
180
    reddit_scroll_into_view(I.buffer.focused_frame, prev);
 
181
}
221
182
interactive("reddit-prev-link",
222
183
            "Move the 'cursor' to the previous reddit entry.",
223
184
            reddit_prev);
224
185
 
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));
230
186
 
 
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);
 
192
    let link;
 
193
    if (xpr && (link = xpr.iterateNext()))
 
194
        browser_object_follow(I.buffer, target || FOLLOW_DEFAULT, link);
 
195
}
 
196
function reddit_open_comments_new_buffer (I) {
 
197
    reddit_open_comments(I, OPEN_NEW_BUFFER);
 
198
}
 
199
function reddit_open_comments_new_window (I) {
 
200
    reddit_open_comments(I, OPEN_NEW_WINDOW);
 
201
}
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));
236
207
 
 
208
 
 
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);
 
215
    let link;
 
216
    if (xpr && (link = xpr.iterateNext()))
 
217
        browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
 
218
}
237
219
interactive("reddit-vote-up",
238
220
            "Vote the currently selected link up.",
239
 
            reddit_mod_up);
240
 
 
 
221
            reddit_vote_up);
 
222
 
 
223
 
 
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);
 
230
    let link;
 
231
    if (xpr && (link = xpr.iterateNext()))
 
232
        browser_object_follow(I.buffer, FOLLOW_DEFAULT, link);
 
233
}
241
234
interactive("reddit-vote-down",
242
235
            "Vote the currently selected link down.",
243
 
            reddit_mod_down);
244
 
 
245
 
/* Creating the keymap */
246
 
define_keymap("reddit_keymap", $parent = content_buffer_normal_keymap);
 
236
            reddit_vote_down);
 
237
 
 
238
 
 
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());
 
246
    });
 
247
 
 
248
 
 
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");
251
 
 
252
 
define_key(reddit_keymap, "o", "reddit-open-current");
253
254
define_key(reddit_keymap, "h", "reddit-open-comments");
254
255
 
255
 
/* Setting up and tearing down the mode */
256
 
 
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);
263
 
}
264
 
 
265
 
function disable_reddit_mode(buffer) {
266
 
  var doc = buffer.document;
267
 
 
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);
271
 
}
272
 
 
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);
 
263
}
 
264
 
 
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",
 
272
                                  "copy"]) {
 
273
                         for each (var c in cmds) {
 
274
                             buffer.default_browser_object_classes[c] =
 
275
                                 browser_object_reddit_current;
 
276
                         }
 
277
                     }
 
278
                     buffer.modalities.push(reddit_modality);
 
279
                 },
 
280
                 $disable = function (buffer) {
 
281
                     var i = buffer.modalities.indexOf(reddit_modality);
 
282
                     if (i > -1)
 
283
                         buffer.modalities.splice(i, 1);
 
284
                 },
276
285
                 $doc = "reddit page-mode: keyboard navigation for reddit.");
277
286
 
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]);
 
289
};