Skip to content

Instantly share code, notes, and snippets.

@satyr
Created July 13, 2009 00:45
Show Gist options
  • Save satyr/145858 to your computer and use it in GitHub Desktop.
Save satyr/145858 to your computer and use it in GitHub Desktop.
Hit-a-Hint-like navigation within preview
var gKey = '@', root = this;
const HHO = {
keys: 'hjkluiopnm',
bind: {
click: 'return',
click1: '@ comma space',
click2: 'C_return',
focus: 'f period',
undo: 'back_space delete',
},
css: String(<![CDATA[
$ {
position:absolute; z-index:2147483647;
color:#000; background-color:#ef7; opacity:0.7;
font:bold 9pt/1 "Consolas",monospace;
margin:0; padding:0 1px; border:1px solid #aaa;}
$.hit {background-color:#fcf;}
]]>),
query: 'a,input,button,textarea,select',
/// each hint's offset from its associated element
offset: {x: -4, y: -2},
/// base point of screen (set negative values to allow overflowed elements)
origin: {x: -8, y: -4},
sign: {
run: isNaN,
put: function SPut(msg){ this.box.textContent = msg },
end: function SEnd(){ $(this.box).remove(); delete this.box },
box: null,
},
};
function HHP(captor, {self: window, document}, {
keys, bind, css, query, offset, origin, sign}){
const StartTime = Date.now(),
K = (keys = keys.toUpperCase()).split(''), L = K.length,
ID = 'HHP'+ StartTime,
{x: OffsetX, y: OffsetY} = offset,
{x: OriginX, y: OriginY} = origin,
CSS = css.replace(/\$/g, '#'+ ID +'>*').replace(/;/g, ' !important;'),
Sign = {
__proto__: sign,
show: function SShow(txt, lmn){
var msg = '@'+ txt;
if(lmn){
var name = lmn.nodeName.toLowerCase();
var href = (lmn.href || lmn.src || '').replace(/^http:\/+/, '');
msg += (' '+ (name === 'input' ? lmn.type.toLowerCase() : name) +
' '+ this.cut(href || lmn.value) +
' '+ this.cut(lmn.title || lmn.alt || lmn.textContent));
}
this.put(msg);
},
cut: function SCut(s, n)
s.length > (n = n || 42) ? s.slice(0, n-1) + '\u2026' : s,
},
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');
box.appendChild(doc.createElement('style')).innerHTML = CSS;
var {max} = Math, dic = {__proto__: null};
var {innerWidth, innerHeight, scrollX, scrollY} = win;
var lms = doc.body.querySelectorAll(query), i = -1, lm, xy;
while((lm = lms[++i])){
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 && box.parentNode.removeChild(box);
},
},
Acts = {
input: function input(e){
var key = String.fromCharCode(e.which || e.keyCode).toUpperCase();
hit(Hints.txt + key) || hit(key);
},
ok: function ok() !hit(),
undo: function undo(){ Hints.txt ? hit(Hints.txt.slice(0, -1)) : end() },
focus: function focus(){ Hints.txt && Hints.now.item.focus() },
__noSuchMethod__: end,
};
var recm = /click|mouse/;
for(var a in new Iterator(bind, true)) if(recm.test(a)) Acts[a] = mouser(a);
bind.input = Array.map(keys + keys.toLowerCase(), ord);
Acts.handleEvent = handler(bind);
Sign.run();
Hints.run();
cast.call(start, window);
Sign.show(Hints.len +'hints / '+ (Date.now() - StartTime) +'ms');
function start(win, doc){
Hints.spray(win, doc);
captor.addEventListener('keypress', Acts, true);
}
function reset(win, doc){
captor.removeEventListener('keypress', Acts, true);
Hints.sweep(doc);
}
function end(){
if(end.done) return;
cast.call(reset, window);
Hints.end();
Sign.end();
return end.done = true;
}
function hit(txt)(
Sign.show(txt = Hints.set(txt), txt && Hints.now.item), txt);
function ord(s) s.charCodeAt();
function mouser(type){
var [typ, btn] = /^[a-z]+(?=(\d?))/(type), dtl = (type === 'dblclick') + 1;
return function mouse(e){
var lm = Hints.now.item;
var me = lm.ownerDocument.createEvent('MouseEvents');
me.initMouseEvent(
typ, 1, 1, e.view, dtl, e.screenX, e.screenY, e.clientX, e.clientY,
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, +btn, lm);
lm.dispatchEvent(me);
btn == 1 || end();
};
}
function cast(win){
try { var doc = win.document } catch([]){}
if(doc && / HTMLBodyElement\]/.test(document.body)) this(win, doc);
Array.forEach(win, cast, this);
}
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 [name, ks] in new Iterator(dic)) arr(ks).reduce(add, name);
return function handle(e){ with(e){
var m = F[ctrlKey<<C | altKey<<A | metaKey<<M | (which || keyCode)];
this[m](e, shiftKey) || preventDefault(stopPropagation());
}}
}
return end;
}
function hh(ev){
if(!ev.ctrlKey || ev.which !== gKey.charCodeAt()) return;
ev.preventDefault();
ev.stopPropagation();
var doc = this.ownerDocument;
var box = doc.createElementNS('http://www.w3.org/1999/xhtml', 'div');
box.className = 'hit-at-hint-sign';
box.style.cssText = ''+<![CDATA[
position:absolute; top:4px; left:4px; z-index:99999;
display:inline-block;
font:bold 125% monospace;
color:#000; background-color:#afd; opacity:0.8;
-moz-border-radius:6px;
]]>;
HHO.sign.box = this.firstElementChild.appendChild(box);
var {contentWindow} = doc.getElementById('ubiquity-browser');
Utils.listenOnce(this, 'popuphidden', HHP(this, contentWindow, HHO));
}
function hhBind(U){
U.msgPanel.addEventListener('keypress', hh, true);
}
function teardown_hh(){
for each(let {gUbiquity} in Utils.chromeWindows) if(gUbiquity)
gUbiquity.msgPanel.removeEventListener('keypress', hh, true);
}
CmdUtils.onUbiquityLoad(hhBind);
CmdUtils.CreateCommand({
name: 'hit@hint',
description: 'Hit-a-Hint-like navigation in the preview.',
help: ''+ (
[it for each(it in new Iterator(HHO.bind))]
.reduce(function(l, [k, v]) l.appendChild(<li><b>{k}</b>: {v}</li>),
(<ul style="list-style:none"><li><b>hit</b>:
{HHO.keys.split('').join(' ')}</li></ul>))),
author: {name: 'satyr', email: 'murky.satyr\x40gmail.com'},
license: 'MIT',
get hh_icon(){
if (hh_icon.key === gKey) return hh_icon.url;
var cv = Utils.hiddenWindow.document.createElementNS(
'http://www.w3.org/1999/xhtml', 'canvas');
cv.width = cv.height = 16;
var c = cv.getContext('2d');
c.scale(1.7, 1);
c.font = '16px monospace';
c.textBaseline = 'top';
c.fillStyle = 'rgba(0, 0, 0, 0.8)';
c.fillText(hh_icon.key = gKey, 0, 0);
return hh_icon.url = cv.toDataURL('image/png');
},
arguments: {object_key: /^[\x21-\x7e]$/},
execute: function hh_execute({object: {text: key}}){
key && displayMessage('Bound to [Ctrl + '+ (gKey = key) +'].', this);
},
preview: function hh_preview(pb, {object: {text: key}}){
var [m, k] = key ? ['Rebinds to', key] : ['Invoke with', gKey];
pb.innerHTML = m +' <b>Ctrl + '+ k +'</b>.<hr/>'+ this.help;
hhBind(context.chromeWindow.gUbiquity);
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment