index.md (8644B)
1 Keyboard link hinting 2 ===================== 3 4 Description 5 ----------- 6 7 This script enables link hinting support to surf. Deploy it into ~/.surf/script.js. 8 9 Keybindings 10 ----------- 11 ^f (open link in current window) 12 ^F (open link in new window) 13 ^c (cancel hinting) 14 15 Azerty keyboards 16 ---------------- 17 Just replace like this to use `&é"'(-è_çà` as numbers : 18 19 "U+a0c3" : "0", 20 "U+0026" : "1", 21 "U+a9c3" : "2", 22 "U+0022" : "3", 23 "U+0027" : "4", 24 "U+0028" : "5", 25 "U+002d" : "6", 26 "U+a8c3" : "7", 27 "U+005f" : "8", 28 "U+a7c3" : "9", 29 30 Author 31 ------ 32 33 - The code was originally from chromium but was adapted by nibble(.ds(at)gmail(dot)com) to work with surf. 34 35 Code 36 ---- 37 38 /* based on chromium plugin code, adapted by Nibble<.gs@gmail.com> */ 39 var hint_num_str = ''; 40 var hint_elems = []; 41 var hint_open_in_new_tab = false; 42 var hint_enabled = false; 43 44 function hintMode(newtab){ 45 hint_enabled = true; 46 if (newtab) { 47 hint_open_in_new_tab = true; 48 } else { 49 hint_open_in_new_tab = false; 50 } 51 setHints(); 52 document.removeEventListener('keydown', initKeyBind, false); 53 document.addEventListener('keydown', hintHandler, false); 54 hint_num_str = ''; 55 } 56 57 function hintHandler(e){ 58 e.preventDefault(); //Stop Default Event 59 var pressedKey = get_key(e); 60 if (pressedKey == 'Enter') { 61 if (hint_num_str == '') 62 hint_num_str = '1'; 63 judgeHintNum(Number(hint_num_str)); 64 } else if (/[0-9]/.test(pressedKey) == false) { 65 removeHints(); 66 } else { 67 hint_num_str += pressedKey; 68 var hint_num = Number(hint_num_str); 69 if (hint_num * 10 > hint_elems.length + 1) { 70 judgeHintNum(hint_num); 71 } else { 72 var hint_elem = hint_elems[hint_num - 1]; 73 if (hint_elem != undefined && hint_elem.tagName.toLowerCase() == 'a') { 74 setHighlight(hint_elem, true); 75 } 76 } 77 } 78 } 79 80 function setHighlight(elem, is_active) { 81 if (is_active) { 82 var active_elem = document.body.querySelector('a[highlight=hint_active]'); 83 if (active_elem != undefined) 84 active_elem.setAttribute('highlight', 'hint_elem'); 85 elem.setAttribute('highlight', 'hint_active'); 86 } else { 87 elem.setAttribute('highlight', 'hint_elem'); 88 } 89 90 } 91 92 function setHintRules() { 93 if (document.styleSheets.length < 1) { 94 var style = document.createElement("style"); 95 style.appendChild(document.createTextNode("")); 96 document.head.appendChild(style); 97 } 98 var ss = document.styleSheets[0]; 99 ss.insertRule('a[highlight=hint_elem] {background-color: yellow}', 0); 100 ss.insertRule('a[highlight=hint_active] {background-color: lime}', 0); 101 } 102 103 function deleteHintRules() { 104 var ss = document.styleSheets[0]; 105 ss.deleteRule(0); 106 ss.deleteRule(0); 107 } 108 109 function judgeHintNum(hint_num) { 110 var hint_elem = hint_elems[hint_num - 1]; 111 if (hint_elem != undefined) { 112 execSelect(hint_elem); 113 } else { 114 removeHints(); 115 } 116 } 117 118 function execSelect(elem) { 119 var tag_name = elem.tagName.toLowerCase(); 120 var type = elem.type ? elem.type.toLowerCase() : ""; 121 if (tag_name == 'a' && elem.href != '') { 122 setHighlight(elem, true); 123 // TODO: ajax, <select> 124 if (hint_open_in_new_tab) 125 window.open(elem.href); 126 else location.href=elem.href; 127 128 } else if (tag_name == 'input' && (type == "submit" || type == "button" || type == "reset")) { 129 elem.click(); 130 } else if (tag_name == 'input' && (type == "radio" || type == "checkbox")) { 131 // TODO: toggle checkbox 132 elem.checked = !elem.checked; 133 } else if (tag_name == 'input' || tag_name == 'textarea') { 134 elem.focus(); 135 elem.setSelectionRange(elem.value.length, elem.value.length); 136 } 137 removeHints(); 138 } 139 140 function setHints() { 141 setHintRules(); 142 var win_top = window.scrollY; 143 var win_bottom = win_top + window.innerHeight; 144 var win_left = window.scrollX; 145 var win_right = win_left + window.innerWidth; 146 // TODO: <area> 147 var elems = document.body.querySelectorAll('a, input:not([type=hidden]), textarea, select, button'); 148 var div = document.createElement('div'); 149 div.setAttribute('highlight', 'hints'); 150 document.body.appendChild(div); 151 for (var i = 0; i < elems.length; i++) { 152 var elem = elems[i]; 153 if (!isHintDisplay(elem)) 154 continue; 155 var pos = elem.getBoundingClientRect(); 156 var elem_top = win_top + pos.top; 157 var elem_bottom = win_top + pos.bottom; 158 var elem_left = win_left + pos.left; 159 var elem_right = win_left + pos.left; 160 if ( elem_bottom >= win_top && elem_top <= win_bottom) { 161 hint_elems.push(elem); 162 setHighlight(elem, false); 163 var span = document.createElement('span'); 164 span.style.cssText = [ 165 'left: ', elem_left, 'px;', 166 'top: ', elem_top, 'px;', 167 'position: absolute;', 168 'font-size: 13px;', 169 'background-color: ' + (hint_open_in_new_tab ? '#ff6600' : 'red') + ';', 170 'color: white;', 171 'font-weight: bold;', 172 'padding: 0px 1px;', 173 'z-index: 100000;' 174 ].join(''); 175 span.innerHTML = hint_elems.length; 176 div.appendChild(span); 177 if (elem.tagName.toLowerCase() == 'a') { 178 if (hint_elems.length == 1) { 179 setHighlight(elem, true); 180 } else { 181 setHighlight(elem, false); 182 } 183 } 184 } 185 } 186 } 187 188 function isHintDisplay(elem) { 189 var pos = elem.getBoundingClientRect(); 190 return (pos.height != 0 && pos.width != 0); 191 } 192 193 function removeHints() { 194 if (!hint_enabled) 195 return; 196 hint_enabled = false; 197 deleteHintRules(); 198 for (var i = 0; i < hint_elems.length; i++) { 199 hint_elems[i].removeAttribute('highlight'); 200 } 201 hint_elems = []; 202 hint_num_str = ''; 203 var div = document.body.querySelector('div[highlight=hints]'); 204 if (div != undefined) { 205 document.body.removeChild(div); 206 } 207 document.removeEventListener('keydown', hintHandler, false); 208 document.addEventListener('keydown', initKeyBind, false); 209 } 210 211 function addKeyBind( key, func, eve ){ 212 var pressedKey = get_key(eve); 213 if( pressedKey == key ){ 214 eve.preventDefault(); //Stop Default Event 215 eval(func); 216 } 217 } 218 219 document.addEventListener( 'keydown', initKeyBind, false ); 220 221 function initKeyBind(e){ 222 var t = e.target; 223 if( t.nodeType == 1){ 224 addKeyBind( 'C-f', 'hintMode()', e ); 225 addKeyBind( 'C-F', 'hintMode(true)', e ); 226 addKeyBind( 'C-c', 'removeHints()', e ); 227 } 228 } 229 230 var keyId = { 231 "U+0008" : "BackSpace", 232 "U+0009" : "Tab", 233 "U+0018" : "Cancel", 234 "U+001B" : "Esc", 235 "U+0020" : "Space", 236 "U+0021" : "!", 237 "U+0022" : "\"", 238 "U+0023" : "#", 239 "U+0024" : "$", 240 "U+0026" : "&", 241 "U+0027" : "'", 242 "U+0028" : "(", 243 "U+0029" : ")", 244 "U+002A" : "*", 245 "U+002B" : "+", 246 "U+002C" : ",", 247 "U+002D" : "-", 248 "U+002E" : ".", 249 "U+002F" : "/", 250 "U+0030" : "0", 251 "U+0031" : "1", 252 "U+0032" : "2", 253 "U+0033" : "3", 254 "U+0034" : "4", 255 "U+0035" : "5", 256 "U+0036" : "6", 257 "U+0037" : "7", 258 "U+0038" : "8", 259 "U+0039" : "9", 260 "U+003A" : ":", 261 "U+003B" : ";", 262 "U+003C" : "<", 263 "U+003D" : "=", 264 "U+003E" : ">", 265 "U+003F" : "?", 266 "U+0040" : "@", 267 "U+0041" : "a", 268 "U+0042" : "b", 269 "U+0043" : "c", 270 "U+0044" : "d", 271 "U+0045" : "e", 272 "U+0046" : "f", 273 "U+0047" : "g", 274 "U+0048" : "h", 275 "U+0049" : "i", 276 "U+004A" : "j", 277 "U+004B" : "k", 278 "U+004C" : "l", 279 "U+004D" : "m", 280 "U+004E" : "n", 281 "U+004F" : "o", 282 "U+0050" : "p", 283 "U+0051" : "q", 284 "U+0052" : "r", 285 "U+0053" : "s", 286 "U+0054" : "t", 287 "U+0055" : "u", 288 "U+0056" : "v", 289 "U+0057" : "w", 290 "U+0058" : "x", 291 "U+0059" : "y", 292 "U+005A" : "z", 293 //"U+005B" : "[", 294 //"U+005C" : "\\", 295 //"U+005D" : "]", 296 "U+00DB" : "[", 297 "U+00DC" : "\\", 298 "U+00DD" : "]", 299 "U+005E" : "^", 300 "U+005F" : "_", 301 "U+0060" : "`", 302 "U+007B" : "{", 303 "U+007C" : "|", 304 "U+007D" : "}", 305 "U+007F" : "Delete", 306 "U+00A1" : "¡", 307 "U+0300" : "CombGrave", 308 "U+0300" : "CombAcute", 309 "U+0302" : "CombCircum", 310 "U+0303" : "CombTilde", 311 "U+0304" : "CombMacron", 312 "U+0306" : "CombBreve", 313 "U+0307" : "CombDot", 314 "U+0308" : "CombDiaer", 315 "U+030A" : "CombRing", 316 "U+030B" : "CombDblAcute", 317 "U+030C" : "CombCaron", 318 "U+0327" : "CombCedilla", 319 "U+0328" : "CombOgonek", 320 "U+0345" : "CombYpogeg", 321 "U+20AC" : "€", 322 "U+3099" : "CombVoice", 323 "U+309A" : "CombSVoice", 324 } 325 326 function get_key(evt){ 327 var key = keyId[evt.keyIdentifier] || evt.keyIdentifier, 328 ctrl = evt.ctrlKey ? 'C-' : '', 329 meta = (evt.metaKey || evt.altKey) ? 'M-' : '', 330 shift = evt.shiftKey ? 'S-' : ''; 331 if (evt.shiftKey){ 332 if (/^[a-z]$/.test(key)) 333 return ctrl+meta+key.toUpperCase(); 334 if (/^[0-9]$/.test(key)) { 335 switch(key) { 336 // TODO 337 case "4": 338 key = "$"; 339 break; 340 }; 341 return key; 342 } 343 if (/^(Enter|Space|BackSpace|Tab|Esc|Home|End|Left|Right|Up|Down|PageUp|PageDown|F(\d\d?))$/.test(key)) 344 return ctrl+meta+shift+key; 345 } 346 return ctrl+meta+key; 347 }