LJ Thread Unfolder

By Timothy Babych Last update Mar 3, 2008 — Installed 11,336 times. Daily Installs: 7, 1, 6, 0, 4, 3, 4, 9, 0, 6, 3, 1, 5, 3, 4, 3, 0, 2, 2, 7, 3, 7, 2, 3, 1, 2, 2, 0, 2, 3, 3, 4
// LJ Thread Unfolder
// version 0.5
// 2008-03-03
// Copyright (c) 2005-2008, Tim Babych
// Homepage: http://clear.com.ua/projects/firefox/unfolder
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "LJ Thread Unfolder", and click Uninstall.
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @name          	LJ Thread Unfolder
// @description	Expands nested discussion threads in place
// @include      	http://*.livejournal.com/*
// ==/UserScript==

//================================
//	INIT
//================================

GM_compat()
add_unfolders(document)

GM_addStyle(
"a.unfold_thread {color: black; text-decoration:none} "+
"a.unfold_thread:hover {color: black; text-decoration:none} "+
"a.unfold_thread img { border:none; position: relative; top: 3px;}"
)


//================================
//	WORKHORSES
//================================

function add_unfolders(elem) {
	get_itemid_regexp = /thread=(\d+)/
	var allReplies
	allReplies = document.evaluate(
	    "//a[contains(@href, '?thread')]",
	    elem,
	    null,
	    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
	    null);
		
	for (var i = 0; i < allReplies.snapshotLength; i++) {
		thisOne = allReplies.snapshotItem(i);
		
		if (thisOne.innerHTML == 'Parent')
			continue
		
		if (thisOne.nextSibling && thisOne.nextSibling.nextSibling && thisOne.nextSibling.nextSibling.innerHTML == 'Unfold')
			continue
		
		// the first child in td
		if (thisOne.parentNode.firstChild == thisOne || // just Unfold
			( thisOne.previousSibling && thisOne.previousSibling.previousSibling 
			&& thisOne.previousSibling.previousSibling.href ) ) { // Unfold all
						
			t = document.createTextNode(' - ')
			thisOne.parentNode.insertBefore(t, thisOne.nextSibling);

			params =  get_itemid_regexp.exec(thisOne.href)

			a = document.createElement('a')
			linktxt = document.createTextNode('Unfold')
			a.href = 'javascript:void(0)'
			if (thisOne.previousSibling) // 'All' mode
				a.setAttribute('all', true)
			a.setAttribute('thread_id', params[1])
			a.setAttribute('thread_href', thisOne.href)
			a.addEventListener("click", unfold_thread, false)
			a.appendChild(linktxt)
			t.parentNode.insertBefore(a, t.nextSibling)
		}
	}
}	

function unfold_thread() {
	this.className = 'unfold_thread'
	this.style.cursor = 'default'
	this.innerHTML = loader
	this.blur()
	elem = this

	GM_xmlhttpRequest({
	    method: 'GET',
	    url: this.getAttribute('thread_href'),
	    onload: function(responseDetails) { parse_result(responseDetails.responseText) }
	    })
	    
	function parse_result(result){
		var dummy = document.createElement('DIV')
		dummy.style.display = 'none'
		document.body.appendChild(dummy)
		// the same one lives in new doc
		document.getElementById('ljcmt'+elem.getAttribute('thread_id')).id += '_old'
		dummy.innerHTML = result
		reply = document.getElementById('ljcmt'+elem.getAttribute('thread_id')) // ajaxed content including spans

/* 
//      expected DOM tree:

span
    table
        tbody
            tr (header) - optional
            tr
                td
                    img
                td
                    content
a
span
...
*/
		add_unfolders(dummy)

		// get cell
		td = elem

		while (td.nodeName != 'TD')
			td = td.parentNode
		tbody = td.parentNode.parentNode
		span = tbody.parentNode.parentNode

		my_padding = tbody.firstChild.firstChild.firstChild.width
		
		// add comments there
		while(reply && reply.nodeName != 'HR'){
			next_one = reply.nextSibling
			span.parentNode.insertBefore(reply, span)
			if (reply.nodeName == 'SPAN')
				reply.firstChild.firstChild.firstChild.firstChild.firstChild.width += my_padding
			reply = next_one
		}

		// remove nesting comments			
		next_comment = span.nextSibling.nextSibling.nextSibling
		while(next_comment && 
				next_comment.firstChild &&
				next_comment.firstChild.firstChild &&
				next_comment.firstChild.firstChild.firstChild &&
				next_comment.firstChild.firstChild.firstChild.firstChild &&
				next_comment.firstChild.firstChild.firstChild.firstChild.firstChild &&				
               (next_comment.firstChild.firstChild.firstChild.firstChild.firstChild.width > my_padding)) {
			to_del = next_comment
			next_comment = next_comment.nextSibling.nextSibling.nextSibling
			to_del.parentNode.removeChild(to_del)
		}
		document.body.removeChild(dummy)
		span.parentNode.removeChild(span)
	}	
	
}


//========================================
//	ROUTINES
//========================================
function GM_compat() {
	if ( typeof GM_xmlhttpRequest == 'undefined' ) 
		GM_xmlhttpRequest = function(params) {
			var req = new XMLHttpRequest()
			req.onreadystatechange =  function() {
					if (req.readyState != 4) return // while not Complete
					params.onload(req)
				}
			req.open(params.method, params.url, true)
			req.send("")
		}
		
	if ( typeof GM_addStyle == 'undefined' ) 
		GM_addStyle = function(css) {
			style = document.createElement('STYLE');
			style.type = 'text/css';
			style.innerHTML = css;
			document.body.appendChild(style);	
		}
}

loader = '<img src="data:image/gif;base64,R0lGODlhEAAQAMQAAP///+7u7t3d3bu7u6qqqpmZmYi'+
'IiHd3d2ZmZlVVVURERDMzMyIiIhEREQAR'+
'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'+
'fySDhGYQdDWGQyUhADs="> Loading&hellip;'