Skip to content

Instantly share code, notes, and snippets.

@edvakf
Created December 17, 2008 00:00
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edvakf/36875 to your computer and use it in GitHub Desktop.
Save edvakf/36875 to your computer and use it in GitHub Desktop.
javascript: (function(_HaH) {
var originalTitle = document.title;
var hintKeys = new String('asdfghjkl');
var choices = [];
var choice = '';
var xpath = '//a[@href]|//input[not(@type=\x22hidden\x22)]|//textarea|//select|//img[@onclick]|//button';
xpath += '|' + xpath.replace(/\/\//g, '//xhtml:');
var defaultType = 'HaHxlink';
function defaultAction(e) {
e.focus();
var ev = document.createEvent('MouseEvents');
ev.initEvent('click', null, null);
e.dispatchEvent(ev)
}
var HaHElements = [{
accessor: function(e) {
var tag = e.tagName.toLowerCase();
return ((tag == 'a' && /^(#|javascript:void\(0\);?)$/.test(e.href)) || /^(textarea|button|select|input|img)$/.test(tag));
},
type:defaultType,
action:defaultAction
},
{
accessor: function(e) {
return e.tagName.toLowerCase() == 'a';
},
type: 'HaHlink',
action: (window.opera)?defaultAction:function(e){if(e.href)location.href = e.href;}
}];
if (_HaH && _HaH.types) {
_HaH.types.forEach(function(item) {
if (item.xpath) xpath += '|' + item.xpath;
HaHElements.unshift({
accessor: item.accessor,
type: item.type || defaultType,
action: item.action || defaultAction,
color: item.color,
})
})
}
function addCSS() {
var style = document.getElementById('HaH-style-element');
var d = '#HaH-div-element';
var cssText = [d, 'span{font-size:10pt; padding:0pt \x20 1pt; margin:0; line-height:10pt; position:absolute; z-index:2147483647; opacity:.7; color:#000;}', d, '.HaHlink{background-color:#FF0;}', d, '.HaHxlink{background-color:#0FF;}', d, '.HaHselected{background-color:#F0F;}'].join('\x20');
HaHElements.forEach(function(item){if(item.color)cssText+=d+'\x20.'+item.type+'{background-color:'+item.color+';}'});
if (style) {
if(style.innerHTML!=cssText)style.innerHTML=cssText;
}else{
style = document.createElement('style');
style.type = 'text/css';
style.id = 'HaH-style-element';
style.innerHTML = cssText;
document.getElementsByTagName('head')[0].appendChild(style);
}
}
hints = [];
var df = document.createDocumentFragment();
function castHint(_ele, _x, _y) {
for (var i = 0; i < HaHElements.length; i++) {
if (HaHElements[i].accessor(_ele)) {
var _type = HaHElements[i].type;
var _action = HaHElements[i].action;
break;
}
}
if (!_type) return;
if (!hints.length) addCSS();
var _text = createText(hints.length);
var hint = {
element: _ele,
type: _type,
action: _action,
text: _text,
x: _x,
y: _y
};
hint.hintNode = addHintNode(hint);
hints[hints.length] = hint;
}
function addHintNode(hint) {
var span = document.createElement('span');
span.className = hint.type;
span.style.left = Math.max(0,hint.x-8) + 'px';
span.style.top = Math.max(0,hint.y-8) + 'px';
span.innerHTML = hint.text;
df.appendChild(span);
return span;
}
function getHintByText(txt) {
return hints[retrieveNumber(txt)];
}
function retrieveNumber(text) {
text += '';
var num = 0;
for (var i = 0,
l = text.length; i < l; i++) {
var fix = (i == 0) ? 0 : 1;
var t = text.charAt(l - i - 1);
num += (hintKeys.indexOf(t) + fix) * Math.pow(hintKeys.length, i);
}
return num;
}
function createText(num) {
var text = '';
var l = hintKeys.length;
var iter = 0;
while (num >= 0) {
var n = num;
num -= Math.pow(l, 1 + iter++);
}
for (var i = 0; i < iter; i++) {
r = n % l;
n = Math.floor(n / l);
text = hintKeys.charAt(r) + text;
}
return text;
}
function rectFixForOpera(e, s) {
if (!window.opera || e.tagName.toLowerCase() != 'a' || s.cssFloat != 'none') return null;
var c = e.firstChild;
var cIsAfterWhiteSpace = true;
while (cIsAfterWhiteSpace) {
if (!c) return {
left: 0,
right: 0
};
cIsAfterWhiteSpace = (c.nodeName.toLowerCase() == 'br' || c.nodeType == 8 || c.nodeType == 3 && !/\S/.test(c.nodeValue));
if (c instanceof HTMLImageElement || c.tagName && c.tagName.toLowerCase() != 'br' && getComputedStyle(c, null).display == 'block') return c.getBoundingClientRect();
c = c.nextSibling;
}
return null;
}
/* function rectFixForOpera(e, s) {
if(!window.opera||s.display!='inline'||s.cssFloat!='none')return null;
var c = e.firstChild,ec = e.firstElementChild;
if(!ec){
if(!/img/i.test(e.tagName))
while(c){
if(c.nodeType==3&&/\S/.test(c.nodeValue))return null;
c=c.nextSibling;
}
return {left:0,right:0};
}else{
if(!/(br|img)/i.test(ec.tagName)&&getComputedStyle(ec,null).display=='inline')return null;
while(c){
if(c.nodeType==3&&/\S/.test(c.nodeValue))return null;
c=c.nextSibling;
}
return ec.getBoundingClientRect();
}
}*/
function getAbsolutePosition(elem) {
var style = getComputedStyle(elem, null);
var rect = rectFixForOpera(elem, style) || elem.getClientRects()[0];
if (rect && rect.right - rect.left > 0 && style.visibility != 'hidden' && style.opacity != 0 && rect.left >= 0 && rect.top >= -5 && rect.bottom <= window.innerHeight + 5 && rect.right <= window.innerWidth) {
var html = document.documentElement;
var body = document.body;
return {
top: (body.scrollTop || html.scrollTop) - html.clientTop + rect.top,
left: (body.scrollLeft || html.scrollLeft) - html.clientLeft + rect.left
}
}
return false;
}
function drawHints() {
var div = document.getElementById('HaH-div-element');
if (div) div.innerHTML = '';
function resolv(p){
if(p=='xhtml')return 'http://www.w3.org/1999/xhtml';
}
var result = document.evaluate(xpath,document,resolv,7,null);
for(var i=0,l=result.snapshotLength;i<l;i++ ) {
var ele=result.snapshotItem(i);
var pos = getAbsolutePosition(ele);
if (!pos) continue;
castHint(ele, pos.left, pos.top);
}
if (!hints.length) return;
if (!div) {
div = document.createElement('div');
div.id = 'HaH-div-element';
}
div.appendChild(df);
document.body.appendChild(div);
document.addEventListener('keypress', interpretHaHKeyStroke, true);
document.title += '\x20-\x20';
}
function removeHints() {
document.title = originalTitle;
if (interpretHaHKeyStroke) {
document.removeEventListener('keypress', interpretHaHKeyStroke, true);
}
document.body.removeChild(document.getElementById('HaH-div-element'));
delete hints,df,choices;
}
var baseFuncs = {
doNothing: function() {
},
pushChoice: function(mod) {
if (mod) return removeHints;
if (choice != '') {
choices.push(choice);
getHintByText(choice).hintNode.className = 'HaHselected';
choice = '';
document.title += '\x20';
}
},
popChoice: function(mod) {
if (mod) return removeHints;
if (choice != '') {
choice = '';
} else {
if (choices.length) {
var lastchoice = getHintByText(choices.pop());
lastchoice.hintNode.className = lastchoice.type;
}
}
document.title = originalTitle + '\x20-\x20' + choices.join('\x20');
},
pushLetter: function(mod, key) {
if (mod) return removeHints();
choice += '';
if (getHintByText(choice + key)) {
choice += key;
var lastHint = '' + hints[hints.length - 1].text;
if (choice.length == lastHint.length || choice.length == lastHint.length - 1 && retrieveNumber(choice) > retrieveNumber(lastHint.substr(0, choice.length))) {
getHintByText(choice).hintNode.className = 'HaHselected';
choices.push(choice);
choice = '';
document.title += '\x20';
}
}
var choicestring = (choices.length) ? choices.join('\x20') + '\x20' + choice: choice;
document.title = originalTitle + '\x20-\x20' + choicestring;
}
};
var keyMap = {
'8' : 'Bkspc',
'32': 'Space',
'16': 'Shift',
'17': 'Ctrl',
'18': 'Alt'
};
var keyFuncMap = {
'Bkspc': baseFuncs.popChoice,
'Space': baseFuncs.pushChoice,
};
hintKeys.split('').forEach(function(l){keyMap[l.charCodeAt(0)] = l});
hintKeys.split('').forEach(function(k){keyFuncMap[k] = baseFuncs.pushLetter;});
['Shift', 'Ctrl', 'Alt'].forEach(function(k){keyFuncMap[k] = baseFuncs.doNothing;});
function defaultFuncTemplate(func){
return function(mod){
if(choice)choices.push(choice);
var ary=[];
choices.forEach(function(c,i){
var hint = getHintByText(c);
if(hint && choices.indexOf(c)==i) ary.push(hint);
});
func(mod,ary);
removeHints();
}
}
function addExtraFunc(func,keyName,keyCode){
keyMap[(keyCode||keyName.charCodeAt(0))] = keyName;
keyFuncMap[keyName] = defaultFuncTemplate(func);
}
addExtraFunc(function(mod,ary){
var windowname = (ary.length==1&&!mod) ?'_top':'_blank';
ary.forEach(function(e){if(e.type=='HaHlink')window.open(e.element,windowname)});
},'Enter',13);
addExtraFunc(function(mod,ary){
if(mod)return;
var firstHint=ary[0];
if(firstHint)firstHint.action(firstHint.element);
},';');
if (_HaH && _HaH.keys) {
_HaH.keys.forEach(function(e){addExtraFunc(e.func,e.keyName,e.keyCode)});
}
function interpretHaHKeyStroke(e) {
var key = keyMap[(e.keyCode || e.which)];
e.preventDefault();
e.stopPropagation();
if (!key) return removeHints();
var modified = (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey);
keyFuncMap[key](modified, key) && removeHints();
}
drawHints();
})(window.HitaHint);
@shohey1226
Copy link

This is just awesome. Looks working fine on Safari too. Many thanks!

@ronnac
Copy link

ronnac commented Dec 20, 2021

This works on Chrome, too. However, on Google search results pages, not all result links receive a hint. Could you look at this, please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment