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.
8 // @name numbered links
9 // @namespace http://patcavit.com/greasemonkey
10 // @description make all links followable with the keyboard
18 // This is activated (and canceled) by pressing the ^C
19 // Press L (first) to switch to one-hand mode
21 // I was getting some very funky return values from String.fromCharCode() for
22 // punctuation keys so I made my own table. You might need to change this for
23 // your computer/keyboard. You'll need to have every character in "charset" in
62 '76': 'l' // switch to one-hand mode
65 //Just some shortcuts and globals
66 var charset = 'thsnd-rcgmvwb/;789aefijkopquxyz234'; // update key_to_char if you add to this
67 var uzblid = 'uzbl_link_hint';
68 var uzbldivid = uzblid + '_div_container';
71 var links = document.links;
72 var forms = document.forms;
73 //Make onlick-links "clickable"
75 HTMLElement.prototype.click = function() {
76 if (typeof this.onclick == 'function') {
83 //Calculate element position to draw the hint
84 //Pretty accurate but on fails in some very fancy cases
85 function elementPosition(el) {
86 var up = el.offsetTop;
87 var left = el.offsetLeft;
88 var width = el.offsetWidth;
89 var height = el.offsetHeight;
90 while (el.offsetParent) {
93 left += el.offsetLeft;
95 return [up, left, width, height];
97 //Calculate if an element is visible
98 function isVisible(el) {
105 if (!el.parentNode) {
109 if (el.style.display == 'none') {
112 if (el.style.visibility == 'hidden') {
116 return isVisible(el.parentNode);
118 //Calculate if an element is on the viewport.
119 function elementInViewport(el) {
120 offset = elementPosition(el);
122 var left = offset[1];
123 var width = offset[2];
124 var height = offset[3];
125 return up < window.pageYOffset + window.innerHeight && left < window.pageXOffset + window.innerWidth && (up + height) > window.pageYOffset && (left + width) > window.pageXOffset;
127 //Removes all hints/leftovers that might be generated
129 function removeAllHints() {
130 var elements = doc.getElementById(uzbldivid);
132 elements.parentNode.removeChild(elements);
135 //Generate a hint for an element with the given label
136 //Here you can play around with the style of the hints!
137 function generateHint(el, label) {
138 var pos = elementPosition(el);
139 var hint = doc.createElement('div');
140 hint.setAttribute('name', uzblid);
141 hint.innerText = label;
142 hint.style.display = 'inline';
143 hint.style.backgroundColor = '#B9FF00';
144 hint.style.border = '2px solid #4A6600';
145 hint.style.color = 'black';
146 hint.style.fontSize = '9px';
147 hint.style.fontWeight = 'bold';
148 hint.style.lineHeight = '9px';
149 hint.style.margin = '0px';
150 hint.style.width = 'auto'; // fix broken rendering on w3schools.com
151 hint.style.padding = '1px';
152 hint.style.position = 'absolute';
153 hint.style.zIndex = '1000';
154 hint.style.textTransform = 'uppercase';
155 hint.style.left = Math.max(-1, (pos[1] - (7 + label.length * 9))) + 'px';
156 hint.style.top = (pos[0] + 1) + 'px';
157 var img = el.getElementsByTagName('img');
158 //if (img.length > 0) {
159 //hint.style.top = pos[1] + img[0].height / 2 - 6 + 'px';
161 hint.style.textDecoration = 'none';
162 // hint.style.webkitBorderRadius = '6px'; // slow
165 //Here we choose what to do with an element if we
166 //want to "follow" it. On form elements we "select"
167 //or pass the focus, on links we try to perform a click,
168 //but at least set the href of the link. (needs some improvements)
169 function clickElem(item) {
172 var name = item.tagName;
175 window.location = item.href;
176 } else if (name == 'INPUT') {
177 var type = item.getAttribute('type').toUpperCase();
178 if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') {
184 } else if (name == 'TEXTAREA' || name == 'SELECT') {
189 window.location = item.href;
193 //Returns a list of all links (in this version
194 //just the elements itself, but in other versions, we
195 //add the label here.
196 function addLinks() {
198 for (var l = 0; l < links.length; l++) {
200 if (isVisible(li) && elementInViewport(li)) {
206 //Same as above, just for the form elements
207 function addFormElems() {
209 for (var f = 0; f < forms.length; f++) {
210 for (var e = 0; e < forms[f].elements.length; e++) {
211 var el = forms[f].elements[e];
212 if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) {
219 //Draw all hints for all elements passed. "len" is for
220 //the number of chars we should use to avoid collisions
221 function reDrawHints(elems, chars) {
223 var hintdiv = doc.createElement('div');
224 hintdiv.setAttribute('id', uzbldivid);
225 for (var i = 0; i < elems[0].length; i++) {
227 var label = elems[1][i].substring(chars);
228 var h = generateHint(elems[0][i], label);
229 hintdiv.appendChild(h);
233 document.body.appendChild(hintdiv);
236 // pass: number of keys
237 // returns: key length
238 function labelLength(n) {
244 n -= 1; // our highest key will be n-1
247 n = Math.floor(n / charset.length);
253 function intToLabel(n) {
256 label = charset.charAt(n % charset.length) + label;
257 n = Math.floor(n / charset.length);
263 function labelToInt(label) {
266 for(i = 0; i < label.length; ++i) {
268 n += charset.indexOf(label[i]);
272 //Put it all together
273 function followLinks(follow) {
274 if(follow.charAt(0) == 'l') {
275 follow = follow.substr(1);
276 charset = 'thsnlrcgfdbmwvz-/';
278 var s = follow.split('');
279 var linknr = labelToInt(follow);
280 var linkelems = addLinks();
281 var formelems = addFormElems();
282 var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])];
283 var len = labelLength(elems[0].length);
284 var oldDiv = doc.getElementById(uzbldivid);
285 var leftover = [[], []];
286 if (s.length == len && linknr < elems[0].length && linknr >= 0) {
287 clickElem(elems[0][linknr]);
291 for (var j = 0; j < elems[0].length; j++) {
293 var label = intToLabel(j);
294 var n = label.length;
295 for (n; n < len; n++) {
296 label = charset.charAt(0) + label;
298 for (var k = 0; k < s.length; k++) {
299 b = b && label.charAt(k) == s[k];
302 leftover[0].push(elems[0][j]);
303 leftover[1].push(label);
306 reDrawHints(leftover, s.length);
335 // from your event handler you can: return stop_event(e)
336 function stop_event(e) {
341 // return false; // IE-compat
346 document.addEventListener(
349 // [de]activate on ^C
352 (e.ctrlKey && e.keyCode == 67)
353 || (e.keyCode == 27 && active == 1)
362 return stop_event(e);
364 if(active == 1 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
365 if(key_to_char[e.keyCode]) {
366 got += key_to_char[e.keyCode];
368 return stop_event(e);