Created
March 17, 2009 09:24
-
-
Save azu/80420 to your computer and use it in GitHub Desktop.
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 word highlight | |
// @namespace http://ss-o.net/ | |
// @description keywords highlight for Google Search and All | |
// @include http://* | |
// @include https://* | |
// ==/UserScript== | |
// スクロール中のみハイライトするように変えた。 | |
//console.time("highlight"); | |
(function word_hightlight(loaded){ | |
if (!loaded && window.opera && document.readyState == 'interactive') { | |
document.addEventListener('DOMContentLoaded', function(){word_hightlight(true);}, false); | |
return; | |
} | |
if (document.contentType && !/html/i.test(document.contentType)) | |
return; | |
//var s = new Date-0; | |
var UP_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAAQ0lEQVR42mNgGAjw+vXr/yBMSB1RAGYYVQxFN4wiQ3EZRpahhAwjyVBChpBkKCHNJBlKSBMhTFXDUAwlpIhUzEBtAABtjZyQ3YVdfgAAAABJRU5ErkJggg=='; | |
var DOWN_IMAGE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAAPElEQVR42q3PsQkAMAwDQe2/qoaIK3UhRuEfXPqwJTrbhxwURS+9ff+N6tW2XGFpQyosoVhCsYRiCcXaBhIFnJBtuvEqAAAAAElFTkSuQmCC'; | |
var PRE = 'wordhighlight', ID_PRE = PRE + '_id'; | |
var STYLE_CLASS = '0123456789'.split('').map(function(a,i){return PRE + '_word'+i;}); | |
var setuped = false; | |
var keyword, words = [], word_lists = [], word_inputs_list, layers; | |
init_keyboard(); | |
if (init_keyword() !== false) | |
setup(); | |
function highlight(doc, ext_word) { | |
words = words.filter(function(w){return ! new RegExp(w).test('');}); | |
if (words.length <= 0) | |
return; | |
var _index,_words; | |
if (ext_word && ext_word.words) { | |
_words = ext_word.words; | |
_index = ext_word.index; | |
} else { | |
_words = words; | |
} | |
var exd_words = _words.map(function(w){return new RegExp('(' + w + ')(?!##)', 'ig');}); | |
var xw = ' and (' + _words.map(function(w){return ' contains(translate(self::text(),"abcdefghijklmnopqrstuvwxyz","ABCDEFGHIJKLMNOPQRSTUVWXYZ"),"'+w.toUpperCase()+'") ';}).join(' or ') + ') '; | |
$X('descendant::text()[string-length(normalize-space(self::text())) > 0 ' + xw +' and not(ancestor::textarea) and not(ancestor::script) and not(ancestor::style)]', doc).forEach(function(text_node) { | |
var df, text = text_node.nodeValue, id_index = 0, | |
parent = text_node.parentNode, range = document.createRange(), replace_strings = [], | |
new_text = reduce(exd_words, function(text,ew,i) { | |
var _i = _index || i; | |
return text.replace(ew,function($0,$1) { | |
replace_strings[id_index] = '<layer id="' + ID_PRE + id_index + '" class="' + STYLE_CLASS[_i%10] + '" name="'+PRE+'_word'+_i+'">' + $1 + '</layer>'; | |
return '##'+(id_index++)+'##'; | |
}); | |
}, text). | |
replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'). | |
replace(/##(\d+)##/g, function($0,$1) { | |
return replace_strings[$1] || ''; | |
}); | |
if (replace_strings.length) { | |
try { | |
range.selectNode(text_node); | |
df = range.createContextualFragment(new_text); | |
parent.replaceChild(df, text_node); | |
range.detach(); | |
} catch (e) { | |
error(e); | |
} | |
} | |
}); | |
} | |
function setup(){ | |
setuped = true; | |
var sheet = addCSS([ | |
'layer.' + PRE + '_word0,.' + PRE + '_item0{display:inline;color: black;background-color: rgba(255,255,0,0.5)}', | |
'layer.' + PRE + '_word1,.' + PRE + '_item1{display:inline;background-color: rgba(0,255,255,0.2); color: black;}', | |
'layer.' + PRE + '_word2,.' + PRE + '_item2{display:inline;background-color: rgba(0,0,255,0.2); color: black;}', | |
'layer.' + PRE + '_word3,.' + PRE + '_item3{display:inline;background-color: rgba(0,255,0,0.2); color: black;}', | |
'layer.' + PRE + '_word4,.' + PRE + '_item4{display:inline;background-color: #cc99ff; color: black;}', | |
'layer.' + PRE + '_word5,.' + PRE + '_item5{display:inline;background-color: #ffcc66; color: black;}', | |
'layer.' + PRE + '_word6,.' + PRE + '_item6{display:inline;background-color: #669999; color: black;}', | |
'layer.' + PRE + '_word7,.' + PRE + '_item7{display:inline;background-color: #cc9966; color: black;}', | |
'layer.' + PRE + '_word8,.' + PRE + '_item8{display:inline;background-color: #999999; color: black;}', | |
'layer.' + PRE + '_word9,.' + PRE + '_item9{display:inline;background-color: #cc6699; color: black;}', | |
'#' + PRE + '_words{cursor:move;position:fixed;z-index:1024;right:1em;opacity:0.8;list-style-type:none;margin:0;padding:0;border:1px solid #333;background:#fff;width:auto;bottom:1em;}', | |
'#' + PRE + '_words.fixed{}', | |
'#' + PRE + '_words:hover{opacity:1;}', | |
'#' + PRE + '_words.edit{opacity:1;cursor:default;}', | |
'#' + PRE + '_words.edit li.' + PRE + '_inputs{display:none;}', | |
'#' + PRE + '_words li.' + PRE + '_editer{display:none;}', | |
'#' + PRE + '_words.edit li.' + PRE + '_editer{display:inline-block;}', | |
'#' + PRE + '_words li{display:inline-block;margin:0.2em;line-height:1.5;padding:0;border:none;font-size:medium;}', | |
'#' + PRE + '_words > li {vertical-align:middle;}', | |
'#' + PRE + '_words > li.' + PRE + '_title{display:inline-block;background:#333;color:#fff;padding:0.1em 0.3em;}', | |
'#' + PRE + '_words > li.' + PRE + '_ctrl > input{display:inline-block;margin:0.1em 0.3em;background:#99cc99;border:2px solid #996666;cursor:pointer;}', | |
'#' + PRE + '_word_inputs_list {padding:0;margin:0;}', | |
'#' + PRE + '_word_inputs_list > li > label{cursor:pointer;}', | |
'#' + PRE + '_word_inputs_list > li > input{cursor:pointer;}', | |
'#' + PRE + '_word_inputs_list > li > label.'+PRE+'_movelabel{line-height:12px;display:block;position:relative;background:#cccccc;width:100%;height:12px;text-align:center;margin:0;padding:0;}', | |
'#' + PRE + '_word_inputs_list > li > label.'+PRE+'_movelabel:hover{background:#aaaaaa;}', | |
'#' + PRE + '_word_inputs_list > li > label > input[type=image]{display:inline;vertical-align:top;padding:0;height:12px;}', | |
'#' + PRE + '_textinput{border:none;margin:0px;padding:0px;position:fixed;bottom:0px;left:0.5em;background:#000;color:#fff;font-weight:bold;}', | |
].join('\n')); | |
highlight(document.body); | |
var ul = document.createElement('ul'); | |
ul.id = PRE + '_words'; | |
ul.className = 'fixed'; | |
var title = document.createElement('li'); | |
title.textContent = 'keywords'; | |
title.className = PRE + '_title'; | |
ul.appendChild(title); | |
var editer = document.createElement('li'); | |
editer.className = PRE + '_editer'; | |
var text_input = document.createElement('input'); | |
text_input.type = 'text'; | |
editer.appendChild(text_input); | |
ul.appendChild(editer); | |
var ctrl = document.createElement('li'); | |
ctrl.className = PRE + '_ctrl'; | |
var edit = document.createElement('input'); | |
edit.type = 'button'; | |
edit.value = 'edit'; | |
ctrl.appendChild(edit); | |
edit.addEventListener('click',function(){ | |
if (ul.className == 'edit') { | |
edit.value = 'edit'; | |
ul.className = ''; | |
keyword = text_input.value; | |
init_words(); | |
resetup(); | |
} else { | |
ul.className = 'edit'; | |
edit.value = 'set'; | |
text_input.value = keyword; | |
} | |
},false); | |
var off = document.createElement('input'); | |
off.type = 'button'; | |
off.value = 'off'; | |
ctrl.appendChild(off); | |
off.addEventListener('click',function(){ | |
document.body.removeChild(ul); | |
$X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body).forEach(function(layer,i){ | |
layer.parentNode.replaceChild(document.createTextNode(layer.textContent),layer); | |
}); | |
sheet.disable = true; | |
if (addCSS.__style.parentNode) addCSS.__root.removeChild(addCSS.__style); | |
window.name = ''; | |
word_lists = []; | |
setuped = false; | |
},false); | |
var _li = document.createElement('li'); | |
word_inputs_list = document.createElement('ul'); | |
_li.appendChild(word_inputs_list); | |
word_inputs_list.id = PRE + '_word_inputs_list'; | |
_li.className = PRE + '_inputs'; | |
ul.appendChild(_li); | |
ul.appendChild(ctrl); | |
word_lists = create_inputlist(); | |
document.body.appendChild(ul); | |
var drag = endrag(ul,{x:'right',y:'bottom'}); | |
drag.hook('__drag_begin', function(e){ | |
if (this.element && this.element.className === 'edit') | |
return false; | |
}); | |
layers = $X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body); | |
if (window.AutoPagerize) init(); | |
else window.addEventListener('GM_AutoPagerizeLoaded',init,false); | |
timerOn = false; | |
document.addEventListener('DOMMouseScroll', function(){ | |
if(!timerOn){ | |
rev_checkbox(); | |
timerOn = true; | |
}else{ | |
clearTimeout(deltimer); | |
} | |
deltimer = setTimeout(function(){ | |
rev_checkbox(); | |
timerOn = false; | |
},1000); | |
}, false); | |
} | |
function rev_checkbox(){ | |
var inputTag = document.getElementById("wordhighlight_word_inputs_list").getElementsByTagName("input"); | |
for(var i=0,l =inputTag.length;i<l;i++){ | |
if(inputTag[i].getAttribute("type") == "checkbox"){ | |
inputTag[i].click(); | |
} | |
} | |
} | |
function resetup() { | |
$X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body).forEach(function(layer,i){ | |
layer.parentNode.replaceChild(document.createTextNode(layer.textContent),layer); | |
}); | |
word_lists.forEach(function(item){item.item.parentNode.removeChild(item.item);}); | |
window.name = PRE + '::' + encodeURIComponent(keyword); | |
highlight(document.body); | |
word_lists = create_inputlist(); | |
} | |
function move(node) { | |
if (!node) return; | |
node.style.outline = node.style.WebkitOutline = '4px solid #33ccff'; | |
var pos = node.parentNode.getBoundingClientRect(); | |
document.documentElement.scrollTop = document.body.scrollTop = | |
pos.top + window.pageYOffset - window.innerHeight/2 + (pos.bottom - pos.top); | |
setTimeout(function(){ | |
node.style.outline = node.style.WebkitOutline = 'none'; | |
},2000); | |
} | |
function create_inputlist() { | |
return words.map(function(w, _i){ | |
var li = document.createElement('li'); | |
li.className = PRE + '_item' + _i%10; | |
var next = document.createElement('input'); | |
var prev = document.createElement('input'); | |
var next_label = document.createElement('label'); | |
var prev_label = document.createElement('label'); | |
next.type = prev.type = 'image'; | |
next.src = DOWN_IMAGE; | |
prev.src = UP_IMAGE; | |
next.id = next_label.htmlFor = PRE + '_next_button' + _i; | |
prev.id = prev_label.htmlFor = PRE + '_prev_button' + _i; | |
prev_label.className = next_label.className = PRE + '_movelabel'; | |
var pos = -1; | |
next.addEventListener('click',function(){ | |
var layers = $X('descendant::layer[@name="' + PRE + '_word' + _i +'"]', document.body); | |
move(layers[++pos] || (pos=1,layers[0])); | |
},false); | |
prev.addEventListener('click',function(){ | |
var layers = $X('descendant::layer[@name="' + PRE + '_word' + _i +'"]', document.body); | |
move(layers[--pos] || (pos=layers.length-1,layers[pos--])); | |
},false); | |
prev_label.appendChild(prev); | |
li.appendChild(prev_label); | |
var label = document.createElement('label'); | |
var xpath = 'count(descendant::layer[@name="' + PRE + '_word' + _i +'"])'; | |
label.textContent = w + '(' + $X(xpath, document.body,null,XPathResult.NUMBER_TYPE).numberValue + ')'; | |
var check = document.createElement('input'); | |
check.type = 'checkbox'; | |
check.checked = false; | |
var _id = check.id = ID_PRE + '_check' + _i; | |
label.htmlFor = _id; | |
label.className = PRE + '_label' + _i % 10; | |
li.appendChild(check); | |
li.appendChild(label); | |
next_label.appendChild(next); | |
li.appendChild(next_label); | |
word_inputs_list.appendChild(li); | |
check.addEventListener('change', function(){ | |
var layers = $X('descendant::layer[@name="' + PRE + '_word' + _i +'"]', document.body); | |
if (check.checked) { | |
highlight(document.body,{words:[w],index:_i}); | |
} else { | |
layers.forEach(function(layer,i){ | |
layer.parentNode.replaceChild(document.createTextNode(layer.textContent),layer); | |
}); | |
} | |
},false); | |
return {item:li,word:w,label:label,xpath:xpath}; | |
}); | |
} | |
function endrag(element,opt){ | |
endrag = function(element,opt){ | |
return new endrag.proto(element,opt||{}); | |
} | |
endrag.proto = function(elem,opt){ | |
var self = this; | |
this.element = elem; | |
this.style = elem.style; | |
var _x = opt.x !== 'right'; | |
var _y = opt.y !== 'bottom'; | |
this.x = _x ? 'left' : 'right'; | |
this.y = _y ? 'top' : 'bottom'; | |
this.xd = _x ? -1 : 1; | |
this.yd = _y ? -1 : 1; | |
this.computed_style = document.defaultView.getComputedStyle(elem, ''); | |
this.drag_begin = function(e){self.__drag_begin(e);}; | |
elem.addEventListener('mousedown', this.drag_begin, false); | |
this.dragging = function(e){self.__dragging(e)}; | |
document.addEventListener('mousemove', this.dragging, false); | |
this.drag_end = function(e){self.__drag_end(e)}; | |
document.addEventListener('mouseup', this.drag_end, false); | |
}; | |
endrag.proto.prototype = { | |
__drag_begin:function(e){ | |
var _c = this.computed_style; | |
this.isDragging = true; | |
this.position = { | |
_x:parseFloat(_c[this.x]), | |
_y:parseFloat(_c[this.y]), | |
x:e.pageX, | |
y:e.pageY | |
}; | |
e.preventDefault(); | |
}, | |
__dragging:function(e){ | |
if (!this.isDragging) return; | |
var x = e.pageX, y = e.pageY, p = this.position; | |
p._x = p._x + (p.x - x) * this.xd; | |
p._y = p._y + (p.y - y) * this.yd; | |
this.style[this.x] = p._x + 'px'; | |
this.style[this.y] = p._y + 'px'; | |
p.x = x; | |
p.y = y; | |
}, | |
__drag_end:function(e){ | |
if (this.isDragging) | |
this.isDragging = false; | |
}, | |
hook:function(method,func){ | |
if (typeof this[method] === 'function') { | |
var o = this[method]; | |
this[method] = function(){ | |
if (func.apply(this,arguments) === false) | |
return; | |
o.apply(this,arguments); | |
}; | |
} | |
} | |
}; | |
return endrag(element,opt); | |
} | |
function init_keyword(){ | |
var ref = document.referrer; | |
var name = window.name; | |
var host = location.host; | |
if (/google\./.test(host) && /^\/search/.test(location.pathname)) { | |
var _q = $X('descendant::input[@name="q"]',document.body)[0]; | |
keyword = clean(_q.value); | |
window.name = PRE + '::' + encodeURIComponent(keyword); | |
} else if (window.name.indexOf(PRE) == 0) { | |
keyword = new RegExp(PRE + '\\d*::(.+)').exec(decodeURIComponent(window.name))[1]; | |
} else if (/google/.test(ref)) { | |
var _a = document.createElement('a'); | |
_a.href = ref; | |
if (!/google\./.test(_a.host)) return; | |
keyword = clean(decodeURIComponent(/[&?]q=([^&]+)/i.exec(_a.search)[1])); | |
window.name = PRE + '::' + encodeURIComponent(keyword); | |
} else { | |
return false; | |
} | |
init_words(); | |
return true; | |
} | |
function clean(str) { | |
return str.replace(/(\s?(?:site|(?:all)?in(?:url|title|anchor|text)):|(\s|^)-)\S*/gi,''); | |
} | |
function init_words(){ | |
keyword && (words = keyword.split(/[\+\s\.:\|#]/)); | |
} | |
function init_minibuffer() { | |
if (window.Minibuffer) | |
document.removeEventListener('keypress', keyhandler, false); | |
var mini = window.Minibuffer; | |
mini.addCommand({ | |
name: 'keyword-search', | |
command: function(stdin){ | |
keyword += ' ' + this.args.join(' '); | |
init_words(); | |
if (setuped) resetup(); | |
else setup(); | |
return stdin; | |
} | |
}); | |
mini.addShortcutkey({ | |
key:'M-n', | |
command:function(e){ | |
layers || (layers = $X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body)); | |
move(layers[keyhandler.pos++] || (keyhandler.pos=1,layers[0])); | |
}, | |
description: 'emphasis next keyword' | |
}); | |
mini.addShortcutkey({ | |
key:'M-N', | |
command:function(e){ | |
layers || (layers = $X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body)); | |
move(layers[keyhandler.pos--] || (keyhandler.pos=layers.length-1,layers[keyhandler.pos--])); | |
}, | |
description: 'emphasis prev keyword' | |
}); | |
} | |
function init_keyboard() { | |
if (window.opera) { | |
} else if (window.Minibuffer) { | |
init_minibuffer(); | |
return; | |
} else { | |
window.addEventListener('GM_MinibufferLoaded', init_minibuffer, false); | |
} | |
document.addEventListener('keypress', keyhandler, false); | |
} | |
function keyhandler(evt){ | |
if (keyword){ | |
var key = String.fromCharCode(evt.which); | |
if (key && (key === 'n' || key === 'N')) { | |
layers || (layers = $X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body)); | |
if (key === 'n') { | |
move(layers[keyhandler.pos++] || (keyhandler.pos=1,layers[0])); | |
} else { | |
move(layers[keyhandler.pos--] || (keyhandler.pos=layers.length-1,layers[keyhandler.pos--])); | |
} | |
} | |
} | |
if (!keyhandler.begin && evt.which === 47 && !/^(?:input|textarea)$/i.test(evt.target.localName)) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
keyhandler.begin = true; | |
var input = keyhandler.input = document.createElement('input'); | |
input.id = PRE + '_textinput'; | |
input.addEventListener('input',function(e) { | |
var text = input.value; | |
if (!/\S/.test(text)) return; | |
var x = 'descendant::text()[contains(self::text(),"' + text + '") and not(ancestor::textarea) and not(ancestor::script) and not(ancestor::style)]/parent::*'; | |
var node = document.evaluate(x, document.body, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; | |
if (node) { | |
move(node); | |
} | |
},false); | |
document.body.appendChild(input); | |
input.focus(); | |
} else if (evt.which === 13 && evt.target == keyhandler.input) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
keyword += ' ' + keyhandler.input.value; | |
init_words(); | |
if (setuped) resetup(); | |
else setup(); | |
keyhandler.begin = false; | |
document.body.removeChild(keyhandler.input); | |
} | |
} | |
function init(){ | |
AutoPagerize.addFilter(function(docs) { | |
docs.forEach(highlight); | |
word_lists.forEach(function(item){ | |
item.label.textContent = item.word + '(' + $X(item.xpath, document.body,null,XPathResult.NUMBER_TYPE).numberValue + ')'; | |
}); | |
layers = $X('descendant::layer[starts-with(@name,"' + PRE + '_word")]', document.body); | |
}); | |
} | |
function $X (exp, context, resolver, result_type) { | |
context || (context = document); | |
var Doc = context.ownerDocument || context; | |
var result = Doc.evaluate(exp, context, resolver, result_type || XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |
if (result_type) return result; | |
for (var i = 0, len = result.snapshotLength, res = new Array(len); i < len; i++) { | |
res[i] = result.snapshotItem(i); | |
} | |
return res; | |
} | |
// reduce https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/reduce#Compatibility | |
function reduce(arr, fun){ | |
var len = arr.length, i = 0, rv; | |
if (arguments.length >= 3) rv = arguments[2]; | |
else {do { | |
if (i in arr) { | |
rv = arr[i++];break; | |
} | |
if (++i >= len) throw new TypeError(); | |
} while (true)}; | |
for (; i < len; i++) if (i in arr) rv = fun.call(null, rv, arr[i], i, arr); | |
return rv; | |
} | |
function error(e){ | |
if (window.opera) { | |
opera.postError(e); | |
} else if (window.console) { | |
console.error(e); | |
} | |
} | |
function addCSS(css){ | |
var sheet, self = arguments.callee; | |
if (document.createStyleSheet) { // for IE | |
sheet = document.createStyleSheet(); | |
sheet.cssText = css; | |
return sheet; | |
} else if (!self.__style || !self.__root) { | |
sheet = document.createElement('style'); | |
sheet.type = 'text/css'; | |
self.__style = sheet; | |
self.__root = document.getElementsByTagName('head')[0] || document.documentElement; | |
} | |
sheet = self.__style.cloneNode(false); | |
sheet.textContent = css; | |
return self.__root.appendChild(sheet).sheet; | |
} | |
//alert(new Date-s); | |
})(); | |
//console.timeEnd("highlight"); | |
// Bench: http://www.google.com/search?hl=en&q=HTML+5+Markup+Language |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment