Skip to content

Instantly share code, notes, and snippets.

@tung
Created April 4, 2010 21:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tung/355697 to your computer and use it in GitHub Desktop.
Save tung/355697 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name HN Toolkit
// @namespace http://news.ycombinator.com
// @description Version 1.8 - Various useful things (blacklist/style, saved links, searchyc, splitview, PRE-fix, new preview, title rename) for Hacker News
// @include http://news.ycombinator.com/*
// ==/UserScript==
/*
Written by Xichekolas ... License: 'do whatever you want with it'.
http://news.ycombinator.com/user?id=Xichekolas
Changelog:
1.8a - Tweaked by tung to work in Chrome (http://news.ycombinator.com/user?id=tung)
1.8 - Added saved links, made new preview side-by-side, made blacklister more general.
1.7 - Added title rename.
1.63 - Fixed several small bugs and made blacklist apply to new links on main page.
1.62 - Fixed event listener bug by switching to GM_xmlhttpRequest()
1.61 - Fixed preview to work on index (silly me).
1.6 - Added preview for new items.
1.51 - Made PRE fix dynamic instead of some fixed amount. No more setting
it to a random number of EMs.
1.5 - Added the PRE layout fix given to us by gojomo. Also cleaned up
some code and removed the describd stuff, since the site does
that for us now.
1.42 - Fixed splitview to work correctly after flag link was added.
1.41 - Fixed splitview to not be so ugly and error prone.
1.4 - Added splitview. (nirmal's brilliant idea: http://nirmalpatel.com/#hnsplitview)
1.33 - Fixed Search YC display at bottom on pages without a footer
<1.33 - I wasn't really versioning at this point, but rest assured that
neat stuff happened.
*/
/* Tung: Spliced from 4chan X. */
if ((typeof GM_getValue == 'undefined') || (GM_getValue('a', 'b') == undefined)) {//chrome compat
GM_addStyle = function(css) {
var style = document.createElement('style');
style.textContent = css;
document.getElementsByTagName('head')[0].appendChild(style);
}
GM_deleteValue = function(name) {
localStorage.removeItem(name);
}
GM_getValue = function(name, defaultValue) {
var value = localStorage.getItem(name);
if (!value)
return defaultValue;
var type = value[0];
value = value.substring(1);
switch (type) {
case 'b':
return value == 'true';
case 'n':
return Number(value);
default:
return value;
}
}
GM_log = function(message) {
console.log(message);
}
GM_registerMenuCommand = function(name, funk) {
//todo
}
GM_setValue = function(name, value) {
value = (typeof value)[0] + value;
localStorage.setItem(name, value);
}
}
// Code is getting kinda messy... I should clean it up someday.
// Just assume the stuff below will work.
var body = document.getElementsByTagName('body')[0];
function byId(id) {
return document.getElementById(id);
}
function add_listeners(id, cl, ol) {
return function(ev) {
// alert('here!');
var elem = byId(id);
if (top.location != document.location) { // If we are inside a frameset.
if (ol) { // If the link is to an outside site, load both frames.
elem.target = "article";
elem.addEventListener('click', function(ev) {
document.location = cl;
}, false);
} else { // Break out of frames.
elem.target = "_top";
}
} else if (ol) { // Not in frames but an outside link, so create frames.
elem.addEventListener('click', function(ev) {
var fs = document.createElement('frameset');
var f1 = document.createElement('frame');
var f2 = document.createElement('frame');
fs.cols = '50%, 50%';
fs.appendChild(f1);
fs.appendChild(f2);
f1.src = elem.href;
f1.name = 'article';
f2.src = cl;
f2.name = 'comments';
document.title = elem.innerHTML;
body.innerHTML = '';
body.style['margin'] = 0;
body.appendChild(fs);
ev.preventDefault();
}, false);
}
}
}
var blacklist = GM_getValue('hndomains', '');
if (blacklist.length > 0) {
blacklist = blacklist.split(/\n/);
}
function getQueueArray() {
var queue = GM_getValue('queue', '');
if (queue.length > 0) {
queue = queue.split('|');
} else {
queue = [];
}
return queue;
}
function showQueue() {
var main_tr = document.evaluate("//table//tr[3]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null).snapshotItem(0);
var content = '<td><ol>';
var queue = getQueueArray();
for (var i = 0; i < queue.length; i += 3) {
content += "<li><a href='" + queue[i+1] + "'>" + queue[i].split('__').join(' ') + "</a> | <a href='" + queue[i+2] + "'>comments</a> | <a href='#' class='hntqrem'>remove</a></li>";
}
content += "</ol></td>";
main_tr.innerHTML = content;
var xpath_rems = document.evaluate("//a[@class='hntqrem']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var n = 0; n < xpath_rems.snapshotLength; n++) {
var link = xpath_rems.snapshotItem(n);
link.addEventListener('click', remFromQueue(n*3), true);
}
}
function saveToQueue(savetag) {
return function(ev) {
var atag = savetag.parentNode.parentNode.previousSibling.lastChild.firstChild;
var queue = getQueueArray();
var newitem = atag.innerHTML.split(' ').join('__');
queue.push(newitem);
queue.push(atag.href);
queue.push(atag.parentNode.parentNode.nextSibling.lastChild.childNodes[4].href);
byId('hnqueue').innerHTML = "saved links (" + Math.floor(queue.length / 3) + ")";
queue = queue.join('|');
GM_setValue('queue', queue);
savetag.parentNode.replaceChild(document.createTextNode('saved'), savetag);
}
}
function remFromQueue(i) {
return function(ev) {
var queue = getQueueArray();
var firsthalf = Math.max(i, 0);
var lasthalf = Math.min(queue.length, i+3);
queue = queue.slice(0, firsthalf).concat(queue.slice(lasthalf));
byId('hnqueue').innerHTML = "saved links (" + Math.floor(queue.length / 3) + ")";
queue = queue.join('|');
GM_setValue('queue', queue);
showQueue();
}
}
function applyStyles(listeners) {
var xpathnodes = document.evaluate("//td[@class='title']//a[not(starts-with(@href, '/x?'))]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var queue = GM_getValue('queue', '');
for (var n = 0; n < xpathnodes.snapshotLength; n++) {
var thisnode = xpathnodes.snapshotItem(n);
var link = thisnode.href.toLowerCase();
for (var i = 0; i < blacklist.length; i++) {
if (blacklist[i].length > 0) {
var entry = blacklist[i].split('->');
var styles = 'display: none;';
if (entry.length > 1) {
styles = entry.pop();
}
entry = entry[0].toLowerCase().replace(/^\s+|\s+$/g, '');
var regex = new RegExp(entry);
if (link.match(regex) || thisnode.innerHTML.toLowerCase().match(regex)) {
var grandpa = thisnode.parentNode.parentNode;
grandpa.style.cssText += styles;
grandpa.nextSibling.style.cssText += styles;
grandpa.nextSibling.nextSibling.style.cssText += styles;
}
}
}
var secondline = thisnode.parentNode.parentNode.nextSibling.lastChild;
if (GM_getValue('hnsplitview', false) && (link.indexOf("ycombinator.com/x?fnid") < 0)) {
thisnode.id = "splitlink" + n;
var comments = secondline.childNodes[4].href;
var outside_link = link.indexOf("ycombinator.com/item") < 0;
if (listeners) {
window.addEventListener('load', add_listeners(thisnode.id, comments, outside_link), true);
} else {
add_listeners(thisnode.id, comments, outside_link)();
}
}
if (GM_getValue('hnqueue', false)) {
if (queue.indexOf(link) > -1) {
secondline.innerHTML += " | saved";
} else {
secondline.innerHTML += " | <a href='#' id='" + n + "_hntsave' class='hntsave'>save</a>";
}
}
}
var xpathsaves = document.evaluate("//a[@class='hntsave']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var n = 0; n < xpathsaves.snapshotLength; n++) {
var thisnode = xpathsaves.snapshotItem(n);
thisnode.addEventListener('click', saveToQueue(thisnode), true);
}
}
if (GM_getValue('hntitle', 'Hacker News') != 'Hacker News') {
// Set actual page title.
var fragments = document.title.split('|');
fragments[0] = GM_getValue('hntitle', 'Hacker News') + (fragments.length > 1 ? ' ' : '');
document.title = fragments.join('|');
// Change title in top left too.
var titlehref = document.evaluate("//span[@class='pagetop']//a[@href='news']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (titlehref.snapshotLength > 0) {
var title_el = titlehref.snapshotItem(0);
title_el.innerHTML = GM_getValue('hntitle', title_el.innerHTML);
}
}
if (GM_getValue('hnnewview', false) && window.location.href.match(new RegExp("^http://news.ycombinator.com(/$|/news.*)"))) {
GM_xmlhttpRequest({
method: 'GET',
url: 'http://news.ycombinator.com/newest',
onload: function(xhr) {
var xpathContainer = document.evaluate("//table/tbody", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
if (xpathContainer.snapshotLength > 0) {
// Fix the header and footer so it handles what we are about to do.
var container = xpathContainer.snapshotItem(0);
var firstrow = container.childNodes[0];
firstrow.firstChild.colSpan = "2";
var lastrow = container.childNodes[3];
lastrow.firstChild.colSpan = "2";
var target_tr = container.childNodes[2];
target_tr.vAlign = 'top';
var response = xhr.responseText;
// Really need a better way to do this than a sandbox. Feel free to enlighten me.
var newdiv = document.createElement('div');
newdiv.id = 'hnnewview_sandbox';
newdiv.style.position = 'absolute';
newdiv.style.top = '0px';
newdiv.style.left = '-5454px';
body.appendChild(newdiv);
newdiv.innerHTML = response.substring(response.indexOf('<center>') + 8, response.lastIndexOf('</center>'));
var xpathnp = document.evaluate("//div[@id='hnnewview_sandbox']//table//tr[3]/td", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var newpage = xpathnp.snapshotItem(0);
target_tr.innerHTML += "<td width='50%'><h3>New Items</h3>" + newpage.innerHTML + "</td>";
target_tr.firstChild.innerHTML = "<h3>Top Items</h3>" + target_tr.firstChild.innerHTML;
newdiv.parentNode.removeChild(newdiv);
}
applyStyles(false);
}
});
} else {
applyStyles(true); // No need to wait for new items to load, apply styles now.
}
if (GM_getValue('hnprefix', false)) {
var xpathpres = document.evaluate("//span[@class='comment']//pre", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var n = 0; n < xpathpres.snapshotLength; n++) {
var thispre = xpathpres.snapshotItem(n);
var reduction = (thispre.parentNode.parentNode.parentNode.firstChild.firstChild.width || 0) + 120;
var width = Math.max((window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 800) - reduction, 300);
thispre.style.backgroundColor = '#FFFFFF';
thispre.style.maxWidth = width + 'px';
}
}
// Everything below is for the settings UI.
if (GM_getValue('hnsearchyc', false)) {
var searchForm = "<form name=\"search\" method=\"post\" action=\"http://searchyc.com/search/yc\" style=\"margin: 4px; padding: 0px;\">" +
"<input type=\"text\" size=\"30\" name=\"search[id]\" id=\"search_id\" style=\"margin-right: 10px; line-height: 12pt;\"/>" +
"<input type=\"submit\" value=\"Search YC\" name=\"commit\"/></form>";
if (GM_getValue('hnsearchycbot', false)) {
var yclinks = document.evaluate("//span[@class='yclinks']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (yclinks.snapshotLength > 0) {
var yclparent = yclinks.snapshotItem(yclinks.snapshotLength - 1).parentNode;
var newDiv = document.createElement('div');
newDiv.innerHTML = searchForm
yclparent.insertBefore(newDiv, yclparent.lastChild);
} else {
var center = document.getElementsByTagName('center')[0];
center.innerHTML += "<div style=\"width: 85%; text-align: right;\">" + searchForm + "</div>";
}
} else {
var center = document.getElementsByTagName('center')[0];
center.innerHTML = "<div style=\"width: 85%; text-align: right;\">" + searchForm + "</div>" + center.innerHTML;
}
}
var submitlink = document.evaluate("//a[@href='submit']", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (submitlink.snapshotLength > 0) {
var slparent = submitlink.snapshotItem(0).parentNode;
var editlink = " | <a id=\"hntoolkit\" href=\"#\">toolkit</a>";
slparent.innerHTML += editlink;
if (GM_getValue('hnqueue', false)) {
editlink = " | <a id=\"hnqueue\" href=\"#\">saved links (" + Math.floor(GM_getValue('queue', '').split('|').length / 3) + ")</a>";
slparent.innerHTML += editlink;
}
function checked(value) {
return GM_getValue(value, false) ? " checked=\"checked\"" : "";
}
var checkboxes = {'prefix': {text: 'Apply PRE Fix?', style: ''},
'splitview': {text: 'Open links in split view?', style: ''},
'newview': {text: 'Show new items on front page?', style: ''},
'queue': {text: 'Enable saved links?', style: ''},
'searchyc': {text: 'Display SearchYC?', style: ''},
'searchycbot': {text: 'Bottom of page', style: 'margin-left: 2em;'}};
var editorHTML = "<div id=\"bleditbox\" style=\"" +
"position: absolute; z-index: 54; border: 2px solid red; top: 100; left: -1000; background-color: white; display: none; padding: 1em;\">" +
"<h1 style=\"margin: 0px;\">Hacker News Toolkit</h1>" +
"<a href=\"http://userscripts.org/scripts/show/25039\">http://userscripts.org/scripts/show/25039</a><br/><br/>" +
"One rule per line in the format regex->styles.<br/>The default style is to hide the match,<br/>so you can simply put the regex to blacklist.<br/>Your regex can just be a domain name.<br/>" +
"<textarea id=\"bldomains\" rows=\"5\" cols=\"54\">"+GM_getValue('hndomains', '')+"</textarea><br/>" +
"<br/>Page Title:<br/> <input type=\"text\" id=\"bltitle\" value=\""+GM_getValue('hntitle', 'Hacker News')+"\" size=\"54\"><br/>";
for (name in checkboxes) {
var style = (checkboxes[name].style.length > 0 ? " style=\""+checkboxes[name].style+"\" " : '');
editorHTML += "<br/><input type=\"checkbox\" id=\"bl"+name+"\""+checked('hn'+name) + style + "> " + checkboxes[name].text;
}
body.innerHTML += editorHTML + "<br/><br/><div style=\"position: absolute; bottom: 1em; right: 1em;\"><input type=\"submit\" id=\"blcancel\" value=\"Cancel\">" +
"<input type=\"submit\" id=\"blsave\" value=\"Save\"></div></div>";
function closeSettings(ev) {
for (name in checkboxes) {
byId('bl'+name).checked = GM_getValue('hn'+name, false);
}
byId('bldomains').value = GM_getValue('hndomains', '');
byId('bltitle').value = GM_getValue('hntitle', 'Hacker News');
byId('bleditbox').style['display'] = 'none';
return false;
}
byId('hntoolkit').addEventListener('click', function(ev) {
if (byId('bleditbox').style['display'] == 'none') {
var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth || 800;
byId('bleditbox').style['display'] = 'block';
byId('bleditbox').style['left'] = (w - byId('bleditbox').offsetWidth) / 2;
byId('bldomains').focus();
} else {
closeSettings(ev);
}
return false;
}, true);
byId('blcancel').addEventListener('mouseup', closeSettings, true);
byId('blsave').addEventListener('mouseup', function(ev) {
for (name in checkboxes) {
GM_setValue('hn'+name, byId('bl'+name).checked);
}
GM_setValue('hntitle', byId('bltitle').value);
GM_setValue('hndomains', byId('bldomains').value);
window.location.reload();
return false;
}, true);
if (GM_getValue('hnqueue', false)) {
byId('hnqueue').addEventListener('click', function(ev) { showQueue(); }, true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment