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 the load_commit_handler:
6 // set load_commit_handler = sh 'echo "script /usr/share/uzbl/examples/scripts/linkfollow.js" > "$4"'
8 // when script is loaded, it can be invoked with
9 // bind f* = js hints.set("%s", hints.open)
10 // bind f_ = js hints.follow("%s",hints.open)
12 // At the moment, it may be useful to have way of forcing uzbl to load the script
13 // bind :lf = script /usr/share/uzbl/examples/scripts/linkfollow.js
15 // The default style for the hints are pretty ugly, so it is recommended to add the following
17 // set stylesheet_uri = /usr/share/uzbl/examples/data/style.css
19 // based on follow_Numbers.js
21 // TODO: fix styling for the first element
22 // TODO: emulate mouseover events when visiting some elements
23 // TODO: rewrite the element->action handling
29 ////////////////////////////////////////////////////////////////////////////
31 // if set to true, you must explicitly call hints.follow(), otherwise it will
32 // follow the link if there is only one matching result
33 var requireReturn = true;
35 // Case sensitivity flag
38 // For case sensitive matching, uncomment:
39 // var matchCase = "";
42 var uzblid = 'uzbl_hint';
43 var uzblclass = 'uzbl_highlight';
44 var uzblclassfirst = 'uzbl_h_first';
51 this.keyPressHandler = keyPressHandler;
53 function elementPosition(el) {
54 var up = el.offsetTop;
55 var left = el.offsetLeft; var width = el.offsetWidth;
56 var height = el.offsetHeight;
58 while (el.offsetParent) {
61 left += el.offsetLeft;
63 return {up: up, left: left, width: width, height: height};
66 function elementInViewport(p) {
67 return (p.up < window.pageYOffset + window.innerHeight &&
68 p.left < window.pageXOffset + window.innerWidth &&
69 (p.up + p.height) > window.pageYOffset &&
70 (p.left + p.width) > window.pageXOffset);
73 function isVisible(el) {
74 if (el == doc) { return true; }
75 if (!el) { return false; }
76 if (!el.parentNode) { return false; }
78 if (el.style.display == 'none') {
81 if (el.style.visibility == 'hidden') {
85 return isVisible(el.parentNode);
88 // the vimperator defaults minus the xhtml elements, since it gave DOM errors
89 var hintable = " //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select";
91 function Matcher(str){
92 var numbers = str.replace(/[^\d]/g,"");
93 var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,matchCase)});
95 this.toString = toString;
96 this.numbers = numbers;
97 function matchAgainst(element){
98 if(element.node.nodeName == "INPUT"){
99 return element.node.value;
101 return element.node.textContent;
104 function test(element) {
105 // test all the regexp
106 var item = matchAgainst(element);
107 return words.every(function (regex) { return item.match(regex)});
111 function HintElement(node,pos){
114 this.isHinted = false;
118 this.addHint = function (labelNum) {
119 // TODO: fix uzblclassfirst
121 this.node.className += " " + uzblclass;
123 this.isHinted = true;
126 var hintNode = doc.createElement('div');
127 hintNode.name = uzblid;
128 hintNode.innerText = labelNum;
129 hintNode.style.left = this.position.left + 'px';
130 hintNode.style.top = this.position.up + 'px';
131 hintNode.style.position = "absolute";
132 doc.body.firstChild.appendChild(hintNode);
135 this.removeHint = function(){
137 var s = (this.num)?uzblclassfirst:uzblclass;
138 this.node.className = this.node.className.replace(new RegExp(" "+s,"g"),"");
139 this.isHinted = false;
144 function createHintDiv(){
145 var hintdiv = doc.getElementById(uzblid);
147 hintdiv.parentNode.removeChild(hintdiv);
149 hintdiv = doc.createElement("div");
150 hintdiv.setAttribute('id',uzblid);
151 doc.body.insertBefore(hintdiv,doc.body.firstChild);
157 doc.body.setAttribute("onkeyup","hints.keyPressHandler(event)");
158 hintdiv = createHintDiv();
161 var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
162 for (var i = 0;i<items.snapshotLength;i++){
163 var item = items.snapshotItem(i);
164 var pos = elementPosition(item);
165 if(isVisible && elementInViewport(elementPosition(item))){
166 visible.push(new HintElement(item,pos));
173 visible.forEach(function (n) { n.removeHint(); } );
174 hintdiv = doc.getElementById(uzblid);
176 hintdiv.parentNode.removeChild(hintdiv);
177 hintdiv = doc.getElementById(uzblid);
181 function update(str,openFun) {
182 var match = new Matcher(str);
183 hintdiv = createHintDiv();
185 visible.forEach(function (n) {
193 if(i==2){ //only been incremented once
199 function hint(str,openFun){
200 if(str.length == 0) init();
204 function keyPressHandler(e) {
205 var kC = window.event ? event.keyCode: e.keyCode;
206 var Esc = window.event ? 27 : e.DOM_VK_ESCAPE;
209 doc.body.removeAttribute("onkeyup");
213 this.openNewWindow = function(item){
214 // TODO: this doesn't work yet
215 item.className += " uzbl_follow";
216 window.open(item.href,"uzblnew","");
218 this.open = function(item){
219 simulateMouseOver(item);
220 item.className += " uzbl_follow";
221 window.location = item.href;
224 function simulateMouseOver(item){
225 var evt = doc.createEvent("MouseEvents");
226 evt.initMouseEvent("MouseOver",true,true,
227 doc.defaultView,1,0,0,0,0,
228 false,false,false,false,0,null);
229 return item.dispatchEvent(evt);
233 function follow(str,openFunction){
234 var m = new Matcher(str);
235 var items = visible.filter(function (n) { return n.isHinted });
237 var num = parseInt(m.numbers,10);
239 var item = items[num-1].node;
241 var item = items[0].node;
244 var name = item.tagName;
246 if(item.click) {item.click()};
248 } else if (name == 'INPUT') {
249 var type = item.getAttribute('type').toUpperCase();
250 if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') {
256 } else if (name == 'TEXTAREA' || name == 'SELECT') {
267 var hints = new Hints();