Created
July 13, 2009 00:45
-
-
Save satyr/145858 to your computer and use it in GitHub Desktop.
Hit-a-Hint-like navigation within preview
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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