Skip to content

Instantly share code, notes, and snippets.

@azu
Created March 17, 2009 09:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save azu/80420 to your computer and use it in GitHub Desktop.
Save azu/80420 to your computer and use it in GitHub Desktop.
// ==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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').
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