uniThreads

// ==UserScript==
// @name           uniThreads
// @namespace      http://yilmazkiymaz.com
// @description    Allows reading multi-page Unity forum threads by dynamically adding remaining replies so you don't have to navigate to individual pages.
// @include        http://forum.unity3d.com/*
// @author         Yilmaz Kiymaz
// @license        CC by-sa 3.0 - http://creativecommons.org/licenses/by-sa/3.0/
// ==/UserScript==

var startingReplyNum = 0;
var repliesPerPage = 15;
var numPagesLoaded = 1;

var startString = "&start=";
var dontProcessString = "&dontProcess=true";

var replyTable;

var contentStates = {notLoaded : 0, loading : 1, loaded : 2, added : 3};
var contentState = contentStates.notLoaded;
var contentLoadThreshold;
var contentAddThreshold;
var addedToNotLoadedStateTimeout = 600;

var currentLastElement;
var lastElementStates = {untouched: 0, loading : 1};
var lastElementState = lastElementStates.untouched; 

var bareURL;
var lastRequestedUrl;
var loadTimeout;
var loadTimeoutTime = 10000;

var frame;
var frameDoc;

(function() {
	var postURL = document.URL;
	if(postURL.indexOf("viewtopic") == -1) //Make sure we're viewing a topic
		return;
	if(postURL.indexOf(dontProcessString) != -1) //Make sure we don't process the pages loaded through the hidden iframe
		return;
	if(postURL.indexOf(startString) != -1) { //If the start parameter already exists in the URL, grab it and update startingReplyNum accordingly
		var startValue = gup('start');
		if(startValue != "") startingReplyNum = parseInt(startValue);
		console.log("startingReplyNum updated to " + startingReplyNum);
	}
	bareURL = (postURL.indexOf('&') == -1) ? postURL : postURL.substring(0, postURL.indexOf('&'));

	var forumBorders = document.getElementsByClassName("forumborder");
	if(!forumBorders || forumBorders.length == 0)
		return;
	replyTable = forumBorders[0].childNodes[1];
	currentLastElement = replyTable.lastChild.previousSibling;

	if(forumBorders.length != 1)
		console.log("There is more than 1 node with forumborder class name. Still picking the first's child for reply table but it might cause conflicts.");

	createIFrame(); //Create and add a hidden iframe to the document body
	recalculateThresholds();

	window.addEventListener('scroll', respondToScroll, false); //Set onscroll responder function
})();

function respondToScroll() {
	var scrollPercentage = getScrollPercentage();
	if(contentState == contentStates.notLoaded && scrollPercentage > contentLoadThreshold) { //Load content into iframe
		lastRequestedUrl = bareURL + startString + (startingReplyNum += repliesPerPage) + dontProcessString;
		loadURLIntoFrame();
	}
	if(scrollPercentage > contentAddThreshold) { //Add loaded content into reply table
		if(contentState == contentStates.loaded) {
			addContent();
		} else if(lastElementState == lastElementStates.untouched) {
			console.log("last element set to loading");
			currentLastElement.innerHTML = "<td></td><td><h2>Loading Replies from Page " + Math.floor((startingReplyNum+1)/repliesPerPage + 1).toString() + "</h2></td>";
			lastElementState == lastElementStates.loading;
		}
	}

}

function loadURLIntoFrame() {
	console.log("loading URL into frame");
	contentState = contentStates.loading;
	frame.contentDocument.location.href = lastRequestedUrl;
	loadTimeout = setTimeout(checkFrameLoadStatus, loadTimeoutTime);
}

function checkFrameLoadStatus() {
	if(contentState == contentStates.loading) {
		loadURLIntoFrame();
	}
}

function addContent() {
	if(!frameDoc) {
		console.log("frame doc is null in addContent");
		contentState = contentStates.notLoaded;
		return;
	}
	var newForumBorders = frameDoc.getElementsByClassName("forumborder");
	if(!newForumBorders || newForumBorders.length == 0) {
		console.log("no elements of class forumborder exist in the frame doc");
		contentState = contentStates.notLoaded;
		return;
	}
	var newReplyTable = newForumBorders[0].childNodes[1];

	if(newForumBorders.length != 1)
		console.log("There is more than 1 node with forumborder class name in the IFRAME document. Still picking the first's child for reply table but it might cause conflicts.");
	var forumTitles = frameDoc.getElementsByClassName("forumtitle");
	if(!forumTitles || forumTitles.length == 0) {
		console.log("no elements of class forumtitle exist in the frame doc");
		contentState = contentStates.notLoaded;
		return;
	}
	if(forumTitles[0].firstChild.textContent == "Information") {
		currentLastElement.innerHTML = "<td><\/td><td><h2>End of Thread<\/h2><\/td>";
		RemoveEventHandlersAndExit();
		return;
	}

	var numChildrenAdded = 0;
	for(i=0; i<newReplyTable.childNodes.length; i++) {
		if(i == 0 || i == 1) //Don't add the first two children
			continue;
		var childNode = newReplyTable.childNodes[i];
		replyTable.appendChild(document.importNode(childNode, true));
		numChildrenAdded++;
	}
	numPagesLoaded++;
	console.log("children added = " + numChildrenAdded.toString());
	currentLastElement.innerHTML = "<td><\/td><td><h2>Replies " + (startingReplyNum+1).toString() + " - " + (startingReplyNum+((numChildrenAdded/2 - 2) / 3)).toString() + " from Page " + Math.floor((startingReplyNum+1)/repliesPerPage + 1).toString() + "<\/h2><\/td>";
	lastElementState == lastElementStates.untouched;
	currentLastElement = replyTable.lastChild.previousSibling; 

	recalculateThresholds();

	contentState = contentStates.added;
	setTimeout(function(){
		if(contentState == contentStates.added) {
			console.log("content state changed from added to not loaded properly.");
			contentState = contentStates.notLoaded;
		} else {
			console.log("content state was changed from added to " + contentState.toString() + " by some other function than addContent timeout!");
		}
	}, addedToNotLoadedStateTimeout);
}

function createIFrame() { //Create and add a hidden iFrame to the document for use in loading content from other pages of the thread
	if(frame) //If frame already exists, don't recreate it
		return;
	console.log("creating iframe");
	frame = document.createElement("iframe");
	frame.setAttribute("id", "hidden-frame");
	frame.setAttribute("type", "content");
	frame.setAttribute("collapsed", "true");
	frame.setAttribute("style", "width:0px; height:0px; border: 0px");
	document.documentElement.appendChild(frame);
	frame.addEventListener('load', frameLoaded, false);
}

function frameLoaded() {
	contentState = contentStates.loaded;
	console.log("event listener called");
	frameDoc = frame.contentDocument;
	if(getScrollPercentage() > contentAddThreshold) { //If we're already past the add threshold when the frame is loaded, add content right away
		addContent();
	}
}

function recalculateThresholds() {
	contentLoadThreshold = 100 - 50/numPagesLoaded;
	contentAddThreshold = Math.min(98, 100 - 10/numPagesLoaded); //Don't let this value get higher than 98
	console.log("thresholds updated: load - " + contentLoadThreshold.toString() + "  add - " + contentAddThreshold.toString());
}

function getScrollPercentage() {
	var d = document.body;
	var percentage = Math.floor(d.scrollTop / (d.scrollHeight - d.clientHeight) * 100);
	return percentage; 
}

function RemoveEventHandlersAndExit() {
	frame.removeEventListener('load', frameLoaded, false);
	window.removeEventListener('scroll', respondToScroll, false);
	clearTimeout(loadTimeout);
}

function gup( name )
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


About

Yilmaz Kiymaz is a Technical Artist with a passion for cinematic and colorful experiences in games. He likes teaching, experimentation, and referring to himself in third-person.

Here’s how you contact me: Take my full name, Yilmaz Kiymaz, take out all vowels and whitespace, slap a gmail at the end of it and compose away.

Categories


%d bloggers like this: