// ==UserScript==
// @name           PHPBB3 Forum Compactor
// @namespace      http://www.pretentiousname.com/
// @description    Makes the default PHPBB3 theme (prosilver) more readable and compact.
// @include        http://resource.dopus.com/*
// ==/UserScript==

//////////////////////////////////////////////////////
// by Leo Davidson (http://www.pretentiousname.com) //
//////////////////////////////////////////////////////
// History:
// 1.6  (22/Nov/2010): Fixed changing unread post titles red after PHPBB 3.0.8 update, due to the titles changing from "New posts" to "Unread posts".
// 1.5  (11/Mar/2010): Fixed issue with search results pages.
//                     Now also changes view-topic pages, making the author of each post more visible and adding separators between each post.
// 1.4  (10/Mar/2010): Fixed issue missing/deleted usernames which don't create links.
// 1.3  (10/Mar/2010): Fixed issue with " - by " appearing in forum list Last Post columns.
// 1.2  (10/Mar/2010): Fixed issue with different date/time formats.
// 1.1  (10/Mar/2010): First release

// This handles forum lists and topic lists separately.
// Note that there are pages which display both types of list at once.


var allRows, thisRow, allElements, thisElement, prevElement, parElement, newElement, newTag, newTag2, remStart, remStart2, strTemp, strClass;

///////////////
// Functions //
///////////////

var whitespace = " \t\n\r";

function isEmpty(s)
{
    return ((s == null) || (s.length == 0));
}

function isWhitespace(s)
{
    var i;
    if (isEmpty(s)) return true;
    for (i = 0; i < s.length; i++)
    {
        var c = s.charAt(i);
        if (whitespace.indexOf(c) == -1) return false;
    }
    return true;
}


/////////////////
// MISC. STUFF //
/////////////////

// When reading threads, move the author line up into the subject line (of each reply)
// This makes the author name larger and the first thing you see for each reply, since it's the most important
// part for following a thread.

allElements = document.evaluate('//body[@class="section-viewtopic ltr"]//p[@class="author"]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; i < allElements.snapshotLength; i++)
{
    thisElement = allElements.snapshotItem(i);

    prevElement = thisElement.previousElementSibling;

    if (prevElement.nodeName == "H3")
    {
        if (!isWhitespace(prevElement.textContent))
        {
            prevElement.insertBefore(document.createTextNode("\u00bb "), prevElement.firstChild);
        }

        while(thisElement.childNodes.length > 0)
        {
            newElement = thisElement.lastChild;
            thisElement.removeChild(thisElement.lastChild);

            if (newElement.textContent != "by ")
            {
                if (newElement.firstChild && newElement.firstChild.nodeName == "IMG")
                {
                    newElement.firstChild.style.verticalAlign = "middle";
                    prevElement.insertBefore(document.createTextNode(" "), prevElement.firstChild);
                }

                prevElement.insertBefore(newElement, prevElement.firstChild);
            }
        }
    }

    // thisElement.parentNode.removeChild(thisElement);
}

// Make the dividers between posts visible so the pale blue post boxes don't all blend together on the white background.

allElements = document.evaluate('//body[@class="section-viewtopic ltr"]//div[contains(concat(" ", normalize-space(@class), " "), " post ")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; i < allElements.snapshotLength; i++)
{
    prevElement = allElements.snapshotItem(i).previousElementSibling;

    if (prevElement.nodeName == "A" && prevElement.getAttribute("id") == "unread")
    {
        prevElement = prevElement.previousElementSibling;
    }

    if (prevElement.nodeName == "HR" && prevElement.getAttribute("class") == "divider")
    {
        prevElement.removeAttribute("class");
    }
}

/////////////////
// FORUM LISTS //
/////////////////

allRows = document.evaluate('//div[contains(concat(" ", normalize-space(@class), " "), " forabg ")]//li[contains(concat(" ", normalize-space(@class), " "), " row ")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var r = 0; r < allRows.snapshotLength; r++)
{
    thisRow = allRows.snapshotItem(r);

    // Remove line-breaks so the columns are actually columns, not two columns interleaved which are difficult to read.

    allElements = document.evaluate('.//br', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.parentNode.removeChild(thisElement);
    }

    // Reduce minimum row height.

    allElements = document.evaluate('.//dl[@class="icon"]', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.minHeight = '24px';
    }

    // Prevent cells from wrapping or overflowing. I'd rather have them clipped as the excess is rarely important.

    allElements = document.evaluate('.//dt|.//dd', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.lineHeight = '24px';
        thisElement.style.whiteSpace = 'nowrap';
        thisElement.style.paddingTop = "0px";
        thisElement.style.paddingBottom = "0px";
        thisElement.style.verticalAlign = "center";

        strClass = thisElement.getAttribute("class");

        // I can't work out why by the last column doesn't actually fill the pseudo-table so we actually want it to overflow.

        if (strClass == "lastpost")
        {
            thisElement.style.overflow = 'visible';
        }
        else
        {
            thisElement.style.overflow = 'hidden';
        }
    }

    // Rearrange the Last Post column so the date is before the name. Makes things line up better and much easier to read.

    allElements = document.evaluate('.//dd[@class="lastpost"]/span', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        parElement = allElements.snapshotItem(i);

        if (parElement.childNodes.length == 8) // Sanity check. Deals with the problem of missing/deleted users whose names don't create links.
        {
            thisElement = parElement.childNodes.item(parElement.childNodes.length - 1); // date string
            newElement = thisElement.cloneNode(true); // Clone the date string
            newElement.data += " - "; // add " - " as nicer separator than " by "
            parElement.removeChild(thisElement); // remove the date string
            parElement.removeChild(parElement.childNodes.item(2)); // remove the "by "
            parElement.insertBefore(newElement,parElement.childNodes.item(2)); // insert the date string at the start
        }
    }

    // Move the forum descriptions into their own column.

    allElements = document.evaluate('.//dt', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        parElement = allElements.snapshotItem(i);
        thisElement = parElement.childNodes.item(parElement.childNodes.length - 1); // description string
        newElement = thisElement.cloneNode(true); // clone the description string
        parElement.removeChild(thisElement); // remove description from its original place.

        parElement.style.width = "20%"; // resize forum name column from 50% to 20%

        // Create and insert the new description column.

        newTag = document.createElement("dd");
        newTag.style.lineHeight = '24px';
        newTag.style.whiteSpace = 'nowrap';
        newTag.style.overflow = 'hidden';
        newTag.style.paddingTop = "0px";
        newTag.style.paddingBottom = "0px";
        newTag.style.verticalAlign = "center";
        newTag.style.width = "30%"; // description column width uses the remaining 30%
        newTag.style.border = 0;

        newTag.appendChild(newElement);

        parElement.parentNode.insertBefore(newTag,parElement.nextElementSibling);
    }

    // For any forums with unread topics, make their anchors red.

    // "New posts" became "Unread posts" in phpbb 3.0.8
    allElements = document.evaluate('.//dt[@title="New posts"]/a|.//dt[@title="Unread posts"]/a', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.color = '#c03c5c';
    }
}

/////////////////
// TOPIC LISTS //
/////////////////

// In the header, insert the First Post heading and adjust the widths of other headings.
// Don't do this for search results pages since we don't create the column in their lists.

allElements = document.evaluate('//body[@class!="section-search ltr"]//div[contains(concat(" ", normalize-space(@class), " "), " forumbg ")]//li[@class="header"]//dd', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; i < allElements.snapshotLength; i++)
{
    thisElement = allElements.snapshotItem(i);

    strClass = thisElement.getAttribute("class");

    // Reduce the width of the Replies and Views columns from 8% to 5%

    if (strClass == "posts" || strClass == "views")
    {
        thisElement.style.width = "5%";
    }

    // Insert the First Post column before the Replies column.

    if (strClass == "posts")
    {
        parElement = thisElement.parentNode;
        newElement = thisElement.cloneNode(true);

        thisElement.previousElementSibling.style.width = "36%";
        newElement.setAttribute("class", "lastpost");
        newElement.style.width = "20%"
        newElement.style.textAlign = "left";
        newElement.innerHTML = "First post";

        parElement.insertBefore(newElement,thisElement);
    }
}

// On search results pages, just adjust the header column widths.

allElements = document.evaluate('//body[@class="section-search ltr"]//div[contains(concat(" ", normalize-space(@class), " "), " forumbg ")]//li[@class="header"]//dd', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; i < allElements.snapshotLength; i++)
{
    thisElement = allElements.snapshotItem(i);

    strClass = thisElement.getAttribute("class");

    // Reduce the width of the Replies and Views columns from 8% to 5%

    if (strClass == "posts" || strClass == "views")
    {
        thisElement.style.width = "5%";
    }

    // Adjust the width of the Topic column.

    if (strClass == "posts")
    {
        thisElement.previousElementSibling.style.width = "56%";
    }
}

// Get the list of rows we wish to modify. Doing this once must be faster than including it in every XPath query.

allRows = document.evaluate('//div[contains(concat(" ", normalize-space(@class), " "), " forumbg ")]//li[contains(concat(" ", normalize-space(@class), " "), " row ")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var r = 0; r < allRows.snapshotLength; r++)
{

    thisRow = allRows.snapshotItem(r);

    // Remove line-breaks so the columns are actually columns, not two columns interleaved which are difficult to read.

    allElements = document.evaluate('.//br', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.parentNode.removeChild(thisElement);
    }

    // Reduce minimum row height.

    allElements = document.evaluate('.//dl[@class="icon"]', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.minHeight = '25px';
    }

    // Prevent cells from wrapping or overflowing. I'd rather have them clipped as the excess is rarely important.

    allElements = document.evaluate('.//dt|.//dd', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.lineHeight = '25px';
        thisElement.style.whiteSpace = 'nowrap';
        thisElement.style.paddingTop = "0px";
        thisElement.style.paddingBottom = "0px";
        thisElement.style.verticalAlign = "center";

        strClass = thisElement.getAttribute("class");

        // I can't work out why by the last column doesn't actually fill the pseudo-table so we actually want it to overflow.

        if (strClass == "lastpost")
        {
            thisElement.style.overflow = 'visible';
        }
        else
        {
            thisElement.style.overflow = 'hidden';
        }

        // Reduce the width of the Replies and Views columns from 8% to 5%

        if (strClass == "posts" || strClass == "views")
        {
            thisElement.style.width = "5%";
        }
    }

    // Rearrange the Last Post column so the date is before the name. Makes things line up better and much easier to read.

    allElements = document.evaluate('.//dd[@class="lastpost"]/span', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        parElement = allElements.snapshotItem(i);

        // Sanity check. Deals with the problem of missing/deleted users whose names don't create links.
        if (parElement.childNodes.length == 7 && parElement.childNodes.item(1).nodeType == 3 && parElement.childNodes.item(1).data.match("by "))
        {
            thisElement = parElement.childNodes.item(parElement.childNodes.length - 1); // date string
            newElement = thisElement.cloneNode(true); // Clone the date string
            newElement.data += " - "; // add " - " as nicer separator than " by "
            parElement.removeChild(thisElement); // remove the date string
            parElement.removeChild(parElement.childNodes.item(1)); // remove the "by "
            parElement.insertBefore(newElement,parElement.childNodes.item(1)); // insert the date string at the start
        }
    }

    // Move the First Post information out of the topic-title column and into a column of its own.

    allElements = document.evaluate('.//dt', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        parElement = allElements.snapshotItem(i);

        // Make the page boxes appear on the same line as the topic title, and before the attachment indicator (if any).

        allElements = document.evaluate('.//strong[@class="pagination"]', parElement, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

        for (var i = 0; i < allElements.snapshotLength; i++)
        {
            thisElement = allElements.snapshotItem(i);
            parElement = thisElement.parentNode;
            newTag = thisElement.cloneNode(true);
            thisElement.parentNode.removeChild(thisElement);

            newTag.style.display = "inline";
            newTag.style.cssFloat = "none";

            remStart2 = parElement.childNodes.length - 4;

            parElement.insertBefore(document.createTextNode(" "), parElement.childNodes.item(remStart2));
            parElement.insertBefore(newTag, parElement.childNodes.item(remStart2));
            parElement.insertBefore(document.createTextNode(" "), parElement.childNodes.item(remStart2));
        }

        remStart = parElement.childNodes.length - 3;

        // Sanity check. Deals with the problem of missing/deleted users whose names don't create links.
        if (!(parElement.childNodes.length > 3 && parElement.childNodes.item(remStart).nodeType == 3 && parElement.childNodes.item(remStart).data.match("by ")))
        {
            parElement.style.width = "56%"; // Don't know how to convert this line so leave it as-is, except for adjusting the width to fit the others.
        }
        else
        {
            // New column

            parElement.style.width = "36%";

            newTag = document.createElement("dd");
            newTag.setAttribute("class", "lastpost");
            newTag.style.lineHeight = '24px';
            newTag.style.whiteSpace = 'nowrap';
            newTag.style.overflow = 'hidden';
            newTag.style.paddingTop = "0px";
            newTag.style.paddingBottom = "0px";
            newTag.style.verticalAlign = "center";
            newTag.style.width = "20%";

            newTag2 = document.createElement("span");
            newTag.appendChild(newTag2);

            // Move "by "
            newElement = parElement.childNodes.item(remStart).cloneNode(true);
            parElement.removeChild(parElement.childNodes.item(remStart));
            newElement.data = " - ";
            newTag2.appendChild(newElement);

            // Move starter's name
            newElement = parElement.childNodes.item(remStart).cloneNode(true);
            parElement.removeChild(parElement.childNodes.item(remStart));
            newTag2.appendChild(newElement);

            // Move start date, stripping the "> " at the start and the " HH:MM" at the end.
            newElement = parElement.childNodes.item(remStart).cloneNode(true);
            parElement.removeChild(parElement.childNodes.item(remStart));

            strTemp = newElement.data;

            strTemp = strTemp.replace(/^\s+|\s+$/g,""); // strip spaces from start/end
            strTemp = strTemp.replace(/\s*am$|\s*pm$/i,""); // strip am/pm from end of 12-hour times.
            strTemp = strTemp.replace(/\s*\d?\d:\d\d$/,""); // strip time from end.
            strTemp = strTemp.replace(/,\s*$/,""); // strip comma, if any, from end.
            strTemp = strTemp.replace(/^.\s+/,""); // strip chevron from start, if any. (Any char followed by at least one space.)

            newElement.data = strTemp;

            newTag2.insertBefore(newElement, newTag2.firstChild);

            // Add new column
            parElement.parentNode.insertBefore(newTag,parElement.nextElementSibling);
        }
    }

    // For any unread topics, make their title red.

    // "New posts" became "Unread posts" in phpbb 3.0.8
    allElements = document.evaluate('.//dt[@title="New posts"]/a|.//dt[@title="Unread posts"]/a', thisRow, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    for (var i = 0; i < allElements.snapshotLength; i++)
    {
        thisElement = allElements.snapshotItem(i);
        thisElement.style.color = "#c03c5c";
    }
}

