Created
July 20, 2009 11:43
-
-
Save mallowlabs/150277 to your computer and use it in GitHub Desktop.
migemo-unite.js
This file contains 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
//==UserScript== | |
//@name migemo-unite.js | |
//@author mallowlabs (http://d.hatena.ne.jp/mallowlabs) | |
//@version 0.0.1 | |
//@description : Inclemental migemo search with Opera Unite | |
//@include * | |
//==/UserScript== | |
// Usage | |
// Ctrl/Cmd + "/" : 検索ボックス表示 | |
// 検索ボックス表示状態 | |
// Shift + G : 次を選択 | |
// Shift + Ctrl + G : 前を選択 | |
// Enter : 選択項目のリンクを開く | |
// Ctrl/Cmd + "/" : リンクのみから検索モードとトグル(文字が赤い時はリンクのみ) | |
// Escape : 検索ボックス非表示 | |
(function(document) { | |
var MigemoServer = { | |
MIGEMO_URL : "http://home.mallowlabs.operaunite.com/migemo/?", | |
SCRIPT_ID : "opera-migemo-script", | |
isBusy : false, | |
send : function(query) { | |
if (this.isBusy || query == "" || query.length < 3) 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 = "utf-8"; | |
s.src = this.MIGEMO_URL + query; | |
}, | |
callback : function(json) { | |
SearchBox.setResult(json); | |
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); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment