JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
ec51ef0cd653d29f5a36d866cb1a5f410f6e6729
[userscripts.git] / numbered_links.user.js
1 /*
2 *   This script is derived (by Jason Woofenden) from an example script from
3 *   uzbl (see uzbl.org) and is thus presumably licensed under the GNU GPLv3. In
4 *   any case, Jason's changes are in the public domain.
5 */
6
7 // ==UserScript==
8 // @name          numbered links
9 // @namespace     http://patcavit.com/greasemonkey
10 // @description   make all links followable with the keyboard
11 // @include       http://*
12 // ==/UserScript==
13
14 (function() 
15 {
16
17
18 // This is activated (and canceled) by pressing the `/~ key. To change it to a
19 // differet key, edit the ascii value on line 301
20 // 
21 // Also, you can change the character set a few lines down. Don't use the "L"
22 // key unless you change the on line 235.
23
24
25
26 //Just some shortcuts and globals
27 var charset = 'thsnd-rcgmvwb/;789aefijkopquxyz234';
28 var uzblid = 'uzbl_link_hint';
29 var uzbldivid = uzblid + '_div_container';
30 var doc = document;
31 var win = window;
32 var links = document.links;
33 var forms = document.forms;
34 //Make onlick-links "clickable"
35 try {
36     HTMLElement.prototype.click = function() {
37         if (typeof this.onclick == 'function') {
38             this.onclick({
39                 type: 'click'
40             });
41         }
42     };
43 } catch(e) {}
44 //Calculate element position to draw the hint
45 //Pretty accurate but on fails in some very fancy cases
46 function elementPosition(el) {
47     var up = el.offsetTop;
48     var left = el.offsetLeft;
49     var width = el.offsetWidth;
50     var height = el.offsetHeight;
51     while (el.offsetParent) {
52         el = el.offsetParent;
53         up += el.offsetTop;
54         left += el.offsetLeft;
55     }
56     return [up, left, width, height];
57 }
58 //Calculate if an element is visible
59 function isVisible(el) {
60     if (el == doc) {
61         return true;
62     }
63     if (!el) {
64         return false;
65     }
66     if (!el.parentNode) {
67         return false;
68     }
69     if (el.style) {
70         if (el.style.display == 'none') {
71             return false;
72         }
73         if (el.style.visibility == 'hidden') {
74             return false;
75         }
76     }
77     return isVisible(el.parentNode);
78 }
79 //Calculate if an element is on the viewport.
80 function elementInViewport(el) {
81     offset = elementPosition(el);
82     var up = offset[0];
83     var left = offset[1];
84     var width = offset[2];
85     var height = offset[3];
86     return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset;
87 }
88 //Removes all hints/leftovers that might be generated
89 //by this script.
90 function removeAllHints() {
91     var elements = doc.getElementById(uzbldivid);
92     if (elements) {
93         elements.parentNode.removeChild(elements);
94     }
95 }
96 //Generate a hint for an element with the given label
97 //Here you can play around with the style of the hints!
98 function generateHint(el, label) {
99     var pos = elementPosition(el);
100     var hint = doc.createElement('div');
101     hint.setAttribute('name', uzblid);
102     hint.innerText = label;
103     hint.style.display = 'inline';
104     hint.style.backgroundColor = '#B9FF00';
105     hint.style.border = '2px solid #4A6600';
106     hint.style.color = 'black';
107     hint.style.fontSize = '9px';
108     hint.style.fontWeight = 'bold';
109     hint.style.lineHeight = '9px';
110     hint.style.margin = '0px';
111     hint.style.width = 'auto'; // fix broken rendering on w3schools.com
112     hint.style.padding = '1px';
113     hint.style.position = 'absolute';
114     hint.style.zIndex = '1000';
115     hint.style.textTransform = 'uppercase';
116     hint.style.left = Math.max(-1, (pos[1] - (7 + label.length * 9))) + 'px';
117     hint.style.top = (pos[0] + 1) + 'px';
118     var img = el.getElementsByTagName('img');
119     //if (img.length > 0) {
120         //hint.style.top = pos[1] + img[0].height / 2 - 6 + 'px';
121     //}
122     hint.style.textDecoration = 'none';
123     // hint.style.webkitBorderRadius = '6px'; // slow
124     return hint;
125 }
126 //Here we choose what to do with an element if we
127 //want to "follow" it. On form elements we "select"
128 //or pass the focus, on links we try to perform a click,
129 //but at least set the href of the link. (needs some improvements)
130 function clickElem(item) {
131     removeAllHints();
132     if (item) {
133         var name = item.tagName;
134         if (name == 'A') {
135             item.click();
136             window.location = item.href;
137         } else if (name == 'INPUT') {
138             var type = item.getAttribute('type').toUpperCase();
139             if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') {
140                 item.focus();
141                 item.select();
142             } else {
143                 item.click();
144             }
145         } else if (name == 'TEXTAREA' || name == 'SELECT') {
146             item.focus();
147             item.select();
148         } else {
149             item.click();
150             window.location = item.href;
151         }
152     }
153 }
154 //Returns a list of all links (in this version
155 //just the elements itself, but in other versions, we
156 //add the label here.
157 function addLinks() {
158     res = [[], []];
159     for (var l = 0; l < links.length; l++) {
160         var li = links[l];
161         if (isVisible(li) && elementInViewport(li)) {
162             res[0].push(li);
163         }
164     }
165     return res;
166 }
167 //Same as above, just for the form elements
168 function addFormElems() {
169     res = [[], []];
170     for (var f = 0; f < forms.length; f++) {
171         for (var e = 0; e < forms[f].elements.length; e++) {
172             var el = forms[f].elements[e];
173             if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) {
174                 res[0].push(el);
175             }
176         }
177     }
178     return res;
179 }
180 //Draw all hints for all elements passed. "len" is for
181 //the number of chars we should use to avoid collisions
182 function reDrawHints(elems, chars) {
183     removeAllHints();
184     var hintdiv = doc.createElement('div');
185     hintdiv.setAttribute('id', uzbldivid);
186     for (var i = 0; i < elems[0].length; i++) {
187         if (elems[0][i]) {
188             var label = elems[1][i].substring(chars);
189             var h = generateHint(elems[0][i], label);
190             hintdiv.appendChild(h);
191         }
192     }
193     if (document.body) {
194         document.body.appendChild(hintdiv);
195     }
196 }
197 // pass: number of keys
198 // returns: key length
199 function labelLength(n) {
200         var oldn = n;
201         var keylen = 0;
202         if(n < 2) {
203                 return 1;
204         }
205         n -= 1; // our highest key will be n-1
206         while(n) {
207                 keylen += 1;
208                 n = Math.floor(n / charset.length);
209         }
210         return keylen;
211 }
212 // pass: number
213 // returns: label
214 function intToLabel(n) {
215         var label = '';
216         do {
217                 label = charset.charAt(n % charset.length) + label;
218                 n = Math.floor(n / charset.length);
219         } while(n);
220         return label;
221 }
222 // pass: label
223 // returns: number
224 function labelToInt(label) {
225         var n = 0;
226         var i;
227         for(i = 0; i < label.length; ++i) {
228                 n *= charset.length;
229                 n += charset.indexOf(label[i]);
230         }
231         return n;
232 }
233 //Put it all together
234 function followLinks(follow) {
235     if(follow.charAt(0) == 'l') {
236         follow = follow.substr(1);
237         charset = 'thsnlrcgfdbmwvz-/';
238     }
239     var s = follow.split('');
240     var linknr = labelToInt(follow);
241     var linkelems = addLinks();
242     var formelems = addFormElems();
243     var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])];
244     var len = labelLength(elems[0].length);
245     var oldDiv = doc.getElementById(uzbldivid);
246     var leftover = [[], []];
247     if (s.length == len && linknr < elems[0].length && linknr >= 0) {
248         clickElem(elems[0][linknr]);
249         got = '';
250         active = 0;
251     } else {
252         for (var j = 0; j < elems[0].length; j++) {
253             var b = true;
254             var label = intToLabel(j);
255             var n = label.length;
256             for (n; n < len; n++) {
257                 label = charset.charAt(0) + label;
258             }
259             for (var k = 0; k < s.length; k++) {
260                 b = b && label.charAt(k) == s[k];
261             }
262             if (b) {
263                 leftover[0].push(elems[0][j]);
264                 leftover[1].push(label);
265             }
266         }
267         reDrawHints(leftover, s.length);
268     }
269 }
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296         var active = 0;
297         var got = '';
298         document.addEventListener(
299                 'keypress',
300                 function(e) {
301                         if(e.keyCode == 96) {  // change this if you want a different activation key
302                                 if(active) {
303                                         got = '';
304                                         removeAllHints();
305                                 } else {
306                                         followLinks(got);
307                                 }
308                                 active = 1 - active;
309                                 return;
310                         } else {
311                                 if(active == 1) {
312                                         got += String.fromCharCode(e.keyCode);
313                                         followLinks(got);
314                                 }
315                         }
316                 },
317                 true);
318 })();