Google Keybinds as a fastladder( or livedoor Reader)

By Sybian Last update Jun 7, 2007 — Installed 237 times. Daily Installs: 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0
// ==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
// @version     1.1.1
// ==/UserScript==


// keybinds are written bottom. 
// "?" input,key table display

// special thanks!
// http://d.hatena.ne.jp/sawat/20070416/1176727349

// ver 1.1.1 patch from http://white.s151.xrea.com/wiki/index.php?diary%2F2007-06-02%2F%A1%D6Google%A4%F2LDR%C9%F7%A4%CB%C1%E0%BA%EE%A4%B9%A4%EB%A1%D7%A4%CE%A5%D1%A5%C3%A5%C1#m4793bc8
// thanks!

// * 2007-06-02  ver.1.1.1 (http://white.s151.xrea.com/wiki/index.php?diary%2F2007-06-02%2F%A1%D6Google%A4%F2LDR%C9%F7%A4%CB%C1%E0%BA%EE%A4%B9%A4%EB%A1%D7%A4%CE%A5%D1%A5%C3%A5%C1#m4793bc8)
//     - support for AutoPagerize/Pagerization added page
//     - fixed keybind for Meta+v != v
//     - fixed error for "k" key pressed when scroll point upper than first item
// 
// * 2007-05-24  ver.1.1.0
//     - adjust next/prev item using current scroll point



(function(){
	var setup=function(){
		// from prototype.js
		String.prototype.escapeHTML=function(){
			var div = document.createElement('div');
			div.innerHTML = this.replace(/<.*?>/g,"");
			return div.childNodes[0].nodeValue;
		}

		// for working both firefox and opera
		var unsafeWindow= unsafeWindow || window;

		// for Opera's popup block through..?
		var wo=unsafeWindow.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"
		);

		// for panel's pin count
		css.addRule(
			"#ldr_panel p"
			,[
				"margin:0"
				,"padding:0"
				,"text-align:right"
			].join(";")
		);

		// for pinned items
		css.addRule(
			"div.g.pinnedItem:before"
			,[
				'content:"pinned"'
				,'background:#00c'
				,'color:#fff'
				,"padding:0 4px"
				,"margin-right:1ex"
				,"font-weight:normal"
			].join(";")
		);
		css.addRule(
			"div.g.pinnedCache h2:before"
			,[
				'content:"cache"'
				,'background:#0c0'
				,'color:#fff'
				,"padding:0 4px"
				,"margin-right:1ex"
				,"font-weight:normal"
			].join(";")
		);


		//--------------------------------------------------
		// panel list
		//--------------------------------------------------
		function List(){
			this.parent=panel;
			this.element=document.createElement("ol");
			this.element.style.display="none";
		}
		List.prototype.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("");
		}
		List.prototype.display=function(){
			this.element.style.display="block";
		}
		List.prototype.hidden=function(){
			this.element.style.display="none";
		}
		List.prototype.toggle=function(){
			if(this.element.style.display == "none"){
				this.display();
			}else{
				this.hidden();
			}
		}
		panel.list=new List();


		//--------------------------------------------------
		// 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(item,cache){
			var url=(cache) ? item.cache : item.url;
			if(this._hashTable[this.hash(url)]){
				this.remove(item,cache);
			}else{
				this.add(item,cache);
			}
		}
		panel.pin.add=function(item,cache){
			if(!item) return ;
			var url=(cache) ? item.cache : item.url;
			if(!url) return;
			if(cache){
				var title="(cache)"+item.title;
				var element=item.cacheElement;
				items.addClass("pinnedCache");
			}else{
				var title=item.title;
				var element=item.element;
				items.addClass("pinnedItem");
			}
			var key=this.hash(url);
			if(this._hashTable[key]) return;
			this._hashTable[key]=item;
			this._pins.push({
				url:url
				,title:title
				,element:element
				,isCacheitem: cache
				,num: element.id.match(/[0-9]+/)
			});
			this.update();
		}
		panel.pin.remove=function(item,cache){
			if(cache){
				var url=item.cache || item.url;
				items.removeClass("pinnedCache",item.num);
			}else{
				var url=item.url;
				items.removeClass("pinnedItem",item.num);
			}
			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;
					this.update();
					break;
				}
			}
		}
		panel.pin.clear=function(){
			var i=0;
			while(this._pins[i]){
				var pin=this._pins[i];
				this.remove(
					pin
					,pin.isCacheitem
				);
			}
			items.clearClasses();
			this.update();
		}
		panel.pin.openAll=function(){
			for(var i=0,l=this._pins.length; i<l; i++){
				var pin=this._pins[i];
				window.openLater(pin.url);
			}
			this.clear();
			items.clearClasses();
		}
		panel.pin.open=function(num){
			window.open(this._pins[num].url);
			this.remove(this._pins[num].url);
		}



		//--------------------------------------------------
		// Items
		//--------------------------------------------------
		function Items(){
			this.scrollHeight;
			this._items=[];
			this.current=-1;
			this.allItems();
		};

		Items.prototype.adjust=function(){
			var pos=document.documentElement.scrollTop || document.body.scrollTop;
			for(var i=0,len=this._items.length; i<len; i++){
				if(this._items[i].offsetTop > pos){
					break;
				}
			}
			this.current=(i-1);
		}
		Items.prototype.allItems=function(){
			if(this._items.length > 0 && document.documentElement.scrollHeight == this.scrollHeight)
			  return this._items;
			this.scrollHeight = document.documentElement.scrollHeight;
			var xpath = 'id("res")//div[contains(@class,"g")]';
			var div = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
			this._items=[];
			for(var i=0,l=div.snapshotLength; i<l; i++){
				div.snapshotItem(i).id="item_"+(1+i);
				this._items.push(div.snapshotItem(i));
			}
			return this._items;
		}
		Items.prototype.focus=function(){
			//location.hash="item_"+(this.current);
			if(this._items[this.current]){
				var y=this._items[this.current].offsetTop;
				scrollTo(0,y);
			}
		}
		Items.prototype.prev=function(){
			this.adjust();
			if(this.current > 0) this.current--;
			this.focus();
		}
		Items.prototype.next=function(){
			this.adjust();
			this.allItems();
			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(num){
			var num=num || this.current;
			var item=this._items[num];
			try{
				var _a=item.getElementsByTagName("h2")[0].getElementsByTagName("a")[0];
				var url=_a.href;
				var title=_a.innerHTML;
			}catch(e){
				
			}
			try{
				var nobr=item.getElementsByTagName("nobr")[0].getElementsByTagName("a");
				for(var i=0,l=nobr.length; i<l ;i++){
					if(nobr[i].href.indexOf("q=cache:") > -1){
						var cacheURL=nobr[i].href;
						var cache=nobr[i];
					}else if(nobr[i].href.indexOf("q=related:") > -1){
						var relatedURL=nobr[i].href;
						var related=nobr[i];
					}
				}
			}catch(e){
				
			}
			return {
				element: item || {}
				,url: url || ""
				,title: title || ""
				,cache: cacheURL || ""
				,cacheElement: cache
				,related: relatedURL
				,relatedElement: related
			};
		}
		Items.prototype.addClass=function(name,num){
			var key=num || this.current;
			var item=this._items[key];
			var cls=(item.className + " ").split(" ");
			cls.push(name);
			item.className=cls.join(" ");
		}
		Items.prototype.removeClass=function(name,num){
			var key=num || this.current;
			var item=this._items[key];
			var cls=item.className.split(" ");
			for(var i=0,l=cls.length; i<l; i++){
				if(cls[i]==name){
					cls.splice(i,1);
				}
			}
			item.className=cls.join(" ");
		}
		Items.prototype.clearClasses=function(){
			for(var i=0,l=this._items.length; i<l; i++){
				this.removeClass("pinnedItem",i);
				this.removeClass("pinnedCache",i);
			}
		}
		var items=new Items();
		if(items.allItems().length == 0){
			return ;
		}



		//--------------------------------------------------
		// keybind
		//--------------------------------------------------
		var keybind={
		}
		keybind.keycode2char=function(e){
			var kc=e.keyCode;
			// based on LDR(event.js)
			var between = function(a,b){
				return a <= kc && kc <= b
			}
			
			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
				191: "?", // keydown
				91 : "[",
				93 : "]",
				
				188: "<",
				190: ">",
				219: "[",
				221: "]",

				229: "IME"
			};
			unsafeWindow.kt = kt;
			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;
			result=(e.altKey || e.metaKey)? "A-"+result : result;
			//alert(result + "/" + kc);
			return result;
		};
		keybind.callback=function(key,func){
			var func=func || function(){};
			func();
		}

		var help=document.createElement("div");
		help.id="ldr_help";
		css.addRule("#ldr_help"
			,[
				"position:fixed"
				,"bottom:10px"
				,"right:10px"
				,"width:50%"
				,"background:#fff"
				,"border:2px solid #333"
			].join(";")
		);
		help.style.display="none";
		help.table=document.createElement("table");
		help.appendChild(help.table);


		//**************************************************
		// event
		//**************************************************
		function eh(e){
			var ch=keybind.keycode2char(e);
			if(keybinds.has(ch)){
				keybind.callback(ch,keybinds.call(ch));
				e.preventDefault();
			}
		}
		function EventHandler(){
			this._enabled=false;
			this.listener=(window.opera)? "keypress" : "keydown";
			this.target=document.getElementsByTagName("html")[0];
		}
		EventHandler.prototype.toggle=function(){
			if(this._enabled){
				this.disable();
			}else{
				this.enable();
			}
		}
		EventHandler.prototype.enable=function(e){
			if(this._enabled == false){
				this.target.addEventListener(this.listener,eh,true);
				this._enabled=true;
			}
		}
		EventHandler.prototype.disable=function(){
			
			if(this._enabled == true){
				this.target.removeEventListener(this.listener,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);
				if(ch == "C-["){ // alternative ESC
					document.getElementsByName("btnG")[0].focus();
					//ev.enable();
				}
			}, false);
			textfield.addEventListener("focus", function(){
				ev.disable();
			}, false);
			textfield.addEventListener("blur", function(){
				ev.enable();
			}, false);
		}




		//**************************************************
		// setup
		//**************************************************
		ev.enable();
		panel.pin.update();
		panel.appendChild(panel.pin.element);
		panel.appendChild(panel.list.element);
		panel.addEventListener("mouseover", function(){
			panel.list.display();
		}, false);
		panel.addEventListener("mouseout", function(){
			panel.list.hidden();
		}, false);


		function Keybinds(){
			this._keys=[];
			this.help=[];
		}
		Keybinds.prototype.register=function(ch,man,func){
			var chars=(ch+" ").split(" ");
			var keys=[];
			for(var i in chars){
				if(!chars[i]) continue;
				this._keys.push({
					ch: chars[i]
					,man: man
					,func: func
				});
				if(typeof chars[i] == "string"){
					keys.push(""+chars[i].escapeHTML());
				}
			}
			this.help.push([
				'<tr>'
				,'<th><kbd>',keys.join('</kbd> / <kbd>'),'</kbd></th>'
				,'<td>',man,'</td>'
				,'</tr>'
			].join(""));
		}
		Keybinds.prototype.find=function(ch){
			for(var i=0,l=this._keys.length; i<l; i++){
				if(this._keys[i].ch == ch){
					return this._keys[i];
				}
			}
			return false;
		}
		Keybinds.prototype.has=function(ch){
			return this.find(ch);
		}
		Keybinds.prototype.call=function(ch){
			this.find(ch)["func"]();
		}


		//**************************************************
		// keybinds setting
		//**************************************************
		var keybinds=new Keybinds();

		// [space] separeted keys are same oprerations
		// ex. "j enter","v S-enter"
		keybinds.register(
			"p"
			,"pin current url"
			,function(){
				panel.pin.toggle(items.getItem());
			}
		);
		keybinds.register(
			"S-p"
			,"pin current url(cache page)"
			,function(){
				panel.pin.toggle(items.getItem(),true);
			}
		);
		keybinds.register(
			"l"
			,"pinlist show/hide"
			,function(){ // pin list show/hide
				panel.list.toggle();
			}
		);
		keybinds.register(
			"o"
			,"open all pins (and clear pins)"
			,function(){ // open all pins(and clear pins)
				panel.pin.openAll();
			}
		);
		keybinds.register(
			"C-S-c"
			,"clear pins"
			,function(){ // clear all pins
				panel.pin.clear();
			}
		);
		keybinds.register(
			"c"
			,"snippet(description text) hide/show"
			,function(){ // description text hide/show
				css.toggle("div.g table","display:none;","ldr_text");
			}
		);
		keybinds.register(
			"v C-enter"
			,"open current url"
			,function(){ // open current url
				var item=items.getItem();
				window.open(item.url);
			}
		);
		keybinds.register(
			"S-v C-S-enter"
			,"open current url(cache)"
			,function(){ // open current url(cache page)
				var item=items.getItem();
				window.open(item.cache);
			}
		);
		keybinds.register(
			"f"
			,"focus search field"
			,function(){ // focus textfield
				scrollTo(0,0);
				document.getElementsByName("q")[0].focus();
			}
		);
		keybinds.register(
			"S-f"
			,"focus search field and select text"
			,function(){ // focus textfield and selected text
				var q=document.getElementsByName("q")[0];
				scrollTo(0,0);
				q.focus();
				q.select();
			}
		);
		keybinds.register(
			"S->"
			,"next page"
			,function(){ // next page
				var d=document.getElementById("nn");
				if(d){
					location.href=d.parentNode.href;
				}
			}
		);
		keybinds.register(
			"S-<"
			,"prev page"
			,function(){ // prev page
				var d=document.getElementById("np");
				if(d){
					location.href=d.parentNode.href;
				}
			}
		);
		keybinds.register(
			"j enter"
			,"next item"
			,function(){
				items.next();
			}
		);
		keybinds.register(
			"S-j"
			,"first item"
			,function(){
				items.bottom();
			}
		);
		keybinds.register(
			"k S-enter"
			,"prev item"
			,function(){
				items.prev();
			}
		);
		keybinds.register(
			"S-k"
			,"last item"
			,function(){
				items.top();
			}
		);
		keybinds.register(
			"S-?"
			,"help show/hide"
			,function(){
				help.style.display=(help.style.display == "none") ? "block" : "none";
			}
		);
		help.table.innerHTML=keybinds.help.join("");


		document.body.appendChild(panel);
		document.body.appendChild(help);

	}; // setup() end

	if(!window.opera){
		setup();
	}else{
		//if opera, to start script press Ctrl+Shift (performance issue)
		document.addEventListener(
			"keydown"
			,function(e){
				if(e.ctrlKey && e.shiftKey){
					var preparing=document.createElement("p");
					preparing.innerHTML="building...";
					preparing.style.background="#f88";
					preparing.style.position="fixed";
					preparing.style.top="10px";
					preparing.style.right="10px";
					document.body.appendChild(preparing);
					setTimeout(
						function(){
							setup();
							document.body.removeChild(preparing);
						}
						,10
					);
					document.removeEventListener(
						"keydown"
						,arguments.callee
						,false
					);
				}
			}
			,false
		);
	}
})();