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
20 // This is activated (and canceled) by pressing the ^C
21 // Press L (first) to switch to one-hand mode
23 // I was getting some very funky return values from String.fromCharCode() for
24 // punctuation keys so I made my own table. You might need to change this for
25 // your computer/keyboard. You'll need to have every character in "charset" in
64 '76': 'l' // switch to one-hand mode
67 //Just some shortcuts and globals
68 var charset = 'thsnd-rcgmvwb/;789aefijkopquxyz234'; // update key_to_char if you add to this
69 var uzblid = 'uzbl_link_hint';
70 var uzbldivid = uzblid + '_div_container';
73 var links = document.links;
74 var forms = document.forms;
76 //Calculate element position to draw the hint
77 //Pretty accurate but on fails in some very fancy cases
78 function element_position(el) {
79 var x = el.offsetLeft;
81 var width = el.offsetWidth;
82 var height = el.offsetHeight;
83 while (el.offsetParent) {
88 return { x: x, y: y, width: width, height: height };
91 // Simulate a click on the element
92 function click_element(target, options) {
93 var event = target.ownerDocument.createEvent('MouseEvents');
94 var pos = element_position(target);
95 options = options || {};
99 options.type || 'click',
100 options.canBubble || true,
101 options.cancelable || true,
102 options.view || target.ownerDocument.defaultView,
104 options.screenX || pos.x - window.pageXOffset,
105 options.screenY || pos.y - window.pageYOffset,
106 options.clientX || pos.x,
107 options.clientY || pos.y,
108 options.ctrlKey || false,
109 options.altKey || false,
110 options.shiftKey || false,
111 options.metaKey || false,
112 options.button || 0, //0 = left, 1 = middle, 2 = right
113 options.relatedTarget || null
116 target.dispatchEvent(event);
119 //Calculate if an element is visible
120 function isVisible(el) {
127 if (!el.parentNode) {
131 if (el.style.display == 'none') {
134 if (el.style.visibility == 'hidden') {
138 return isVisible(el.parentNode);
140 //Calculate if an element is on the viewport.
141 function elementInViewport(el) {
142 var pos = element_position(el);
143 return pos.y < window.pageYOffset + window.innerHeight && pos.x < window.pageXOffset + window.innerWidth && (pos.y + pos.height) > window.pageYOffset && (pos.x + pos.width) > window.pageXOffset;
145 //Removes all hints/leftovers that might be generated
147 function removeAllHints() {
148 var elements = doc.getElementById(uzbldivid);
150 elements.parentNode.removeChild(elements);
153 //Generate a hint for an element with the given label
154 //Here you can play around with the style of the hints!
155 function generateHint(el, label) {
156 var pos = element_position(el);
157 var hint = doc.createElement('div');
158 hint.setAttribute('name', uzblid);
159 hint.innerText = label;
160 hint.style.display = 'inline';
161 hint.style.backgroundColor = '#B9FF00';
162 hint.style.border = '2px solid #4A6600';
163 hint.style.color = 'black';
164 hint.style.fontSize = '9px';
165 hint.style.fontWeight = 'bold';
166 hint.style.lineHeight = '9px';
167 hint.style.margin = '0px';
168 hint.style.width = 'auto'; // fix broken rendering on w3schools.com
169 hint.style.padding = '1px';
170 hint.style.position = 'absolute';
171 hint.style.zIndex = '1000';
172 hint.style.textTransform = 'uppercase';
173 hint.style.left = Math.max(-1, (pos.x - (7 + label.length * 9))) + 'px';
174 hint.style.top = (pos.y + 1) + 'px';
175 var img = el.getElementsByTagName('img');
176 //if (img.length > 0) {
177 //hint.style.top = pos.x + img[0].height / 2 - 6 + 'px';
179 hint.style.textDecoration = 'none';
180 // hint.style.webkitBorderRadius = '6px'; // slow
184 // This function takes action on the element chosen by entering the "numbered
185 // link" shortcut key(s)
186 function do_element(item) {
189 var name = item.tagName;
192 window.location = item.href;
193 } else if (name == 'INPUT') {
194 var type = (item.getAttribute('type') || 'text').toLowerCase();
197 item.checked = !item.checked;
206 item.click(); // only tested on submit buttons
208 case 'file': // don't think js can activate this, so focus
217 // give it keyboard focus
222 } else if (name == 'TEXTAREA' || name == 'SELECT') {
226 // as a catch-all, try simulating a mouse click on that element
228 window.location = item.href;
233 //Returns a list of all links (in this version
234 //just the elements itself, but in other versions, we
235 //add the label here.
236 function addLinks() {
238 for (var l = 0; l < links.length; l++) {
240 if (isVisible(li) && elementInViewport(li)) {
246 //Same as above, just for the form elements
247 function addFormElems() {
249 for (var f = 0; f < forms.length; f++) {
250 for (var e = 0; e < forms[f].elements.length; e++) {
251 var el = forms[f].elements[e];
252 if (el && ['INPUT', 'TEXTAREA', 'SELECT'].indexOf(el.tagName) + 1 && isVisible(el) && elementInViewport(el)) {
259 //Draw all hints for all elements passed. "len" is for
260 //the number of chars we should use to avoid collisions
261 function reDrawHints(elems, chars) {
263 var hintdiv = doc.createElement('div');
264 hintdiv.setAttribute('id', uzbldivid);
265 for (var i = 0; i < elems[0].length; i++) {
267 var label = elems[1][i].substring(chars);
268 var h = generateHint(elems[0][i], label);
269 hintdiv.appendChild(h);
273 document.body.appendChild(hintdiv);
276 // pass: number of keys
277 // returns: key length
278 function labelLength(n) {
284 n -= 1; // our highest key will be n-1
287 n = Math.floor(n / charset.length);
293 function intToLabel(n) {
296 label = charset.charAt(n % charset.length) + label;
297 n = Math.floor(n / charset.length);
303 function labelToInt(label) {
306 for(i = 0; i < label.length; ++i) {
308 n += charset.indexOf(label[i]);
312 //Put it all together
313 function followLinks(follow) {
314 if(follow.charAt(0) == 'l') {
315 follow = follow.substr(1);
316 charset = 'thsnlrcgfdbmwvz-/';
318 var s = follow.split('');
319 var linknr = labelToInt(follow);
320 var linkelems = addLinks();
321 var formelems = addFormElems();
322 var elems = [linkelems[0].concat(formelems[0]), linkelems[1].concat(formelems[1])];
323 var len = labelLength(elems[0].length);
324 var oldDiv = doc.getElementById(uzbldivid);
325 var leftover = [[], []];
326 if (s.length == len && linknr < elems[0].length && linknr >= 0) {
327 do_element(elems[0][linknr]);
331 for (var j = 0; j < elems[0].length; j++) {
333 var label = intToLabel(j);
334 var n = label.length;
335 for (n; n < len; n++) {
336 label = charset.charAt(0) + label;
338 for (var k = 0; k < s.length; k++) {
339 b = b && label.charAt(k) == s[k];
342 leftover[0].push(elems[0][j]);
343 leftover[1].push(label);
346 reDrawHints(leftover, s.length);
350 // from your event handler you can: return stop_event(e)
351 function stop_event(e) {
356 // return false; // IE-compat
361 document.addEventListener(
364 // [de]activate on ^C
367 (e.ctrlKey && e.keyCode == 67)
368 || (e.keyCode == 27 && active == 1)
377 return stop_event(e);
379 if(active == 1 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
380 if(key_to_char[e.keyCode]) {
381 got += key_to_char[e.keyCode];
383 return stop_event(e);
386 // general keybinding, unrelated to numbered links
387 var active_type = (document.activeElement.type || 'a').toLowerCase();
389 if (document.activeElement == document.body) {
392 switch ((document.activeElement.type || 'a').toLowerCase()) {
403 var c = key_to_char[e.keyCode];
406 window.scrollBy(0, -200);
410 window.scrollBy(0, 200);
433 } else if (e.keyCode == 27) {
435 document.activeElement.blur();