//==UserScript== //@name migemo-search-with-google-suggest.js //@author mallowlabs (http://d.hatena.ne.jp/mallowlabs) //@version 0.0.1 //@description : Inclemental migemo search with Google Suggest //@include * //==/UserScript== // Usage // Ctrl/Cmd + "/" : 検索ボックス表示 // 検索ボックス表示状態 // Shift + G : 次を選択 // Shift + Ctrl + G : 前を選択 // Enter : 選択項目のリンクを開く // Ctrl/Cmd + "/" : リンクのみから検索モードとトグル(文字が赤い時はリンクのみ) // Escape : 検索ボックス非表示 (function(document) { // http://d.hatena.ne.jp/javascripter/20080517/1211055234 var uniq = function(r) { return r.filter(function(a,i) { return !r.slice(i+1).some( function(b) { return a === b; } ); } ) } var MigemoServer = { MIGEMO_URL : "http://suggestqueries.google.com/complete/search?hl=ja&json=t&jsonp=migemoCallback&qu=", SCRIPT_ID : "opera-migemo-script", isBusy : false, send : function(query) { if (this.isBusy || query == "") return; this.isBusy = true; // JSONP のコールバックを登録 window["migemoCallback"] = this.callback; // 前回appendしたスクリプトがあれば削除 var head = document.getElementsByTagName("head")[0]; var old = document.getElementById(MigemoServer.SCRIPT_ID); if (old != null) head.removeChild(old); // migemo-serverから正規表現を受け取る. // migemo-serverと通信.関数migemoCallbackが呼ばれる. var s = head.appendChild(document.createElement("script")); s.id = MigemoServer.SCRIPT_ID; s.type = "text/javascript"; s.charset = "euc-jp"; s.src = this.MIGEMO_URL + query; }, callback : function(json) { var re = ""; // 例: goo|google|good if (json[1].length > 0) { /* google map google search ... となっているので先頭だけ取って unique する */ re = uniq(json[1].map(function(item) { return item.split(" ")[0]; })).join("|"); } else { re = json[0]; } SearchBox.setResult(re); MigemoServer.isBusy = false; } }; var SearchBox = { SEARCH_BOX_ID : "opera-migemo-search-box", INPUT_BOX_ID : "opera-migemo-input-box", currentWord : "", intervalId : null, linkonly : false, create: function(linkonly) { this.linkonly = !!linkonly; var d = document.createElement("div"); d.setAttribute("id", SearchBox.SEARCH_BOX_ID); d.setAttribute("style", "position:fixed!important;bottom:0.5%;left:0.5%;border:silver 2px solid;opacity:0.7;z-index:50"); var i = document.createElement("input"); i.setAttribute("id", SearchBox.INPUT_BOX_ID); i.setAttribute("size", "50"); var self = this; i.addEventListener('keyup', function(event) { self.currentWord = i.value; }, false); i.addEventListener('keypress', function(e) { switch(e.keyCode) { case 13: // enter self.onEnterKey(e); break; case 27: // escape self.onEscapeKey(e); break; case 71: // G self.onNext(e); e.preventDefault(); break; case 47: // '/' self.onToggle(e); break; } }, false); d.appendChild(i); document.body.appendChild(d); intervalId = window.setInterval(function() { self.search(); }, 100); i.focus(); }, onEscapeKey : function(e) { //終了する window.clearInterval(this.intervalId); TextHilighter.unhilightAll(); // 検索ボックスを削除 var m = document.getElementById(SearchBox.SEARCH_BOX_ID); m.parentNode.removeChild(m); }, onEnterKey : function(e) { TextHilighter.followLink(); }, onNext : function(e) { if (e.ctrlKey || e.metaKey) { TextHilighter.hilightNext(-1); } else { TextHilighter.hilightNext(1); } }, onToggle : function(e) { if (e.ctrlKey || e.metaKey) { this.linkonly = !this.linkonly; var i = document.getElementById(SearchBox.INPUT_BOX_ID); if (this.linkonly) { i.setAttribute("style", "color:red"); } else { i.setAttribute("style", ""); } } }, searchWord : "", search : function() { if (this.currentWord == "") { TextHilighter.unhilightAll(); } else if (!MigemoServer.isBusy && this.currentWord != this.searchWord) { this.searchWord = this.currentWord; MigemoServer.send(this.searchWord); } }, setResult : function(re) { TextHilighter.hilightAll(re, this.linkonly); TextHilighter.hilightNext(1); } } var TextHilighter = { hilightNodes : [], currentIndex : -1, hilightAll : function(re, linkonly) { var xpath = "descendant::text()"; if (!!linkonly) { xpath = "descendant::a/descendant::text()"; } this.unhilightAll(); var result = document.evaluate(xpath, document.body, null, 7, null); for (var i = 0; i < result.snapshotLength; i ++) { var t = result.snapshotItem(i); var f = t.textContent.search(RegExp(re, "i")); if (f != -1) { this.hilightNode(t, f, re); } } }, hilightNode : function(node, f, re) { var str = node.textContent; var pre = str.substring(0, f); var post = str.substring(f); var mid = post.match(RegExp(re, "i"))[0]; post = post.substring(mid.length); var parentNode = node.parentNode; var e1 = document.createTextNode(pre); var e2 = document.createElement("span") e2.setAttribute("style", "color:black;background:yellow"); e2.appendChild(document.createTextNode(mid)); var e3 = document.createTextNode(post) parentNode.insertBefore(e1, node); parentNode.insertBefore(e2, node); parentNode.insertBefore(e3, node); parentNode.removeChild(node); this.hilightNodes.push(e2); // recursive call var f2 = e3.textContent.search(RegExp(re, "i")); if (f2 != -1) { this.hilightNode(e3, f2, re); } }, unhilightAll : function() { for (var i = 0, length = this.hilightNodes.length; i < length; i++ ) { var node = this.hilightNodes[i]; var text = node.textContent; var tn = document.createTextNode(text); var parentNode = node.parentNode; parentNode.insertBefore(tn, node); parentNode.removeChild(node); parentNode.normalize(); } this.hilightNodes = []; this.currentIndex = -1; }, hilightNext : function(dir) { if (this.hilightNodes.length == 0) return; if (this.currentIndex >= 0) { this.hilightNodes[this.currentIndex] .setAttribute("style", "color:black;background:yellow"); } this.currentIndex += dir; if (this.currentIndex >= this.hilightNodes.length) { this.currentIndex = 0; } else if (this.currentIndex < 0) { this.currentIndex = this.hilightNodes.length -1; } var n = this.hilightNodes[this.currentIndex]; if (n != null) { n.setAttribute("style", "color:white;background:green"); this.scrollToElem(n); } }, scrollToElem : function(ele) { window.scrollTo(0, getElementYpos(ele) - window.innerHeight / 2); // 指定したエレメントのY座標を取得 function getElementYpos(ele){ var y = 0; var tmp = ele; while (tmp.nodeName != "BODY") { y += tmp.offsetTop; tmp = tmp.offsetParent; } return y; } }, followLink : function() { if (this.hilightNodes.length == 0) return; var node = this.hilightNodes[this.currentIndex]; var name = node.nodeName; while (name != "BODY") { if (name == "A") { var url = node.getAttribute("href"); if (url != "" && url != null) { SearchBox.onEscapeKey(null); node.click(); break; } } node = node.parentNode; name = node.nodeName; } } } document.addEventListener("keypress", function(e) { if (e.keyCode == 47 && (e.ctrlKey || e.metaKey)) { // ctrl/cmd + "/" if (document.getElementById(SearchBox.INPUT_BOX_ID) == null) { SearchBox.create(); } } }, false); })(document);