1 // link follower for uzbl
2 // requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059
4 // first, it needs to be loaded before every time it is used.
5 // One way would be to use something like load_start_handler to send
6 // "act script /usr/share/examples/scripts/linkfollow.js"
7 // (currently, it is recommended to use load_finish_handler since the JS code seems to get
8 // flushed. Using a load_start_handler with a 1s delay works but not always)
10 // when script is loaded, it can be invoked with
11 // bind f* = js hints.set("%s")
12 // bind f_ = js hints.follow("%s")
14 // At the moment, it may be useful to have way of forcing uzbl to load the script
15 // bind :lf = script /usr/share/examples/scripts/linkfollow.js
17 // To enable hint highlighting, add:
18 // set stylesheet_uri = /usr/share/uzbl/examples/data/style.css
20 // based on follow_Numbers.js
22 // TODO: set CSS styles (done, but not working properly)
23 // TODO: load the script as soon as the DOM is ready
28 var uzblid = 'uzbl_hint';
29 var uzblclass = 'uzbl_highlight';
30 var uzblclassfirst = 'uzbl_h_first';
33 this.follow = followHint;
34 this.keyPressHandler = keyPressHandler;
36 function hasClass(ele,cls) {
37 return ele.className.split(/ /).some(function (n) { return n == cls });
40 function addClass(ele,cls) {
41 if (!hasClass(ele,cls)) ele.className += " "+cls;
44 function removeClass(ele,cls) {
45 ele.className = ele.className.split(/ /).filter(function (n) { n != cls}).join(" ");
48 function elementPosition(el) {
49 var up = el.offsetTop;
50 var left = el.offsetLeft; var width = el.offsetWidth;
51 var height = el.offsetHeight;
53 while (el.offsetParent) {
56 left += el.offsetLeft;
58 return [up, left, width, height];
61 function generateHint(pos, label) {
62 var hint = doc.createElement('div');
63 hint.setAttribute('name', uzblid);
64 hint.innerText = label;
65 //the css is set with ./examples/data/style.css
66 hint.style.left = pos[1] + 'px';
67 hint.style.top = pos[0] + 'px';
68 //var img = el.getElementsByTagName('img');
69 //if (img.length > 0) {
70 // hint.style.left = pos[1] + img[0].width / 2 + 'px';
75 function elementInViewport(offset) {
78 var width = offset[2];
79 var height = offset[3];
80 return (up < window.pageYOffset + window.innerHeight &&
81 left < window.pageXOffset + window.innerWidth
82 && (up + height) > window.pageYOffset
83 && (left + width) > window.pageXOffset);
86 function isVisible(el) {
87 if (el == doc) { return true; }
88 if (!el) { return false; }
89 if (!el.parentNode) { return false; }
91 if (el.style.display == 'none') {
94 if (el.style.visibility == 'hidden') {
98 return isVisible(el.parentNode);
101 var hintable = "//a[@href] | //img | //input";
103 function Matcher(str){
104 var numbers = str.replace(/[^\d]/g,"");
105 var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,"i")});
107 this.toString = toString;
108 this.numbers = numbers;
109 function test(element) {
110 // test all the regexp
111 return words.every(function (regex) { return element.textContent.match(regex)});
114 return "{"+numbers+"},{"+words+"}";
119 function setHints(r){
120 if(doc.body) doc.body.onkeyup = this.keyPressHandler;
121 var re = new Matcher(r);
123 var hintdiv = doc.createElement('div');
124 hintdiv.setAttribute('id', uzblid);
126 var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
127 for (var i = 0; i < items.snapshotLength;i++){
128 var item = items.snapshotItem(i);
129 var pos = elementPosition(item);
130 if(re.test(item) && isVisible(item) && elementInViewport(pos)){
131 var h = generateHint(pos,c);
132 h.href = function () {return item};
133 hintdiv.appendChild(h);
135 addClass(item,uzblclassfirst);
136 // addClass(item,uzblclass);
138 addClass(item,uzblclass);
144 document.body.insertBefore(hintdiv,document.body.firstChild);
148 function clearHints(){
149 var hintdiv = doc.getElementById(uzblid);
151 hintdiv.parentNode.removeChild(hintdiv);
153 var first = doc.getElementsByClassName(uzblclassfirst)[0];
155 removeClass(first,uzblclassfirst);
157 // TODO: not all class attributes get removed
158 var items = doc.getElementsByClassName(uzblclass);
159 for (var i = 0; i<items.length; i++){
160 removeClass(items[i],uzblclass);
165 function keyPressHandler(e) {
166 var kC = window.event ? event.keyCode: e.keyCode;
167 var Esc = window.event ? 27 : e.DOM_VK_ESCAPE;
170 doc.body.removeAttribute("onkeyup");
173 function followHint(follow){
174 var m = new Matcher(follow);
175 var elements = doc.getElementsByClassName(uzblclass);
178 matched.push(doc.getElementsByClassName(uzblclassfirst)[0]);
179 for (var i = 0; i < elements.length;i++){
180 if(m.test(elements[i])){
181 matched.push(elements[i]);
185 var n = parseInt(m.numbers,10);
187 var item = matched[n-1];
189 var item = matched[0];
192 item.style.borderStyle = "dotted";
193 item.style.borderWidth = "thin";
195 var name = item.tagName;
197 if(item.click) {item.click()};
198 window.location = item.href;
199 } else if (name == 'INPUT') {
200 var type = item.getAttribute('type').toUpperCase();
201 if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') {
207 } else if (name == 'TEXTAREA' || name == 'SELECT') {
212 window.location = item.href;
218 //document.addEventListener("DOMContentLoaded",function () { hints = new Hints()},false);
220 var hints = new Hints();