「http://d.hatena.ne.jp/Sybian/20070415/p1」をGreasemonkeyに移植した。
インストール
http://sawat.jf.land.to/gm/google_ldr_keybind_fx.user.js
(追記) 本家がFirefox/Opera両対応になったので、こちらをインストールしてください。
http://sybian.picopico.cc/data/google_ldr_keybind_fx_op.user.js
ソース
// ==UserScript== // @name Google Search keybind as a LDR // @namespace http://d.hatena.ne.jp/Sybian/ // @include http://*.google.co*/search?* // @description Google keybind as a LDR // ==/UserScript== // keybinds are written bottom. function log(str) { // unsafeWindow.console.log(str); } (function(){ // for popup block through..? var wo=window.open; window.openLater=function(){ var args=arguments; setTimeout( function(){ wo.apply(window,args) },100 ); } //************************************************** // css //************************************************** function CSS(){ this._elm=[]; this._cnt=0; this._hash={}; this._head=document.getElementsByTagName("head")[0]; } CSS.prototype.factory=function(id){ this._cnt++; var id=id || this._cnt; var elm=document.createElement("style"); elm.type="text/css"; elm.id="css_"+id; return elm; } CSS.prototype.toggle=function(selector,prototype,id){ if(!id) return; if(this._hash["css_"+id]){ this.removeRule(id); }else{ this.addRule(selector,prototype,id); } } CSS.prototype.addRule=function(selector,property,id){ if(this._hash["css_"+id]) return ; var elm=this.factory(id); elm.innerHTML=selector+"{"+property+"}"; this._head.appendChild(elm); this._hash[elm.id]=true; return elm.id; } CSS.prototype.removeRule=function(id){ var elm=document.getElementById("css_"+id); if(elm){ this._head.removeChild(elm); this._hash["css_"+id]=false; } } var css=new CSS(); //************************************************** // panel //************************************************** var panel=document.createElement("div"); panel.id="ldr_panel"; css.addRule( "#ldr_panel" ,[ ,"position:fixed" ,"right:10px; top:10px;" ,"background:#eee;" ].join(";") ,"panel" ); css.addRule( "div.g.pinned" ,[ "background:#e9ffe9" ].join("") ,"pinned" ); css.addRule( "nobr .pinned" ,[ "background:#3f3;" ].join("") ,"pinned_cache" ); //-------------------------------------------------- // panel list //-------------------------------------------------- panel.list={ parent: panel ,element: document.createElement("ol") } panel.list.element.style.display="none"; panel.list.parent=panel; panel.list.update=function(){ var tmp=[]; for(var i=0,l=this.parent.pin._pins.length; i<l; i++){ var pin=this.parent.pin._pins[i]; tmp.push([ '<li>' ,'<a href="',pin.url,'">',pin.title,'</a>' ,'</li>' ].join("")); } this.element.innerHTML=tmp.join(""); } panel.list.display=function(){ this.element.style.display="block"; } panel.list.hidden=function(){ this.element.style.display="none"; } panel.list.toggle=function(){ if(this.element.style.display == "none"){ this.display(); }else{ this.hidden(); } } //-------------------------------------------------- // panel pin //-------------------------------------------------- panel.pin={ parent: panel ,_pins: [] ,_hashTable: {} }; panel.pin.element=document.createElement("p"); panel.pin.hash=function(url){ return url.replace(/[^a-zA-Z0-9-_]/g,""); } panel.pin.update=function(){ this.element.innerHTML=this._pins.length; this.parent.list.update(); } panel.pin.toggle=function(url, title, element){ log("toggle >> "+ this._hashTable[this.hash(url)]); if(this._hashTable[this.hash(url)]){ this.remove(url, title, element); }else{ this.add(url, title, element); } } panel.pin.add=function(url,title,element){ if(!url) return ; var key=this.hash(url); if(this._hashTable[key]) return; this._hashTable[key]=true; this._pins.push({url:url,title:title,element:element}); element.className+=" pinned"; this.update(); } panel.pin.remove=function(url,title,element){ var key=this.hash(url); if(!this._hashTable[key]) return; for(var i=0,l=this._pins.length; i<l; i++){ if(this._pins[i].url == url){ this._pins.splice(i,1); this._hashTable[key]=false; element.className=element.className.replace(/ pinned/g,""); this.update(); break; } } } panel.pin.clear=function(){ var i=0; while(this._pins[i]){ var pin=this._pins[i]; this.remove(pin.url,pin.title,pin.element); } this.update(); } panel.pin.openAll=function(){ for(var i=0,l=this._pins.length; i<l; i++){ window.openLater(this._pins[i].url); } this.clear(); } panel.pin.open=function(num){ window.open(this._pins[num].url); this.remove(this._pins[num].url); } //-------------------------------------------------- // Items //-------------------------------------------------- function Items(){ this._items=[]; this.current=-1; this.allItems(); }; Items.prototype.allItems=function(){ if(this._items.length > 0) return this._items; var div=document.getElementsByTagName("div"); for(var i=1,l=div.length; i<l; i++){ if(div[i].className == "g"){ div[i].id="item_"+(i-2); this._items.push(div[i]); } } return this._items; } Items.prototype.focus=function(){ location.hash="item_"+(this.current); // location.hash=""; } Items.prototype.prev=function(){ if(this.current > 0) this.current--; this.focus(); } Items.prototype.next=function(){ if(this.current < this._items.length) this.current++; this.focus(); } Items.prototype.top=function(){ this.current=0; this.focus(); } Items.prototype.bottom=function(){ this.current=this._items.length-1; this.focus(); } Items.prototype.getItem=function(){ var item=this._items[this.current]; try{ var _a=item.getElementsByTagName("h2")[0].getElementsByTagName("a")[0]; var url=_a.href; var title=_a.innerHTML; }catch(e){ log(e); } try{ var cache=item.getElementsByTagName("nobr")[0].getElementsByTagName("a")[0] || {}; var cacheURL=cache.href || ""; }catch(e){ log(e); } return { element: item || {} ,url: url || "" ,title: title || "" ,cache: cacheURL || "" ,cacheElement: cache }; } var items=new Items(); //-------------------------------------------------- // keybind //-------------------------------------------------- var keybind={ } keybind.keycode2char=function(e){ var kc=e.keyCode; // from LDR(event.js) var between = function(a,b){ return a <= kc && kc <= b } log(kc); var _32_40 = "space pageup pagedown end home left up right down".split(" "); var kt = { 8 : "back", 9 : "tab", 10 : "enter", 13 : "enter", 16 : "shift", 17 : "ctrl", 44 : ",", 46 : ".", 47 : "/", 58 : ":", // keypress 60 : "<", // keypress 62 : ">", // keypress 63 : "?", // keypress 91 : "[", 93 : "]", 188: "<", 190: ">", 219: "[", 221: "]", 229: "IME" }; var result=(function(){return ( between(65,90) ? String.fromCharCode(kc+32) : // keydown a-z between(97,122) ? String.fromCharCode(kc) : // keypress a-z between(48,57) ? String.fromCharCode(kc) : // 0-9 between(96,105) ? String.fromCharCode(kc-48) : // num 0-9 between(32,40) ? _32_40[kc-32] : kt.hasOwnProperty(kc) ? kt[kc] : "null" )})(); result=(e.shiftKey) ? "S-"+result : result; result=(e.ctrlKey)? "C-"+result : result; //alert(result + "/" + kc); return result; }; keybind.callback=function(key,func){ var func=func || function(){}; func(); } //************************************************** // event //************************************************** function eh(e){ var ch=keybind.keycode2char(e); log(">> " + ch); if(keybinds[ch]){ keybind.callback(ch,keybinds[ch]); e.preventDefault(); } } function EventHandler(){ this._enabled=false; } EventHandler.prototype.toggle=function(){ if(this._enabled){ this.disable(); }else{ this.enable(); } } EventHandler.prototype.enable=function(e){ log("enable"); if(this._enabled == false){ document.getElementsByTagName("html")[0].addEventListener("keydown",eh,true); this._enabled=true; } } EventHandler.prototype.disable=function(){ log("disable"); if(this._enabled == true){ document.getElementsByTagName("html")[0].removeEventListener("keydown",eh,true); this._enabled=false; } } var ev=new EventHandler(); var textfields=document.getElementsByName("q"); for(var i=0;i<textfields.length;i++) { var textfield = textfields[i]; textfield.addEventListener("keydown" , function(e){ var ch=keybind.keycode2char(e); log(ch); if(ch == "C-["){ // alternative ESC ev.enable(); } }, false); textfield.addEventListener("focus", function(){ log('focus'); ev.disable(); }, false); textfield.addEventListener("blur", function(){ log('blur'); ev.enable(); }, false); } //************************************************** // setup //************************************************** ev.enable(); panel.pin.update(); panel.appendChild(panel.pin.element); panel.appendChild(panel.list.element); document.body.appendChild(panel); panel.addEventListener("mouseover", function(){ log('hoge' + this); panel.list.display(); }, false); panel.addEventListener("mouseout", function(){ panel.list.hidden(); }, false); keybinds={ "p": function(){ var item=items.getItem(); panel.pin.toggle(item.url,item.title,item.element); log(item); } ,"S-p": function(){ var item=items.getItem(); panel.pin.toggle(item.cache,"(cache)"+item.title,item.cacheElement); } ,"l": function(){ panel.list.toggle(); } ,"o": function(){ panel.pin.openAll(); } ,"C-S-c": function(){ panel.pin.clear(); } ,"c": function(){ // toggle description text css.toggle("div.g table","display:none;","ldr_text"); } ,"f": function(){ // focus textfield document.getElementsByName("q")[0].focus(); } // ">" input ,"S->": function(){ // next page var d=document.getElementById("nn"); if(d){ location.href=d.parentNode.href; } } // "<" input ,"S-<": function(){ // prev page var d=document.getElementById("np"); if(d){ location.href=d.parentNode.href; } } ,"j": function(){ items.next(); } ,"S-j": function(){ items.bottom(); } ,"k": function(){ items.prev(); } ,"S-k": function(){ items.top(); } } })(); /** 作業ログ。 ・onkeypress = function() { ... } などの行でエラーが発生している。 → 全て addEventListener を使うように書き換え。 → エラーは出なくなった。が、なにも機能はしていないようだ。 ・logを挿入してイベントが正しく補足できているかチェック。 → keypressに対するイベントハンドラの登録先を document から document.getElementsByName("html")[0] に変更。 → イベントは受けれるようになったが、event.keyCodeが取得できていない(いつも0)。 対象イベントを "keypress" から "keydown" に変更。 → []<> のキーで keycode2char() の戻り値がnullになっている。 ハッシュのキーコードが間違っているので正しい値(188, 190, 219, 221)を追加。 ・jを押すと、何か動くみたいだけど、ページが再読込されてしまう。 → ソースを見てみると、location.hashを書き換えてスクロールさせているようだ。 → しかも、書き換えた後で location.hash="" として、変更をクリアしている。 → これ要らないじゃね? コメントアウトする。 → j, k で動くようになった。 ・pを押してピン状態にしても画面に変化が無い。(oを押すと開くのでピンは刺さっているらしい) → ソースを見てみるとclassNameの変更をやっているようだが反応が無いらしい。 → ためしに element.style.border = "3px dotted orange"; として見る。 → ピンを着けた項目にボーダーが正常に表示される。 → css.addRuleが効いてないじゃないか?ソースを良く見てみる。 → 怪しい箇所発見。 elm.innerText=selector+"{"+property+"}"; → innerTextプロパティはIEの独自拡張であり、IEとIEに対して最大限の互換性を 得るために頑張っているOperaでしか使えないので、innerHTMLにして見る。 → スタイルが変わるようになった。 → innerHTMLなので、ほんとはエスケープが要るんだけど、まあいいか。 → もう要らないので、element.style.borderの書き換えを消す。 ・panel.pin.toggle=function(){ ... } は argument と apply関数を駆使していたが、 まったくその必要性が感じられないので、修正。 ・あと、何の機能があるの? → S-kでページ末尾、S-jでページ頭らしい。 → S-kは動くけど、S-jは先頭の次にジャンプしてる。 → this.current=1; → 怪しいので、0にする。 → 先頭にジャンプした。 ・ずっと気になってた"char"(JavaScriptでは予約語。つかわれていなけど)を"ch"に置換する。 ・こんなもんかな? → 問題発覚。テキストエリア(下の方)に j とか k とか打てない orz → document.getElementsByName("q")に対してループさせる。 ・そういえば、現在指しているpinの数が表示されるんじゃなかったっけ? → pin.list.update にも innerText を使っている箇所があったので修正 ・せっかくだから Trixie でも動くようにしようかと思ったけど、 position:fixed なんてリッチな機能を使ってるのでやめた。 */
上記の通りの修正で無事FirefoxのGreasemonkeyで動かすことができた。
ただし、別の問題も発生した。
逆にOperaで動かなくっていることだ。*1
クロスブラウザの道のりは険しい…。
*1:あとで直すつもり→ 原因は、修正中のゴミがのこっていたせいでした。 Opera9.0.2で上記修正済みソースで動きました。