Facepunch: Thread Stuff

By Vampired Last update Dec 30, 2008 — Installed 415 times.
// ==UserScript==
// @name			Facepunch: Thread Stuff
// @description		Adds a few things to thread view
// @author			Vampired (http://forums.facepunchstudios.com/Vampired)
// @include			http://forums.facepunchstudios.com/showthread.php*
// @version			1.0
// ==/UserScript==

/*
Features

* Fixes annoying page scroll thing.
* Adds collapsing lua/code boxes (Mouse over to show menu).
* Resizes big images to fit in browser window.
* Opening a media player closes all others unless control is held.
* Changes page navigation to a dropdown menu. Makes Prev and Next buttons easier to click.

*/

function $(id)
{
	return typeof id == "string" ? document.getElementById(id) : id;
}

function $x(xpath)
{
	var item, arr = [], xpr = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	for(var i = 0; item = xpr.snapshotItem(i); i++)
		arr.push(item);
	return arr;
}

function $A(iterable)
{
	if (!iterable) return [];
	if (iterable.toArray) return iterable.toArray();
	var length = iterable.length || 0, results = new Array(length); 
	while (length--) results[length] = iterable[length];
	return results;
}
var Event = {
	add: function(t, ev, fn) { $(t).addEventListener(ev, fn, false) },
	remove: function(t, ev, fn) { $(t).removeEventListener(ev, fn, false) }
};
var Dom = {
	add: function(dest, type)
	{
		var el = document.createElement(type);
		var dest = $(dest);
		dest.appendChild(el);
		return el;
	},
	addAfter: function(dest, type)
	{
		var dest = $(dest);
		var parent = dest.parentNode;
		
		var newel = document.createElement( type );
		parent.insertBefore(newel, dest.nextSibling)
		return newel;
	},
	addText: function(dest, text)
	{
		var node = document.createTextNode(text);
		var dest = $(dest);
		dest.appendChild(node);
		return node;
	},
	remove: function(el)
	{
		var el = $(el);
		el.parentNode.removeChild(el);
	},
	removeAll: function(el)
	{
		el = $(el);
		if(el.hasChildNodes())
		{
			while(el.childNodes.length >= 1)
				Dom.remove(el.firstChild);
		}
	}
}

Function.prototype.bind = function()
{
	var __method = this, args = $A(arguments), object = args.shift();
	return function()
	{
		return __method.apply(object, args.concat($A(arguments)));
	}
} 

var CODEBOX_NORMAL = 0;
var CODEBOX_HIDDEN = 1;
var CODEBOX_TEXTBOX = 2;

var CODEBOX_LUA = 1;
var CODEBOX_CODE = 2;


// Images
// http://www.greywyvern.com/code/php/binary2base64

var pencil = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAFUSURBVDjLrZM/SAJxGIZdWwuDlnCplkAEm1zkaIiGFFpyMIwGK5KGoK2lphDKkMDg3LLUSIJsSKhIi+684CokOtTiMizCGuzEU5K3vOEgKvtBDe/2Pc8H3x8NAM1fQlx4H9M3pcOWp6TXWmM8A7j0629v1nraiAVC0IrrwATKIgs5xyG5QiE+Z4iQdoeU2oAsnqCSO1NSTu+D9VhqRLD8nIB8F0Q2MgmJDyipCzjvYJkIfpN2UBLG8MpP4dxvQ3ZzGuyyBQ2H+AnOOCBd9aL6soh81A5hyYSGWyCFvxUcerqI4S+CvYVOFPMHxLAq8I3qdHVY5LbBhJzEsCrwutpRFBlUHy6wO2tEYtWAzLELPN2P03kjfj3luqDycV2F8AgefWbEnVqEHa2IznSD6BdsVDNStB0lfh0FPoQjdx8RrAqGzC0YprSgxzsUMOY2bf37N/6Ud1Vc9yYcH50CAAAAAElFTkSuQmCC";
var exclamation = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJPSURBVDjLpZPLS5RhFMYfv9QJlelTQZwRb2OKlKuINuHGLlBEBEOLxAu46oL0F0QQFdWizUCrWnjBaDHgThCMoiKkhUONTqmjmDp2GZ0UnWbmfc/ztrC+GbM2dXbv4ZzfeQ7vefKMMfifyP89IbevNNCYdkN2kawkCZKfSPZTOGTf6Y/m1uflKlC3LvsNTWArr9BT2LAf+W73dn5jHclIBFZyfYWU3or7T4K7AJmbl/yG7EtX1BQXNTVCYgtgbAEAYHlqYHlrsTEVQWr63RZFuqsfDAcdQPrGRR/JF5nKGm9xUxMyr0YBAEXXHgIANq/3ADQobD2J9fAkNiMTMSFb9z8ambMAQER3JC1XttkYGGZXoyZEGyTHRuBuPgBTUu7VSnUAgAUAWutOV2MjZGkehgYUA6O5A0AlkAyRnotiX3MLlFKduYCqAtuGXpyH0XQmOj+TIURt51OzURTYZdBKV2UBSsOIcRp/TVTT4ewK6idECAihtUKOArWcjq/B8tQ6UkUR31+OYXP4sTOdisivrkMyHodWejlXwcC38Fvs8dY5xaIId89VlJy7ACpCNCFCuOp8+BJ6A631gANQSg1mVmOxxGQYRW2nHMha4B5WA3chsv22T5/B13AIicWZmNZ6cMchTXUe81Okzz54pLi0uQWp+TmkZqMwxsBV74Or3od4OISPr0e3SHa3PX0f3HXKofNH/UIG9pZ5PeUth+CyS2EMkEqs4fPEOBJLsyske48/+xD8oxcAYPzs4QaS7RR2kbLTTOTQieczfzfTv8QPldGvTGoF6/8AAAAASUVORK5CYII%3D";
var page_white_text = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVBgZBcExblNBGAbA2ceegTRBuIKOgiihSZNTcC5LUHAihNJR0kGKCDcYJY6D3/77MdOinTvzAgCw8ysThIvn/VojIyMjIyPP+bS1sUQIV2s95pBDDvmbP/mdkft83tpYguZq5Jh/OeaYh+yzy8hTHvNlaxNNczm+la9OTlar1UdA/+C2A4trRCnD3jS8BB1obq2Gk6GU6QbQAS4BUaYSQAf4bhhKKTFdAzrAOwAxEUAH+KEM01SY3gM6wBsEAQB0gJ+maZoC3gI6iPYaAIBJsiRmHU0AALOeFC3aK2cWAACUXe7+AwO0lc9eTHYTAAAAAElFTkSuQmCC";


//
// Collapsable code box
//

var CodeBoxes = {
	menubar: null,	// menubar div
	current: null,	// current codebox mouse over
	
	init: function()
	{
		// Lua code boxes
		// All divs with class = code
		var codes = $x("//div[@class='code']");
		
		for(var i=0; i<codes.length; i++)
		{
			var inner = codes[i].getElementsByClassName("lua")[0]; // should only be one element with class=lua
			
			// Store the actual code in a variable.
			// textContent gets the actual text content stripped of HTML and the like.
			codes[i].v_code = inner.textContent;
			
			codes[i].v_style = CODEBOX_NORMAL;
			codes[i].v_type = CODEBOX_LUA;
			
			Event.add(codes[i], "mouseover", this.event_mouseover.bind(this, codes[i]));
			Event.add(codes[i], "mouseout", this.event_mouseout.bind(this, codes[i]));
		}
		
		// Code code boxes
		// All pre elements with class = code
		var codes = $x("//pre[@class='code']");
		
		for(var i=0; i<codes.length; i++)
		{
			// Store the actual code in a variable.
			// textContent gets the actual text content stripped of HTML and the like.
			codes[i].v_code = codes[i].textContent;
			
			codes[i].v_style = CODEBOX_NORMAL;
			codes[i].v_type = CODEBOX_CODE;
			
			// We put all code inside a span so we can reference it similar to a Lua box.
			codes[i].innerHTML = "<span>"+codes[i].innerHTML+"</span>";
			codes[i].style.paddingTop = "30px";
			
			Event.add(codes[i], "mouseover", this.event_mouseover.bind(this, codes[i]));
			Event.add(codes[i], "mouseout", this.event_mouseout.bind(this, codes[i]));
		}
		
		
		GM_addStyle([
		"#codeboxes_menubar{ position:absolute; display:none; background-color:#DDDDDD; font-family:verdana; padding:1px; margin:1px }",
		"#codeboxes_menubar ul{ padding:0px; margin:0px }",
		"#codeboxes_menubar li{ list-style: none; float:left; margin:2px; }",
		
		".codeboxes_textbox{ margin-top:20px; width:100%; height:200px; }",
		].join(""));
		
		// Menu bar
		
		this.menubar = Dom.add(document.body, "div");
		this.menubar.id = "codeboxes_menubar";
		
		this.menubar.innerHTML = [
		"<ul>",
		"<li><a href=\"javascript:;\" id=\"codebar0\"><img src=\""+exclamation+"\" title=\"Hide\"></a></li>",
		"<li><a href=\"javascript:;\" id=\"codebar1\"><img src=\""+page_white_text+"\" title=\"Show\"></a></li>",
		"<li><a href=\"javascript:;\" id=\"codebar2\"><img src=\""+pencil+"\" title=\"Textbox\"></a></li>",
		"</ul>",
		].join("");
		
		Event.add("codebar0", "click", this.event_click0.bind(this));
		Event.add("codebar1", "click", this.event_click1.bind(this));
		Event.add("codebar2", "click", this.event_click2.bind(this));
		
	},
	
	hideCode: function(codebox)
	{
		// Hide all elements inside
		for(var i=0; i<codebox.childNodes.length; i++)
			if(codebox.childNodes[i].nodeType == 1)
				codebox.childNodes[i].style.display = "none";
	},
	showCode: function(codebox)
	{
		// Show all elements inside
		for(var i=0; i<codebox.childNodes.length; i++)
			if(codebox.childNodes[i].nodeType == 1)
				codebox.childNodes[i].style.display = "";
			
	},
	
	event_click0: function(ev)
	{
		this.current.v_style = CODEBOX_HIDDEN;
		
		// Remove textbox if exists
		if(this.current.v_textbox)
		{
			Dom.remove(this.current.v_textbox);
			this.current.v_textbox = null;
		}
		
		this.hideCode(this.current);
	},
	event_click1: function(ev)
	{
		this.current.v_style = CODEBOX_NORMAL;
		
		// Remove textbox if exists
		if(this.current.v_textbox)
		{
			Dom.remove(this.current.v_textbox);
			this.current.v_textbox = null;
		}
		
		this.showCode(this.current);
	},
	event_click2: function(ev)
	{
		this.current.v_style = CODEBOX_TEXTBOX;
		this.hideCode(this.current);
		
		// Create an editting textbox and assign it to a code box
		
		if(!this.current.v_textbox)
		{
			var textbox = Dom.add(this.current, "textarea");
			textbox.className = "codeboxes_textbox";
			
			textbox.value = this.current.v_code;
			textbox.focus();
			textbox.select();
			
			this.current.v_textbox = textbox;
		}
		
	},
	
	event_mouseover: function(codebox, ev)
	{
		codebox.appendChild(this.menubar);
		
		this.menubar.style.left = codebox.offsetLeft+"px";
		this.menubar.style.top = codebox.offsetTop+"px";
		this.menubar.style.display = "block";
		
		this.current = codebox;
	},
	
	event_mouseout: function(codebox, ev)
	{
		this.menubar.style.display = "none";
		this.current = null;
	},
}

//
// Image Resize
//

ImageResize = {
	on: true,
	
	init: function()
	{
		if(!this.on) return;
		
		Event.add(window, "resize", this.event_resize.bind(this));
		this.updateImages();
	},
	
	toggle: function()
	{
		this.on = !this.on;
	},
	
	updateImages: function()
	{
		if(!this.on) return;
		
		var pageWidth = document.documentElement.clientWidth;
		
		var images = $x("//img[@class='bbimg']");
		for(var i=0; i<images.length; i++)
		{
			var neww = pageWidth - images[i].offsetLeft - 20;
			if(images[i].naturalWidth > neww)
				images[i].width = neww;
			else if(images[i].width < images[i].naturalWidth)
				images[i].width = images[i].naturalWidth;
		}
		
	},
	
	// Events ////////
	
	event_resize: function(ev)
	{
		this.updateImages();
	}
}

//
// Media Player Toggle
//

var MediaPlayerToggle = {

	currentMedia: [],	// array of media players open
	controlPressed: false,	// if control key is pressed
	
	init: function()
	{
		
		//var obj = ;
		
		// Overwrite PlayMedia function
		unsafeWindow.PlayMedia = function(id) { }
		
		var divs = $x("//div[starts-with(@id,'mediabuttons_')]");
		
		for(var i=0; i<divs.length; i++)
		{
			var id = divs[i].id.substr("mediabuttons_".length);
			Event.add(divs[i], "click", this.event_click.bind(this, id));
		}
		
		
		Event.add(window, "keydown", this.event_keydown.bind(this));
		Event.add(window, "keyup", this.event_keyup.bind(this));
	},
	
	event_click: function(id, ev)
	{
		
		// If control key isn't held
		if(!this.controlPressed)
		{
			// Loop all open media windows and close them
			for(var i=0; i<this.currentMedia.length; i++)
			{
				$("mediabuttons_"+this.currentMedia[i]).style.display = "block";
				$("mediadiv_"+this.currentMedia[i]).style.display = "none";
			}
			
			// Empty list
			this.currentMedia = [];
		}
		
		// Open the one we clicked
		
		$("mediabuttons_"+id).style.display = "none";
		$("mediadiv_"+id).style.display = "inline";
		
		// Add to list
		
		this.currentMedia.push(id);
			
	},
	
	event_keyup: function(ev)
	{
		if(ev.keyCode == 17) // control
			this.controlPressed = false;
	},
	
	event_keydown: function(ev)
	{
		if(ev.keyCode == 17) // control
			this.controlPressed = true;
	}

}



//
// Better Page Nav
//

var PageNav = {
	threadid: null,
	
	init: function()
	{
		var divs = $x("//div[@class='pagenav']");
		
		if(!divs[0])
		{
			// Single page thread
			return;
		}
		
		var m = divs[0].innerHTML.match(/Page ([0-9]+) of ([0-9]+)/);
		var currentpage = Number(m[1]);
		var totalpages = Number(m[2]);
		
		var m = divs[0].innerHTML.match(/showthread\.php\?t=([0-9]+)/);
		this.threadid = m[1];
		
		var prevpage = "showthread.php?t="+this.threadid + (currentpage > 2 ? "&page="+(currentpage-1) : "");
		var nextpage = "showthread.php?t="+this.threadid+"&page="+(currentpage+1);
		
		var options = "";
		for(var i=1; i<=totalpages; i++)
		{
			options += "<option"+(i==currentpage ? " selected=true style=\"font-weight:bold\"" : "")+">"+i+"</option>"
		}
		
		var html = ["<table cellspacing=\"1\" cellpadding=\"3\" border=\"0\" class=\"tborder\">",
		"<tbody><tr>",
		//"<td style=\"font-weight: normal;\" class=\"vbmenu_control\">Page "+currentpage+" of "+totalpages+"</td>",
		(currentpage > 1 ? "<td class=\"alt1\"><a title=\"Prev Page\" href=\""+prevpage+"\" class=\"smallfont\" style=\"padding:2px\">< Prev Page</a></td>" : ""),
		"<td class=\"alt2\"><select onchange=\"pagenav_selectChange(this)\">"+options+"</select> of "+totalpages+"</td>",
		(currentpage < totalpages ? "<td class=\"alt1\"><a title=\"Next Page\" href=\""+nextpage+"\" class=\"smallfont\" style=\"padding:2px\">Next Page></a></td>" : ""),
		
		"</tr></tbody></table>"].join("");
		
		for(var i=0; i<divs.length; i++)
		{
			
			var div = Dom.addAfter(divs[i], "div");
			div.setAttribute("align", "right");
			div.innerHTML = html;
			
			divs[i].style.display = "none";
		}
		
		unsafeWindow.pagenav_selectChange = this.event_selectChange.bind(this);
	},
	
	// Events
	
	
	event_selectChange: function(selectobj)
	{
		window.location.href = "showthread.php?t="+this.threadid+"&page="+selectobj.value;
	},
}


// 
// Misc Stuff
//


var MiscStuff = {
	init: function()
	{
		// Fixes annoying scrolling thing.
		document.body.setAttribute("onload","");
	},
}




CodeBoxes.init();
ImageResize.init();
MediaPlayerToggle.init();
PageNav.init();
MiscStuff.init();