-
-
Save anonymous/4fbe008e40fc36d09b63537a381864b0 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 E-Hentai Highlighter (Unlimited version) | |
// @namespace http://userscripts.org/users/106844 | |
// @description Highlighter for E-Hentai by etc. Edited for unlimited number of tags | |
// @include http://e-hentai.org/* | |
// @include https://e-hentai.org/* | |
// @include http://g.e-hentai.org/* | |
// @include https://g.e-hentai.org/* | |
// @include http://exhentai.org/* | |
// @include https://exhentai.org/* | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @version 0.1 | |
// ==/UserScript== | |
// -------------------- DEFAULTS ------------------- | |
var defaults = { | |
defaultColor : '#ec7e7e' , | |
exColor : '#ed6464' , | |
highlighterEnabled : false , | |
filterEnabled : false , | |
opacityEnabled : false , | |
opacity : 0.1 , | |
showTags : true , | |
highlightTags : true , | |
reorderGalleries : true , | |
hideRatedGalleries : false , | |
showHideOption : true , | |
smartForegrounds : false | |
}; | |
var apiurl = "https://e-hentai.org/api.php"; | |
var apimax = 25; | |
// -------------------- /DEFAULTS ------------------- | |
var EHH = { | |
init: function() { | |
EHH.metadatas=[]; | |
EHH.galleryTagsHashtable = {}; | |
EHH.augmentJS(); | |
EHH.numberOfRequestSent=0; | |
EHH.numberOfRequestReceived=0; | |
// settings | |
EHH.settings = { }; | |
for (var key in defaults) | |
EHH.settings[key] = Utils.load(key, defaults[key]); | |
EHH.dontWalk = false; | |
EHH.onPanda = document.URL.indexOf('e-hentai') == -1; | |
EHH.defaultColor = EHH.settings[EHH.onPanda ? 'exColor' : 'defaultColor']; | |
EHH.thumbnails = document.querySelector('.itg .id1') !== null; | |
EHH.gallery = document.querySelector('#taglist') !== null; | |
// User data | |
Utils.migrateSettings(); | |
EHH.keywords = Utils.load('keywords',[ ]); | |
EHH.filters = Utils.load('filters',[ ]); | |
EHH.addMenuItems(); | |
EHH.updateMenu(); | |
// Colors | |
var colors = | |
EHH.onPanda ? { toggle: 'lightblue', toggleHover: 'lightcyan', disable: 'lightgreen', | |
disableHover: 'springgreen', enable: 'salmon', enableHover: 'lightsalmon', | |
row1: '#363940', row2: '#4F535B' } | |
: { toggle: 'slateblue', toggleHover: 'skyblue', disable: 'forestgreen', | |
disableHover: 'mediumseagreen', enable: 'indianred', enableHover: 'darkred', | |
row1: '#F2F0E4', row2: '#EDEBDF' }; | |
var format = function(text) { return text.replace(/%(\w+)/g,function(x) { return colors[x.slice(1)]; }); }; | |
// Permanent style | |
var style = document.createElement('style'); | |
style.innerHTML = format( | |
// popup (general) | |
'#e-HentaiPopup {' + | |
'position: fixed; top: 0; right: 0; padding: 3px; border-radius: 0 !important;' + | |
'border: 1px black solid; z-index: 10; margin: 0 !important; min-width: 0 !important; width: auto !important;' + | |
'}' + | |
'#e-HentaiPopup:not(:hover) *:not(:first-child) { display: none; }' + | |
'#e-HentaiPopup * {' + | |
'font-family: Verdana, Tahoma, Georgia, Dejavu, "Times New Roman", Serif;' + | |
'font-size: 10px;' + | |
'} #e-Header { text-align: center; position: relative; }' + | |
'[mode="default"] [mode="settings"], [mode="settings"] [mode="default"] { display: none; }' + | |
'#e-ToggleMode {' + | |
'cursor: pointer; color: %toggle !important; font-weight: bold;' + | |
'position: absolute; right: 5px; border-bottom: 1px dotted;' + | |
'}' + | |
'#e-ToggleMode:hover { color: %toggleHover !important; }' + | |
'#e-HentaiPopup div[mode] { width: 350px; text-align: left; }' + | |
// user menu | |
'#hideRatedLabel { text-decoration: underline; cursor: pointer; display: none; }' + | |
'#hideRatedLabel.visible { display: inline; }' + | |
'#hideRatedLabel.active { color: red; font-weight: bold; }' + | |
// popup (default view) | |
'#e-HentaiPopup td:nth-child(2) { text-align: right; }' + | |
'#e-HentaiPopup td:nth-child(2) a, #e-HentaiPopup tr:last-child a {' + | |
'cursor: pointer; font-weight: bold; border-bottom: 1px dotted;' + | |
'}' + | |
'#e-HentaiPopup .e-Disable { color: %disable; }' + | |
'#e-HentaiPopup .e-Disable:hover { color: %disableHover; }' + | |
'#e-HentaiPopup .e-Enable { color: %enable; }' + | |
'#e-HentaiPopup .e-Enable:hover { color: %enableHover; }' + | |
'#e-HentaiPopup tr:last-child a:hover { color: black; }' + | |
'#e-HentaiPopup td > span { margin-right: 5px; float: right; }' + | |
'#e-HentaiPopup table { width: 100%; }' + | |
'#e-HentaiPopup textarea { width: 100%; height: 200px; box-sizing: border-box; -moz-box-sizing: border-box; }' + | |
// popup (settings view) | |
'#e-HentaiPopup label { display: block; padding: 2px; }' + | |
'#e-HentaiPopup input[type="checkbox"] { margin: 0 5px 0 0; float: left; }' + | |
'[name="slider"]:not([visible="true"]), [name="slider"]:not([visible="true"]) + span { display: none; }' + | |
'[name="slider"] { margin-left: 20px; width: 250px; }' + | |
'[name="slider"] + span { position: relative; bottom: 7px; }' + | |
'#e-HentaiPopup [mode="settings"] { padding: 10px; box-sizing: border-box; -moz-box-sizing: border-box; }' + | |
'#e-Buttons { text-align: center; padding-top: 20px; }' + | |
'.e-Button {' + | |
'min-width: 100px; height: 25px; line-height: 25px; text-align: center; color: white;' + | |
'background: black; display: inline-block; cursor: pointer; margin-right: 10px;' + | |
'}' + | |
'.e-Button:hover { text-decoration: underline; }' + | |
'.e-Button + input { display: none; }' + | |
'#e-PickerLabel { margin-top: 10px; }' + | |
'#e-ColorPicker + div { width: 30px; height: 18px; display: inline-block; margin-left: 10px; vertical-align: top; }' + | |
// highlight/filter style | |
'.e-Highlighted b { font-weight: inherit; }' + | |
'.e-Highlighted:not(.e-Transparent), [id^="ta_"][style*="background"] { color: black !important; }' + | |
'.e-white:not(.e-Transparent) { color: white !important; }' + | |
'.e-black:not(.e-Transparent) { color: black !important; }' + | |
'.e-Highlighted:not(.e-Transparent) a { color: inherit !important; }' + | |
'.e-Highlighted b { font-weight: bold !important; font-size: 115%; text-decoration: underline; }' + | |
// tag divs | |
'.e-Tags { position: absolute; top: 0px; left: 0px; text-align: left; color: black; ' + | |
'margin-left: 1px; text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;' + | |
'font-weight: bold; font-family: "Segoe UI"; font-size: 12px; line-height: 11px;' + | |
'}' + | |
'.e-Tags > div { padding: 3px; max-width: 70px; overflow: hidden; transition: max-width .5s linear;' + | |
'white-space: nowrap; }' + | |
'.id3:hover .e-Tags > div { max-width: 200px !important; }' + | |
'.itg, #pp { display: flex; flex-flow: row wrap; }' + | |
'.id1 { float: none !important; }' | |
); | |
document.head.appendChild(style); | |
// Mutable style | |
EHH.opaqueFilterCSS = document.createElement('style'); | |
EHH.opaqueFilterCSS.id = 'e-OpaqueFilter'; | |
EHH.opaqueFilterCSSMask = format('{0}#toppane ~ .c, .e-Filtered { display: none !important; }\n' + | |
'{0}tr.color1 { background: %row1 }\n' + | |
'{0}tr.color0 { background: %row2 }\n' + | |
'{1}#toppane ~ .c, .e-Filtered { opacity: {opacity} !important;}\n' + | |
'{1}.e-Filtered:hover { opacity: 1 !important; -webkit-transition: opacity .1s linear;' + | |
'-moz-transition: opacity .1s linear; -o-transition: opacity .1s linear; }'); | |
document.head.appendChild(EHH.opaqueFilterCSS); | |
// Popup | |
EHH.generatePopup(); | |
// Events | |
document.addEventListener('DOMNodeInserted',function(e) { | |
if (e.target.nodeName == 'TBODY') | |
EHH.walk(e.target); | |
},false); //TODO | |
if (EHH.gallery) | |
document.addEventListener('DOMNodeInserted',EHH.updateTagList,false); | |
if (!EHH.gallery && !EHH.thumbnails) | |
EHH.interceptMouseHover(); //TODO | |
// Data synchronization | |
EHH.link('keywords' , 'keywords' , EHH.updatePopup , EHH.clearRegexes , EHH.walk); | |
EHH.link('filters' , 'filters' , EHH.updatePopup , EHH.clearRegexes , EHH.walk); | |
EHH.link('defaultColor' , null , EHH.updatePopup , EHH.walk); | |
EHH.settings.link('defaultColor' , 'defaultColor'); | |
EHH.settings.link('exColor' , 'exColor' ); | |
EHH.settings.link('filterEnabled' , 'filterEnabled' , EHH.updatePopup , EHH.toggleOpacity , EHH.walk); | |
EHH.settings.link('highlighterEnabled' , 'highlighterEnabled' , EHH.updatePopup , EHH.walk); | |
EHH.settings.link('opacityEnabled' , 'opacityEnabled' , EHH.updatePopup , EHH.toggleOpacity); | |
EHH.settings.link('opacity' , 'opacity' , EHH.updatePopup , EHH.toggleOpacity); | |
EHH.settings.link('showTags' , 'showTags' , EHH.toggleTagDivs); | |
EHH.settings.link('highlightTags' , 'highlightTags' , EHH.highlightTags); | |
EHH.settings.link('reorderGalleries' , 'reorderGalleries' , EHH.walk); | |
EHH.settings.link('hideRatedGalleries' , 'hideRatedGalleries' , EHH.updateMenu, EHH.walk); | |
EHH.settings.link('showHideOption' , 'showHideOption' , EHH.updateMenu, EHH.walk); | |
EHH.settings.link('smartForegrounds' , 'smartForegrounds' , EHH.walk); | |
// Start | |
EHH.getGalleryTags(); | |
EHH.toggleOpacity(); | |
EHH.walk(); | |
}, | |
getGalleryTags: function() | |
{ | |
var reqs = EHH.get_reqs(apimax); | |
reqs.forEach(function(elems) { | |
var ids = elems.map(function(e) { | |
var anchor = (e.getElementsByClassName('id3')[0]).firstElementChild; | |
var ref = anchor.href.split('/'); | |
return [ref[4], ref[5]]; | |
}); | |
var gdata = { "method" : "gdata", "gidlist" : ids, "namespace" : 1 }; | |
EHH.send_req(gdata, elems, apiurl); | |
EHH.numberOfRequestReceived=0; | |
EHH.numberOfRequestSent=reqs.length | |
}); | |
}, | |
get_reqs: function (maxlen) { | |
var classes=['id1','id1 e-Highlighted']; | |
var elems = classes.map(function(x) { | |
return Array.prototype.slice.call(document.getElementsByClassName(x)); | |
}); | |
var all = elems[0]; | |
for (var i=1; i < elems.length; i++) all = all.concat(elems[i]); | |
var api_reqs = []; | |
for (var i=0; i<all.length; i+=maxlen) api_reqs.push(all.slice(i, i+maxlen)); | |
return api_reqs; | |
}, | |
send_req: function (gdata, elems, aurl) { | |
var req = new XMLHttpRequest(); | |
req.onreadystatechange = (function() { | |
if (4 === req.readyState) { | |
if (200 !== req.status) { | |
console.error('cannot send request to ' + aurl); | |
} | |
else { | |
var apirsp = JSON.parse(req.responseText); | |
EHH.numberOfRequestReceived+=1; | |
elems.forEach(function(e,i,arr) { | |
EHH.metadatas.push(apirsp.gmetadata[i]); | |
}); | |
if(EHH.numberOfRequestReceived==EHH.numberOfRequestSent){ | |
EHH.metadatas.forEach(function(metadata) { | |
EHH.galleryTagsHashtable[metadata.gid]=metadata.tags; | |
}); | |
EHH.walk(); | |
} | |
} | |
} | |
}); | |
req.open("POST", apiurl, true); | |
req.send(JSON.stringify(gdata)); | |
}, | |
augmentJS: function() { | |
/*Object.getOwnPropertyNames(Array.prototype).forEach(function(x) { | |
NodeList.prototype[x] = Array.prototype[x]; | |
});*/ | |
var linkedObjects = { }; | |
Object.defineProperty(Object.prototype,'link', { | |
enumerable : false, | |
configurable : false, | |
writable : false, | |
value : function(localProperty,storedProperty,onChangeCallbacks) { | |
var currentValue = this[localProperty], args = arguments; | |
var get = function() { return currentValue; }; | |
var set = function(value) { | |
currentValue = value; | |
if (storedProperty) Utils.save(storedProperty,currentValue); | |
for (var i=2;i<args.length;++i) { | |
if (args[i]) | |
args[i](currentValue); | |
} | |
}; | |
delete this[localProperty]; | |
var descriptor = { get: get, set: set, enumerable: true, configurable: true }; | |
Object.defineProperty(this,localProperty,descriptor); | |
linkedObjects[storedProperty] = { object: this, key: localProperty }; | |
} | |
}); | |
window.addEventListener('storage',function(e) { | |
if (!linkedObjects.hasOwnProperty(e.key)) return; | |
var target = linkedObjects[e.key]; | |
target.object[target.key] = JSON.parse(e.newValue); | |
},false); | |
}, | |
addMenuItems: function() { | |
var target = document.querySelector('#searchbox .nopm + .nopm, .nosel + div > form > div + div'); | |
if (!target) return; | |
var label = document.createElement('label'); | |
label.id = 'hideRatedLabel'; | |
var input = document.createElement('input'); | |
input.type = 'checkbox'; | |
Utils.linkCheckbox(input,EHH.settings,'hideRatedGalleries'); | |
label.appendChild(input); | |
label.appendChild(document.createTextNode('Hide rated galleries')); | |
label.setAttribute('title', 'Note that you must use a star color other than yellow in order for this feature to work.'); | |
target.appendChild(document.createTextNode('\u00A0')); | |
target.appendChild(document.createTextNode('\u00A0')); | |
target.appendChild(label); | |
}, | |
updateMenu: function() { | |
EHH.settings.doFilterRated = (EHH.settings.showHideOption && EHH.settings.hideRatedGalleries); | |
if (!document.getElementById('hideRatedLabel')) return; | |
if (!EHH.settings.showHideOption) className = ''; | |
else if (!EHH.settings.hideRatedGalleries) className = 'visible'; | |
else className = 'visible active'; | |
document.querySelector('#hideRatedLabel input').checked = EHH.settings.hideRatedGalleries; | |
document.querySelector('#hideRatedLabel').className = className; | |
}, | |
generatePopup: function() { | |
EHH.popup = document.createElement('div'); | |
EHH.popup.id = 'e-HentaiPopup'; | |
EHH.popup.className = 'ido'; | |
EHH.popup.setAttribute('mode','default'); | |
EHH.popup.innerHTML = | |
'<div id="e-Header">' + | |
'<b>E-H Highlighter</b>' + | |
'<a id="e-ToggleMode" target="settings">Show settings</a>' + | |
'</div><hr/>' + | |
'<div mode="default">' + | |
'<table align="right">' + | |
'<tr><td style="text-align:left">Keywords:</td><td><a id="e-HighlighterSwitch">Highlighter: enabled</a></tr>' + | |
'<tr><td colspan="2"><textarea></textarea></td></tr>' + | |
'<tr><td style="text-align:left">Filters:</td><td><a id="e-FilterSwitch">Filter: enabled</a></td></tr>' + | |
'<tr><td colspan="2"><textarea></textarea></td></tr>' + | |
'<tr><td colspan="2"><a id="e-PopupSave">Save changes</a><span><b>Filtered items:</b> <span id="e-FilteredItems"></span></span></td></tr>' + | |
'</table>' + | |
'</div>' + | |
'<div mode="settings">' + | |
'<label><input type="checkbox" id="opacitySwitch"> Enable opacity mode for filtered items</label>' + | |
'<input type="range" name="slider" min="0" max="100"> <span></span>' + | |
'<label><input type="checkbox" id="tagDivSwitch">Display any tags matching one or more highlight keywords in front of the gallery thumbnails</label>' + | |
'<label><input type="checkbox" id="highlightTagSwitch">Apply highlighting and filters to each gallery\'s tag list</label>' + | |
'<label><input type="checkbox" id="reorderGalleriesSwitch">Move highlighted galleries to the top and filtered galleries to the bottom (thumbnail mode only)</label>' + | |
'<label><input type="checkbox" id="showHideOption">Show "hide rated galleries" button on the search box</label>' + | |
'<label><input type="checkbox" id="smartForegrounds">Automatically pick the best foreground color (white or black) for highlighted galleries</label>' + | |
'<label id="e-PickerLabel"><input type="checkbox" style="visibility: hidden">Default highlight color: <input type="color" id="e-ColorPicker"> <div></div></label>' + | |
'<div id="e-Buttons">' + | |
'<div class="e-Button" id="e-Export">Export data</div>' + | |
'<div class="e-Button" id="e-Import">Import data</div><input type="file" accept="application/json">' + | |
'</div>' + | |
'</div>'; | |
document.body.appendChild(EHH.popup); | |
// Popup elements | |
EHH.highlighterSwitch = document.getElementById('e-HighlighterSwitch'); | |
EHH.filterSwitch = document.getElementById('e-FilterSwitch'); | |
var textareas = Utils.query('#e-HentaiPopup textarea'); | |
EHH.highlighterArea = textareas[0]; | |
EHH.filterArea = textareas[1]; | |
// Events (default view) | |
Utils.onClick(document.getElementById('e-ToggleMode'),function() { | |
EHH.popup.setAttribute('mode',this.getAttribute('target')); | |
var showSettings = this.getAttribute('target') == 'settings'; | |
this.innerHTML = (showSettings ? 'Show keywords' : 'Show settings'); | |
this.setAttribute('target',showSettings ? 'default' : 'settings'); | |
}); | |
[EHH.highlighterSwitch,EHH.filterSwitch].forEach(function(x) { | |
Utils.onClick(x,function() { | |
var target = /Highlighter/.test(this.textContent) ? 'highlighterEnabled' : 'filterEnabled'; | |
var status = /enabled/.test(this.textContent); | |
EHH.settings[target] = !status; | |
}); | |
}); | |
Utils.onClick(document.getElementById('e-PopupSave'),function() { | |
var validate = function(regexes) { for (var i=0;i<regexes.length;++i) new RegExp(regexes[i]); }; | |
var keywords = EHH.highlighterArea.value.split(/[\n]/).filter(function(x) { return x.length > 0; }); | |
var filters = EHH.filterArea.value.split(/[\n]/).filter(function(x) { return x.length > 0; }); | |
try { | |
validate(keywords); | |
validate(filters); | |
EHH.dontWalk = true; | |
EHH.keywords = keywords; | |
EHH.filters = filters; | |
EHH.dontWalk = false; | |
EHH.getGalleryTags(); //EHH.walk(); | |
EHH.highlightTags(); | |
} catch (e) { | |
alert('Couldn\'t parse keyword. ' + e.message + '\nSettings have NOT been saved.'); | |
} | |
}); | |
// Events (settings view) | |
Utils.linkCheckbox(document.getElementById('opacitySwitch'),EHH.settings,'opacityEnabled'); | |
Utils.linkCheckbox(document.getElementById('tagDivSwitch'),EHH.settings,'showTags'); | |
Utils.linkCheckbox(document.getElementById('highlightTagSwitch'),EHH.settings,'highlightTags'); | |
Utils.linkCheckbox(document.getElementById('reorderGalleriesSwitch'),EHH.settings,'reorderGalleries'); | |
Utils.linkCheckbox(document.getElementById('showHideOption'),EHH.settings,'showHideOption'); | |
Utils.linkCheckbox(document.getElementById('smartForegrounds'),EHH.settings,'smartForegrounds'); | |
// Opacity slider | |
EHH.slider = EHH.popup.querySelector('[name="slider"]'); | |
if (EHH.slider.type != 'range') EHH.slider = null; // not supported | |
else { | |
EHH.slider.value = EHH.settings.opacity * 100; | |
EHH.slider.nextElementSibling.innerHTML = (Math.floor(EHH.settings.opacity * 10000) / 100) + '%'; | |
EHH.slider.addEventListener('change',function(e) { | |
e.target.nextElementSibling.innerHTML = e.target.value + '%'; | |
EHH.settings.opacity = parseInt(e.target.value,10) / 100; | |
},false); | |
} | |
// Color picker | |
var picker = document.getElementById('e-ColorPicker'), | |
preview = picker.nextElementSibling, | |
supported = picker.type == 'color'; | |
picker.value = preview.style.backgroundColor = EHH.defaultColor; | |
if (supported) picker.style.cssText = 'padding: 0px; border: 0px; background: none; position: relative; top: 3px'; | |
else picker.style.cssText = 'width: 60px; color: black'; | |
preview.style.cssText = (supported ? 'display: none;' : 'background-color: ' + EHH.defaultColor); | |
var lastColor = preview.style.backgroundColor; | |
picker.addEventListener(supported ? 'change' : 'input',function() { | |
preview.style.backgroundColor = picker.value; | |
if (preview.style.backgroundColor == lastColor) return; | |
lastColor = preview.style.backgroundColor; | |
EHH.settings[EHH.onPanda ? 'exColor' : 'defaultColor'] = picker.value; | |
EHH.defaultColor = picker.value; | |
},false); | |
// Import-export functions | |
var importButton = document.getElementById('e-Import'), importInput = importButton.nextElementSibling; | |
Utils.onClick(importButton,function() { importInput.click(); }); | |
importInput.addEventListener('change',function(e) { | |
var reader = new FileReader(); | |
reader.onerror = function(e) { alert('Couldn\'t read the selected file.'); }; | |
reader.onload = function(e) { | |
try { | |
var data = JSON.parse(this.result); | |
if (!data.keywords || !data.filters || !data.settings) throw null; | |
var confirmation = confirm('This will overwrite your data. Are you sure you want to proceed?'); | |
if (confirmation) { | |
EHH.dontWalk = true; | |
EHH.keywords = data.keywords; | |
EHH.filters = data.filters; | |
for (var key in EHH.settings) EHH.settings[key] = data.settings[key]; | |
setTimeout(function() { window.location.reload(); },50); | |
} | |
} | |
catch (_) { alert('Couldn\'t recognize the selected file.'); } | |
}; | |
reader.readAsText(this.files[0]); | |
},false); | |
Utils.onClick(document.getElementById('e-Export'),function() { | |
var result = { keywords: EHH.keywords, filters: EHH.filters, settings: EHH.settings }; | |
var blob = new Blob([JSON.stringify(result,null,2)],{ type: 'application/json' }); | |
var a = document.createElement('a'); | |
a.href = URL.createObjectURL(blob); | |
a.download = 'EHH.settings.' + (new Date().valueOf()) + '.json'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
}); | |
EHH.updatePopup(); | |
}, | |
updatePopup: function() { | |
var updateSwitch = function(target,enable) { | |
target.textContent = target.textContent.replace(/[^\s]+$/,enable ? 'enabled' : 'disabled'); | |
target.className = enable ? 'e-Disable' : 'e-Enable'; | |
var filtered = document.getElementsByClassName('e-Filtered').length; | |
document.getElementById('e-FilteredItems').textContent = filtered; | |
}; | |
EHH.highlighterArea.textContent = EHH.keywords.join('\n'); | |
EHH.filterArea.textContent = EHH.filters.join('\n'); | |
updateSwitch(EHH.highlighterSwitch,EHH.settings.highlighterEnabled); | |
updateSwitch(EHH.filterSwitch,EHH.settings.filterEnabled); | |
document.getElementById('opacitySwitch').checked = EHH.settings.opacityEnabled; | |
}, | |
toggleOpacity: function() { | |
// changes the mutable style to enable or disable opacity | |
EHH.opaqueFilterCSS.innerHTML = EHH.opaqueFilterCSSMask | |
.replace(/\{0\}/g,EHH.settings.opacityEnabled ? '//' : '') | |
.replace(/\{1\}/g,EHH.settings.opacityEnabled ? '' : '//') | |
.replace(/\{opacity\}/,EHH.settings.opacity); | |
if (EHH.slider) EHH.slider.setAttribute('visible',EHH.settings.opacityEnabled); | |
}, | |
toggleTagDivs: function() { | |
Utils.query('.e-Tags').forEach(function(x) { | |
x.style.display = EHH.settings.showTags ? null : 'none'; | |
}); | |
}, | |
clearRegexes: function() { | |
EHH.parse.regexes = null; | |
}, | |
prepareRegexes: function() { | |
/* Returns an object containing two properties: | |
* highlight : an array of keywords; each element is an object with a "type" property ("tag" or "title"), | |
* a "regex" property, a "color" property (null if no color is specified for that keyword) | |
* and optional "excludeTitle" and "excludeTags" properties. Keywords will be checked | |
* sequentially; the first matching keyword with a color specified will decide the item's | |
* color. If no color is found but the item still has to be highlighted, EHH.defaultColor | |
* will be used instead | |
* filters : an object with two properties (tag and title), each one a regular expression to | |
* be applied to the relevant target | |
*/ | |
var splitFilter = function(x,target) { | |
if (x.length > 0 && x[0] == ':') target.tag.push(x.slice(1)); | |
else if (x.length > 0) target.title.push(x); | |
}; | |
var highlight = EHH.keywords.map(function(x) { | |
var type = (x[0] == ':' ? 'tag' : 'title'); | |
if (type == 'tag') x = x.slice(1); | |
var tokens = x.match(/^([^!\/;]+)(![^\/;]+)?(;[^\/]+)?(\/\/?[^\/]+)?$/); | |
if (!tokens) { | |
console.error('Unable to parse highlighter "' + x + '"'); | |
return null; | |
} | |
var include = tokens[1], excludeTitle = tokens[2], excludeTags = tokens[3], color = tokens[4]; | |
return { | |
regex: new RegExp('\^'+include+'\$', 'gi'), | |
excludeTitle: (excludeTitle && excludeTitle.length > 1 ? new RegExp(excludeTitle.slice(1), 'gi') : null), | |
excludeTags: (excludeTags && excludeTags.length > 1 ? new RegExp(excludeTags.slice(1), 'gi') : null), | |
type: type, | |
color: (color ? color.slice(1) : null) | |
}; | |
}); | |
var filters = { title: [ ], tag: [ ] }; | |
EHH.filters.forEach(function(x) { | |
if (x[0] == ':') filters.tag.push(x.slice(1)); | |
else filters.title.push(x); | |
}); | |
if (filters.title.length === 0) filters.title.push('EHH: no active title filter'); | |
if (filters.tag.length === 0) filters.tag.push('EHH: no active tag filter'); | |
filters.title = new RegExp('(' + filters.title.join('|') + ')','i'); | |
filters.tag = new RegExp('(' + filters.tag.join('|') + ')','i'); | |
return { highlight: highlight.filter(function(x) { return x !== null; }), filters: filters }; | |
}, | |
parse: function(title,tags_all,rated) { | |
/* title : a string, the title of the gallery item to be parsed | |
* tags_all : an array of all tags of the gallery | |
* rated : true if the gallery has been rated, false otherwise | |
* Uses the object built by EHH.prepareRegexes to decide the course of action for each gallery | |
* Returns an object with a "result" property indicating what has to be done | |
* Possible values are "highlighted", "filtered" or null for no action | |
* Filters take precedence over highlight keywords | |
* If the gallery item is to be highlighted, the result will also contain three additional properties: | |
* - titleKeywords : a list of title substrings that match one or more keywords (can be empty) | |
* - tagKeywords : a list of matching tags (to be passed to EHH.addTagDiv if EHH.settings.showTags is set, can be empty) | |
* - color : the color the gallery needs to be highlighted in (EHH.defaultColor if the user did not specify any color) | |
*/ | |
if (!EHH.parse.regexes) | |
EHH.parse.regexes = EHH.prepareRegexes(); | |
var regexes = EHH.parse.regexes; | |
if (EHH.settings.doFilterRated && rated) | |
return { result: 'filtered' }; | |
if (EHH.settings.filterEnabled) { | |
var filtered = regexes.filters.title.test(title) || tags_all.some(function(x) { | |
return regexes.filters.tag.test(x);}); | |
if (filtered) return { result: 'filtered' }; | |
} | |
if (EHH.settings.highlighterEnabled) { | |
var titleKeywords = { }, tagKeywords = { }, color = null; | |
regexes.highlight.forEach(function(data) { | |
// apply title exclusion | |
if (data.excludeTitle !== null && title.match(data.excludeTitle)) return; | |
var excluded = false; | |
// apply tag highlights + tag exclusion | |
if (data.type == 'tag' || data.excludeTags !== null) { | |
tags_all.forEach(function(tag) { | |
if (excluded) return; | |
if (data.excludeTags !== null && tag.match(data.excludeTags)) { | |
excluded = true; | |
tagKeywords = { }; | |
return; | |
} | |
if (data.type == 'tag' && !excluded) { | |
if (tagKeywords.hasOwnProperty(tag) || !tag.match(data.regex)) return; | |
tagKeywords[tag] = data.color; | |
if (!color) color = data.color; | |
} | |
}); | |
} | |
// apply title highlights | |
if (data.type == 'title' && !excluded) { | |
var tokens = title.match(data.regex); | |
if (!tokens) return; | |
tokens = tokens.length == 1 ? tokens : tokens.slice(1); | |
for (var i=0;i<tokens.length;++i) titleKeywords[tokens[i]] = true; | |
if (!color) color = data.color; | |
} | |
}); | |
titleKeywords = Object.keys(titleKeywords); | |
if (titleKeywords.length === 0 && Object.keys(tagKeywords).length === 0) return { result: null }; | |
return { result: 'highlighted', titleKeywords: titleKeywords, tagKeywords: tagKeywords, color: color || EHH.defaultColor }; | |
} | |
return { result: null }; | |
}, | |
computeTagColor: function(tag) { | |
// don't use style.backgroundPositionY | |
var y = parseInt(tag.style.cssText.split(/\s/).slice(-1)[0],10); | |
y = -(y+1) / 17; | |
return ['salmon','darkorange','gold','mediumaquamarine','skyblue','mediumorchid'][y]; | |
}, | |
// extracts both the title and a list of tags for a given target | |
// tags are parsed into objects with "tag" and "color" properties representing | |
// respectively the tag itself and the color of their associated tag flag | |
extractData: function(target) { | |
//galleryTagsHashtable | |
if (!target) return null; | |
var title = target.querySelector('.it5 > a, .id2 > a, [class^="t2"] > a') || target.querySelector('.itd a'), | |
tags = Utils.query(target,'.tft, .tfl'); | |
if (!title && target.className.indexOf('t2') === 0) title = target.firstChild; | |
if (!title) return null; | |
if (tags.length > 0) { | |
tags = tags | |
.map(function(x) { | |
var color = EHH.computeTagColor(x); | |
return x.title.split(/, /).map(function(y) { return { tag: y, color: color }; }); | |
}) | |
.reduce(function(x,y) { return x.concat(y); }); | |
} | |
var rated = !!target.querySelector('.irb, .irg, .irr'); | |
var ref = title.href.split('/'); | |
tags_all=EHH.galleryTagsHashtable[ref[4]]; | |
return { title: title, tags: tags, rated: rated, tags_all: tags_all}; | |
}, | |
addTagDiv: function(target,tags) { | |
if (!tags || Object.keys(tags).length === 0) return; | |
var div = document.createElement('div'); | |
div.className = 'e-Tags'; | |
var html = ''; | |
for (t in tags) { | |
var tag = t, color = tags[t]; //.replace(/^.+:/,'') TODO | |
html += '<div style="background-color: ' + color + '">' + tag + '</div>'; | |
} | |
div.innerHTML = html; | |
if (!EHH.settings.showTags) div.style.display = 'none'; | |
var temp = target.getElementsByClassName('id3')[0]; | |
if (temp) temp.firstElementChild.appendChild(div); | |
else target.appendChild(div); | |
}, | |
walk: function(root) { | |
/* walks the DOM to highlight and filter gallery items (or the taglist) */ | |
if (EHH.gallery) { | |
EHH.highlightTags(); | |
return; | |
} | |
if (EHH.dontWalk) return; | |
var removeTagDiv = function(target) { | |
Utils.query(target,'.e-Tags').forEach(function(x) { x.parentNode.removeChild(x); }); | |
}; | |
var editTitle = function(target,keywords) { | |
var temp = target.innerHTML; | |
keywords.forEach(function(keyword) { | |
var length = keyword.length, n = target.innerHTML.indexOf(keyword); | |
while (n != -1) { | |
temp = temp.slice(0,n) + new Array(length+1).join('\0') + temp.slice(n+length); | |
n = target.innerHTML.indexOf(keyword,n+1); | |
} | |
}); | |
return temp.replace(/\0+/g,function(match,start) { | |
return '<b>' + target.innerHTML.slice(start,start+match.length) + '</b>'; | |
}); | |
}; | |
// ---------- | |
var flip = 1, | |
targets = Utils.query('[class^="gtr"], [class^="id1"], div[class^="t2"]'), | |
groups = (EHH.settings.reorderGalleries && EHH.thumbnails ? [ [ ], [ ], [ ] ] : null); | |
targets.forEach(function(target) { | |
var data = EHH.extractData(target); | |
if (data === null) return; | |
// reset element | |
target.className = target.className.replace(/\s?e-\w+/g,''); | |
target.style.cssText = target.style.cssText.replace(/(background-color|order|color):.+?;/g,''); | |
data.title.innerHTML = data.title.innerHTML = data.title.innerHTML.replace(/<\/?b>/g,''); | |
removeTagDiv(target); | |
var parsed = EHH.parse(data.title.textContent,data.tags_all,data.rated); | |
if (parsed.result == 'filtered') { | |
target.className += ' e-Filtered'; | |
if (groups !== null) groups[2].push(target); | |
} | |
else if (parsed.result == 'highlighted') { | |
if (parsed.color[0] != '/') { // background color | |
if (parsed.color != 'transparent') { | |
target.className += ' e-Highlighted'; | |
target.style.cssText += 'background-color: ' + parsed.color + ' !important;'; | |
if (EHH.settings.smartForegrounds) { | |
var bestForeground = EHH.getBestForeground(parsed.color); | |
target.className += ' e-' + bestForeground; | |
} | |
} else { | |
target.className += ' e-Highlighted e-Transparent'; | |
} | |
} else { // foreground color | |
target.className += ' e-Highlighted'; | |
target.style.cssText += 'color: ' + parsed.color.slice(1) + ' !important;'; | |
} | |
data.title.innerHTML = editTitle(data.title,parsed.titleKeywords); | |
if (EHH.thumbnails) EHH.addTagDiv(target,parsed.tagKeywords); | |
if (groups !== null) groups[0].push(target); | |
} | |
else if (groups !== null) | |
groups[1].push(target); | |
if (!/^gtr/.test(target.className)) return; | |
if (parsed.result == 'filtered' && !EHH.opaque) return; | |
flip = (flip+1)%2; | |
if (target.className.indexOf('color') == -1) target.className += ' color' + flip; | |
else target.className = target.className.replace(/color\d/,'color' + flip); | |
}); | |
if (groups !== null) { | |
var order = 1; | |
Array.prototype.concat.apply([ ],groups).forEach(function(g) { | |
g.style.cssText += 'order: ' + (order++) + ';'; | |
}); | |
}; | |
var filtered = document.getElementsByClassName('e-Filtered').length; | |
document.getElementById('e-FilteredItems').textContent = filtered; | |
}, | |
highlightTags: function() { | |
Utils.query('[id^="ta_"]').forEach(function(x) { | |
if (!EHH.settings.highlightTags) x.style.cssText = ''; | |
else { | |
var fullName = x.id.slice(3).replace(/_/g,' '); | |
if (!EHH.parse.regexes) | |
EHH.parse.regexes = EHH.prepareRegexes(); | |
var regexes = EHH.parse.regexes; | |
if (EHH.settings.filterEnabled) { | |
if (regexes.filters.tag.test(fullName)) { | |
x.style.cssText = 'text-decoration: line-through'; | |
} | |
} | |
if (EHH.settings.highlighterEnabled) { | |
regexes.highlight.forEach(function(data) { | |
if (data.type == 'tag' && fullName.match(data.regex)) | |
{ | |
x.style.cssText='background-color: ' + data.color +' !important'; | |
} | |
}); | |
} | |
} | |
}); | |
}, | |
updateTagList: function(e) { | |
if (e.target.nodeName != 'TABLE' || e.target.parentNode.id != 'taglist') return; | |
if (EHH.settings.highlightTags) EHH.highlightTags(); | |
}, | |
interceptMouseHover: function(e) { | |
if (typeof(MutationObserver) != 'function') return; | |
var observer = new MutationObserver(function(e) { | |
var thumbnail = e[0].target; | |
if (thumbnail.style.visibility == 'hidden') | |
Utils.query(thumbnail,'.e-Tags').forEach(function(x) { x.parentNode.removeChild(x); }); | |
else if (EHH.settings.showTags) { | |
var row = document.evaluate('ancestor::tr[1]',thumbnail,null,9,null).singleNodeValue, | |
data = EHH.extractData(row); | |
if (data === null) return; | |
parsed = EHH.parse(data.title.textContent,data.tags); | |
if (parsed.result == 'highlighted') EHH.addTagDiv(thumbnail,parsed.tagKeywords); | |
} | |
}); | |
Utils.query('.it2[id^="i"]').forEach(function(x) { | |
observer.observe(x,{ attributes: true }); | |
}); | |
}, | |
getBestForeground: function(color) { | |
if (!EHH.colorCache) EHH.colorCache = { }; | |
if (EHH.colorCache.hasOwnProperty(color)) return EHH.colorCache[color]; | |
var parsedColor = null, div = null; | |
try { | |
div = document.createElement('div'); | |
div.style.color = color; | |
document.body.appendChild(div); | |
parsedColor = window.getComputedStyle(div).color.match(/(\d+)/g); | |
parsedColor = [ parseInt(parsedColor[0], 10), parseInt(parsedColor[1], 10), parseInt(parsedColor[2], 10) ]; | |
} catch (e) { | |
parsedColor = [ 255, 255, 255 ]; | |
} finally { | |
if (div && div.parentNode) | |
div.parentNode.removeChild(div); | |
} | |
var whiteDistance = Math.sqrt(Math.pow(255 - parsedColor[0], 2) + Math.pow(255 - parsedColor[1], 2) + Math.pow(255 - parsedColor[2], 2)); | |
var blackDistance = Math.sqrt(Math.pow(-parsedColor[0], 2) + Math.pow(-parsedColor[1], 2) + Math.pow(-parsedColor[2], 2)); | |
var result = (whiteDistance > blackDistance ? 'white' : 'black'); | |
EHH.colorCache[color] = result; | |
return result; | |
} | |
}; | |
var Utils = { | |
// moves localStorage-based settings to GM_[gs]etValue when possible | |
migrateSettings: function() { | |
if (typeof(GM_getValue) == 'undefined' || typeof(GM_setValue) == 'undefined') return; | |
if (Utils.load('migrationDone2', false)) return; | |
try { | |
for (var key in EHH.settings) { | |
if (localStorage.getItem(key) === null) continue; | |
EHH.settings[key] = JSON.parse(localStorage.getItem(key)); | |
Utils.save(key, EHH.settings[key]); | |
} | |
['keywords', 'filters'].forEach(function(key) { | |
if (localStorage.getItem(key) === null) return; | |
EHH[key] = JSON.parse(localStorage.getItem(key)); | |
Utils.save(key,EHH[key]); | |
}); | |
Utils.save('migrationDone2', true); | |
} catch (e) { } | |
}, | |
save: function(key,value) { | |
if (typeof(GM_setValue) != 'undefined') GM_setValue(key, JSON.stringify(value)); | |
else localStorage.setItem(key, JSON.stringify(value)); | |
}, | |
load: function(key,def) { | |
var result = null; | |
if (typeof(GM_getValue) != 'undefined') result = GM_getValue(key, null); | |
else result = (localStorage.getItem(key) || null); | |
return (result === null ? def : JSON.parse(result)); | |
}, | |
onClick: function(element,f) { | |
element.addEventListener('click',function(e) { | |
if (e.which != 1) return; | |
if (f) f.call(this); | |
},false); | |
}, | |
linkCheckbox: function(checkbox,object,property) { | |
if (!object) return; | |
if (object.hasOwnProperty(property)) checkbox.checked = object[property]; | |
Utils.onClick(checkbox,function() { object[property] = checkbox.checked; }); | |
}, | |
query: function(root,selector) { | |
if (!selector) | |
return Array.prototype.slice.call(document.querySelectorAll(root),0); | |
else | |
return Array.prototype.slice.call(root.querySelectorAll(selector),0); | |
} | |
}; | |
EHH.init(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment