Skip to content

Instantly share code, notes, and snippets.

@satyr
Created November 10, 2009 09:23
Show Gist options
  • Save satyr/230771 to your computer and use it in GitHub Desktop.
Save satyr/230771 to your computer and use it in GitHub Desktop.
caret@hint
javascript:[{/* caret@hint for Firefox (keyconfig or bookmarklet) */
keys: 'hjkluiopnm',
bind: {
/* moves to head/tail of hit node (selects with shift) */
head: 'space , < [ {',
tail: 'return . > ] }',
pick: '@ S s', /* selects hit node */
free: '0 F f', /* deselects all */
undo: 'back_space ^ ~',
},
css: ''+ <><![CDATA[
$ {
position:absolute; z-index:2147483647;
color:#000; background-color:#9fc; opacity:0.7;
font:bold 9pt/1 "Consolas",monospace; text-transform:uppercase;
margin:0; padding:0 1px; border:1px solid #aaa;}
$.hit {background-color:#fcf;}
]]></>,
/* each hint's offset from its associated element */
offset: {x: -12, y: 0},
/* base point of screen (negative values allow overflowed elements) */
origin: {x: -8, y: -8},
/*sign: {__noSuchMethod__: isNaN},*/
},
function CatH(window, {keys, bind, css, offset, origin, sign}){
const StartTime = Date.now(), {document} = window,
K = (keys = keys.toUpperCase()).split(''), L = K.length,
ID = 'CatH'+ StartTime,
{x: OffsetX, y: OffsetY} = offset,
{x: OriginX, y: OriginY} = origin,
CSS = css.replace(/\$/g, '#'+ ID +'>*').replace(/;/g, ' !important;'),
Hint = {set on(b){ this.hint.className = b ? 'hit' : '' }},
Hints = {
txt: '',
run: function HRun(){ this.dic = {}; this.len = 0 },
end: function HEnd(){ this.dic = this.len = null },
add: function HAdd(hint, item){
var n = this.len++;
this.dic[hint.textContent = K[n] || (K[n] = K[~-(n / L)] + K[n % L])]
= {__proto__: Hint, hint: hint, item: item};
},
get now() this.dic[this.txt],
set: function HSet(t){
var h = this.now;
if(h) h.on = false;
return this.txt = (h = this.dic[t]) ? h.on = t : '';
},
spray: function HSpray(win, doc){
var box = doc.createElement('hs'), dic = {__proto__: null}, lm, tn, xy;
box.appendChild(doc.createElement('style')).innerHTML = CSS;
var ns = doc.contentType !== 'text/html' ? 'xhtml:' : '';
var it = doc.evaluate(
('descendant::text()'+
'[translate(.," \n\r\t\xA0\u3000","")!=""]'+
'[not(ancestor::'+ ns +'option)]'+
'[not(ancestor::'+ ns +'button)]'),
doc.body, function nsr() 'http://www.w3.org/1999/xhtml', 5, null);
var {innerWidth, innerHeight, scrollX, scrollY} = win, {max} = Math;
while((tn = it.iterateNext())){
/* tn must be a firstChild, or an empty element's nextSibling */
if((lm = tn.previousSibling)){
if(lm.nodeType !== 1 || lm.hasChildNodes()) continue;
} else lm = tn.parentNode;
var {top, right, bottom, left} = lm.getBoundingClientRect();
if(left >= right || top >= bottom ||
OriginX > left || OriginY > top ||
right > innerWidth - OriginX ||
bottom > innerHeight - OriginY) continue;
var h = doc.createElement('h');
var x = max(0, left + OffsetX) + scrollX;
var y = max(0, top + OffsetY) + scrollY;
while((xy = x +','+ y) in dic) y += 12;
dic[xy] = 1;
h.setAttribute('style', 'left:'+ x +'px'+ ';top:'+ y +'px');
this.add(box.appendChild(h), lm);
}
doc.body.appendChild(box).id = ID;
},
sweep: function HSweep(doc){
var box = doc.getElementById(ID);
box && doc.body.removeChild(box);
},
},
Acts = {
input: function AInput(e){
var key = String.fromCharCode(e.which || e.keyCode).toUpperCase();
hit(Hints.txt + key) || hit(key);
},
head: function AHead(e, s){ caret(s, 0) },
tail: function ATail(e, s){ caret(s, 1) },
undo: function AUndo(){ Hints.txt ? hit(Hints.txt.slice(0, -1)) : end() },
pick: function APick(){ Hints.txt && select(Hints.now.item) },
free: function AFree(){
var i = 0;
cast.call(function deselect(win){
var sel = win.getSelection();
if(!sel) return;
i += sel.rangeCount;
sel.removeAllRanges();
} , window);
sign.put('freed: '+ i);
},
__noSuchMethod__: end,
};
sign || (sign = {
run: function SRun(){ this._ = document.title; this.put('') },
put: function SPut(txt, lmn){
var msg = '@'+ txt;
if(lmn){
var name = lmn.nodeName.toLowerCase();
msg += ' '+ [
name === 'input' ? lmn.type : name,
lmn.href || lmn.src || lmn.value || lmn.cite || lmn.datetime,
lmn.title || lmn.alt || lmn.textContent].filter(Boolean).join(' ');
}
document.title = msg;
},
end: function SEnd(){ document.title = this._ },
});
bind.input =
Array.map(keys + keys.toLowerCase(), function(k) k.charCodeAt());
Acts.handleEvent = handler(bind);
sign.run();
Hints.run();
cast.call(start, window);
sign.put(Hints.len +'hints ('+ (Date.now() - StartTime) +'ms)');
function start(win, doc){
Hints.spray(win, doc);
win.addEventListener('keypress', Acts, true);
}
function reset(win, doc){
win.removeEventListener('keypress', Acts, true);
Hints.sweep(doc);
}
function end(){
cast.call(reset, window);
Hints.end();
sign.end();
return true;
}
function hit(txt)(
sign.put(txt = Hints.set(txt), txt && Hints.now.item), txt);
function caret(ext, aft){
if(!Hints.txt) return void end();
var [sel, rng] = select(Hints.now.item);
if(ext){
for(var rs = [], i = sel.rangeCount; i--;) rs[i] = sel.getRangeAt(i);
var [s, e] = ['start', 'end'][aft ? 'valueOf' : 'reverse']();
sel.collapse(rng[s +'Container'], rng[s +'Offset']);
sel.extend(rng[e +'Container'], rng[e +'Offset']);
rs.push(rng);
rs.forEach(sel.addRange, sel);
} else (aft
? rng.setStart(rng.endContainer, rng.endOffset)
: rng.setEnd(rng.startContainer, rng.startOffset));
if(typeof gPrefService === 'object')
gPrefService.setBoolPref('accessibility.browsewithcaret', true);
end();
}
function cast(win){
try { var doc = win.document } catch(_){}
if(doc && doc.body instanceof HTMLBodyElement) this(win, doc);
Array.forEach(win, cast, this);
}
function select(lmn){
var doc = lmn.ownerDocument, win = doc.defaultView;
var sel = win.getSelection(), rng = doc.createRange();
win.focus();
if(sel.isCollapsed) sel.removeAllRanges();
rng.selectNodeContents(lmn.lastChild ? lmn : lmn.nextSibling);
sel.addRange(rng);
return [sel, rng];
}
function handler(dic){
const F = {__proto__: null},
C = 9, A = 10, M = 11, Modic = {C_:1<<C, A_:1<<A, M_:1<<M};
function translate(key) key.match(/[CAM]_|.+/igy).reduce(acc, 0);
function acc(n, k) n | (k.length === 1 && k.charCodeAt() ||
Modic[k = k.toUpperCase()] ||
KeyEvent['DOM_VK_'+ k]);
function add(m, k)
F[typeof k === 'number' ? k : translate(k)] = m;
function arr(x)
x == null ? [] : x.map ? x : x.split ? x.split(/\s+/) : [x];
for(var [meth, ks] in new Iterator(dic)) arr(ks).reduce(add, meth);
return function handle(e){ with(e){
var m = F[ctrlKey<<C | altKey<<A | metaKey<<M | (which || keyCode)];
this[m](e, shiftKey) || preventDefault(stopPropagation());
}}
}
},
function(win){
win.focus();
this[1](win, this[0]);
}] [2](self.content || top)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment