There are 15 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name Rllmuk Topic Ignore List
// @namespace http://www.jonathanbuchanan.plus.com/repos/greasemonkey/
// @description Implements a topic ignore list, sending selected topics to an unobtrusive Ignored Topics section at the foot of topic listing pages.
// @include http://www.rllmukforum.com/*
// @include http://rllmukforum.com/*
// ==/UserScript==
/* Changelog
* ---------
* 2012-02-07 Updated for IPB 3.2.
* 2011-02-09 Updated Ignored Topics toggling, as Chrome can't make use of
* JavaScript in the content page.
* 2010-08-12 Added an id to the Ignored Topics wrapper for styling.
* 2010-08-06 Maintained topic striping when topics are moved around.
* 2010-08-05 Restyled to fit in with default forum theme.
* 2010-07-29 Sorted ignored foldernames; reduced whitespace around icons.
* 2010-07-28 Added "Collapse Ignored Topics section by default" setting
* 2010-07-27 Tweaks after testing on updated forum.
* 2010-07-07 Updated script for impending move to IPB3.
* 2010-06-17 Now usable as a library in a Chrome extension.
* 2010-06-16 Refactored into main logic, config and UI modules.
* 2009-07-14 Fixed error saving ignored folders in latest version.
* 2008-09-13 Fixed preferences display issue in Firefox 3.
* 2007-03-05 Forum software was updated, which broke the script.
* 2007-02-20 Minor style update to remove multiple scrollbars when the window
* is smaller than the preferences dialogue.
* 2007-02-19 No longer using User Script Commands menu - Script controls are
* now integrated into pages.
* 2007-01-25 Added extranoise.co.uk domain.
* 2006-11-02 Added GUI for configuration.
* Removed positionInArray function, now using JS 1.6's Array.indexOf
* 2006-04-07 Added ignoring of specified folders on search pages and a menu
* item to edit the ignored folder list.
* 2006-03-13 Removed setting which toggled usage of the Ignored Topics section
* and did a general code tidy.
* 2006-03-12 Changed method for getting topic's row to avoid dumping the entire
* post table in the Ignored Topics section.
* 2006-03-10 Updated to work with latest version of Greasemonkey and removed
* all use of cookies in favour of GM's own storage mechanism.
* 2005-06-12 Reduced MAX_COOKIE_SIZE to 4000, as setting cookies when close to
* the old 4096 limit seems to silently fail.
* 2005-05-26 Functionally complete version finished, tidied up and commented.
* -------------------------------------------------------------------------- */
var isGM = !(typeof GM_getValue == "undefined" || GM_getValue("a", "b") == undefined);
/**
* If we're running on a content page, this variable will point at an object
* containing settings retrieved from the extension's localStorage, otherwise
* we're running in the extension's context and want to access localStorage
* directly.
*
* This allows us to include this script for use as a library in extension
* contexts, such as in the preferences dialogue.
*/
var cachedSettings = null;
/**
* Cached folder names for use in the preferences dialogue. For the chrome
* extension, the initialising script will send these names to the background
* page, where the preferences dialogue can retrieve them to populate this
* variable.
*/
var folderNames = null;
if (!isGM)
{
GM_getValue = function(name, defaultValue)
{
var value = (cachedSettings == null ? localStorage.getItem(name) : cachedSettings[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_setValue = function(name, value)
{
value = (typeof value)[0] + value;
if (cachedSettings == null)
{
localStorage.setItem(name, value);
}
else
{
cachedSettings[name] = value;
chrome.extension.sendRequest({type: "setpref", name: name, value: value});
}
}
if (typeof(unsafeWindow) == "undefined")
{
unsafeWindow = window;
}
}
Function.prototype.bind = function(object)
{
var __method = this;
return function()
{
__method.apply(object, arguments);
}
};
/**
* Processing of the current page.
*/
var TIL =
{
pageType: null,
topicIdRegExp: /\/topic\/([0-9]+)-/,
trashIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAMAAABIK2QJAAAAAXNSR0IArs4c6QAAAIFQTFRFMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzVFqv0AAAAAF0Uk5TAEDm2GYAAAApSURBVAjXY2BgYGDS0tJiYoAALShm0EIABhSALKzFD4VUZDNBjWdiAACJ5QyrLpYjAwAAAABJRU5ErkJggg%3D%3D",
tickIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAALCAMAAACTbPdTAAAAAXNSR0IArs4c6QAAAIFQTFRFMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzVFqv0AAAAAF0Uk5TAEDm2GYAAAAySURBVAjXY2CAA2YGJMCoxYjM0dJiRuaA5TiROUxACiGjBQGMCB1aCPMYkTloNiG5AgB4FAUbj0thrwAAAABJRU5ErkJggg%3D%3D",
init: function()
{
this.pageType = this.determineCurrentPageType();
if (this.pageType != null)
{
this.processPage();
}
this.registerControls();
},
determineCurrentPageType: function()
{
var pageType = null;
if (window.location.href.indexOf("module=search") != -1)
{
pageType = "search";
}
else if (window.location.href.indexOf("index.php?/forum") != -1)
{
pageType = "topicListing";
}
return pageType;
},
getTopicLinkXPath: function()
{
return (this.pageType == "search"
? "//table[@id='forum_table']/tbody/tr/td[2]/h4/a[@title='View result']"
: "//table[@id='forum_table']/tbody/tr/td[2]/h4/a[@class='topic_title']");
},
processPage: function()
{
var removedTopics = [];
var ignoredTopicIds = TIL.Config.getIgnoredTopicIds();
var topicLinkNodes =
document.evaluate(
this.getTopicLinkXPath(),
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < topicLinkNodes.snapshotLength; i++)
{
var topicLinkNode = topicLinkNodes.snapshotItem(i);
var topicId = this.topicIdRegExp.exec(topicLinkNode.href)[1];
var ignoredTopicIndex = ignoredTopicIds.indexOf(topicId);
var beingIgnored = (ignoredTopicIndex > -1);
if (beingIgnored)
{
// Remove this topic's id from the current ignore list and place it
// at the front of the removed topics list.
removedTopics.splice(0, 0, ignoredTopicIds.splice(ignoredTopicIndex, 1));
}
// Create control cell for topic management
var control = document.createElement("td");
control.className = "col_f_preview";
var icon = document.createElement("img")
icon.src = beingIgnored ? this.tickIcon : this.trashIcon;
icon.className = "expander";
icon.style.verticalAlign = "text-top";
if (beingIgnored)
{
icon.alt = "Unignore";
icon.title = "Stop ignoring this topic";
}
else
{
icon.alt = "Ignore";
icon.title = "Ignore this topic";
}
control.appendChild(icon);
control.addEventListener("click", this.createIgnoreHandler(topicId), false);
// Find the table cell which will contain the ignore control
var cell = topicLinkNode.parentNode.parentNode.nextElementSibling.nextElementSibling
// Insert the control
cell.parentNode.insertBefore(control, cell);
// Insert the Ignored Topics section on the first loop iteration
if (i === 0)
{
this.insertIgnoredTopicsSection();
}
// If this topic is being ignored, move its row to the Ignored Topics
// section.
if (beingIgnored)
{
document.getElementById("TILInsertTarget").appendChild(cell.parentNode);
}
}
// Place any active ignored topics on the front of the ignored topic list
// and store it.
if (removedTopics.length > 0)
{
TIL.Config.setIgnoredTopicIds(
removedTopics.concat(ignoredTopicIds));
}
if (this.pageType == 'search')
{
this.fixTopicStriping();
}
},
/**
* Inserts an Ignored Topics section into the current page to store table
* rows which contain ignored topic details.
*
* @param postTable the DOM Node for the table which holds topic listings,
* to be used as a reference point for insertion of the new
* section.
*/
insertIgnoredTopicsSection: function(postTable)
{
var collapse = TIL.Config.getCollapseIgnoredTopics();
var toggleableSectionHTML =
'<div id="ignored_topics" class="category_block block_wrap' + (collapse ? ' collapsed' : '') + '">' +
'<h3 class="maintitle"><a id="toggle_ignored_topics" class="toggle right" title="Toggle this category" href="#">Toggle Ignored Topics</a>Ignored Topics</h3>' +
'<table id="ignored_topics_table" style="' + (collapse ? 'display: none' : '') + '" class="ipb_table topic_list hover_rows" summary="Ignored Topics">' +
'<tbody id="TILInsertTarget">' +
'</tbody>' +
'</table>' +
'</div>';
var ignoredSection = document.createElement("div");
ignoredSection.innerHTML = toggleableSectionHTML;
ignoredSection.style.marginTop = '10px';
if (this.pageType == 'search')
{
ignoredSection.style.clear = "both";
document.querySelector("div.ipsLayout_content").appendChild(ignoredSection);
}
else
{
var footer = document.getElementById("forum_footer")
footer.parentNode.insertBefore(ignoredSection, footer);
}
var control = document.getElementById("toggle_ignored_topics");
control.addEventListener("click", function(e)
{
var ignoredTopics = document.getElementById("ignored_topics_table");
ignoredTopics.style.display = (ignoredTopics.style.display == "none" ? "" : "none");
document.getElementById("ignored_topics").classList.toggle("collapsed")
e.preventDefault();
}, false);
},
// className functions
// Dean Edwards 2004.10.24
addClass: function(element, className)
{
if (element.className)
{
element.className += " " + className;
}
else
{
element.className = className;
}
},
removeClass: function(element, className)
{
var regexp = new RegExp("(^|\\s)" + className + "(\\s|$)");
element.className = element.className.replace(regexp, "$2");
},
hasClass: function(element, className)
{
var regexp = new RegExp("(^|\\s)" + className + "(\\s|$)");
return regexp.test(element.className);
},
fixRowStriping: function(xPath)
{
var rowNodes =
document.evaluate(
xPath,
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
for (var i = 0; i < rowNodes.snapshotLength; i++)
{
var rowNode = rowNodes.snapshotItem(i);
var expectedClass = (i % 2 == 0 ? "row1" : "row2");
var otherClass = (expectedClass == "row1" ? "row2" : "row1");
if (!this.hasClass(rowNode, expectedClass))
{
this.removeClass(rowNode, otherClass);
this.addClass(rowNode, expectedClass);
}
}
},
/**
* Ensures topics are alternately striped.
*/
fixTopicStriping: function()
{
// Topics
this.fixRowStriping("//table[@id='forum_table']/tbody/tr[@class!='header']");
// Ignored topics
this.fixRowStriping("//div[@id='ignored_topics']/table/tbody/tr[@class!='header']");
},
/**
* Creates an event handling Function for ignoring a topic.
*
* @param topicId the id of the topic to be ignored.
*
* @return a Function which, when executed, will toggle the ignored state of
* the topic with the given id.
*/
createIgnoreHandler: function(topicId)
{
return function(e)
{
// Toggle this topic out of the list if it's already there
var newlyIgnoredTopic = true;
var ignoredTopicIds = TIL.Config.getIgnoredTopicIds();
var topicIdIndex = ignoredTopicIds.indexOf(topicId);
if (topicIdIndex > -1)
{
ignoredTopicIds.splice(topicIdIndex, 1);
newlyIgnoredTopic = false;
}
var cell = e.target;
if (cell.nodeName.toLowerCase() == 'img')
{
cell = cell.parentNode
}
var icon = cell.firstElementChild;
if (newlyIgnoredTopic)
{
// Add this topic's id to the front of the ignore list
ignoredTopicIds.splice(0, 0, topicId);
// Move the table row Node which contains this topic's details
// to the Ignored Topics section.
document.getElementById("TILInsertTarget").appendChild(cell.parentNode);
// Update the topic ignoring icon appropriately
icon.src = TIL.tickIcon;
icon.title = "Stop ignoring this topic";
if (TIL.pageType == 'search')
{
TIL.fixTopicStriping();
}
}
else
{
// Show that this topic won't be ignored on next page load
icon.src = TIL.trashIcon;
icon.title = "Re-ignore this topic";
}
// Store the updated ignored topic list
TIL.Config.setIgnoredTopicIds(ignoredTopicIds);
};
},
registerControls: function()
{
var homeItem = document.getElementById("nav_home");
// Only insert this link for GM - Chrome will use a page action
if (isGM && homeItem)
{
homeItem.parentNode.insertBefore(this.createLinkControl("Topic Ignore List",
TIL.UI.show.bind(TIL.UI)),
homeItem);
}
},
createLinkControl: function(name, handler)
{
var li = document.createElement("li");
li.className = "right"
var a = document.createElement("a");
a.href = "#";
a.appendChild(document.createTextNode(name));
a.addEventListener("click", handler, false);
li.appendChild(a);
return li;
}
};
/**
* Configuration.
*/
TIL.Config =
{
getIgnoredTopicIds: function()
{
var topicIds = GM_getValue("ignoredTopics");
return (topicIds ? topicIds.split(",") : []);
},
setIgnoredTopicIds: function(ignoredTopics)
{
GM_setValue("ignoredTopics", ignoredTopics.join(","));
},
getCollapseIgnoredTopics: function()
{
return this._getBooleanConfig("collapseIgnoredTopics", true);
},
setCollapseIgnoredTopics: function(collapseIgnoredTopics)
{
GM_setValue("collapseIgnoredTopics", collapseIgnoredTopics);
},
_getBooleanConfig: function(configName, defaultValue)
{
var config = GM_getValue(configName);
if (config === undefined)
{
GM_setValue(configName, defaultValue);
config = defaultValue;
}
return config;
}
};
/**
* Preferences User Interface (UI).
*/
TIL.UI =
{
PREFS_HTML: "data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw%2BDQo8aHRtbCBsYW5nPSJlbiI%2BDQo8aGVhZD4NCiAgPHRpdGxlPlRvcGljIElnbm9yZSBMaXN0IFByZWZlcmVuY2VzPC90aXRsZT4NCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCiAgaHRtbCB7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50IH0NCiAgYm9keSB7IG1hcmdpbjowOyBwYWRkaW5nOjA7IGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OyBjb2xvcjogIzFDMjgzNzsgZm9udDogMTNweCBhcmlhbCx2ZXJkYW5hLHRhaG9tYSxzYW5zLXNlcmlmOyB3aWR0aDogNzIwcHg7IG1hcmdpbjogMCBhdXRvOyB9DQogIC5tb2R1bGUgeyBtYXJnaW4tYm90dG9tOiA1cHg7IH0NCiAgLm1vZHVsZSBoMiwgLm1vZHVsZSBjYXB0aW9uIHsNCiAgICAtbW96LWJvcmRlci1yYWRpdXM6NXB4IDVweCAwIDA7DQogICAgZm9udC1zaXplOjE0cHg7DQogICAgZm9udC13ZWlnaHQ6bm9ybWFsOw0KICAgIG1hcmdpbjowICFpbXBvcnRhbnQ7DQogICAgb3ZlcmZsb3c6aGlkZGVuOw0KICAgIHBhZGRpbmc6OHB4ICFpbXBvcnRhbnQ7DQogICAgYmFja2dyb3VuZDp1cmwoImh0dHA6Ly93d3cucmxsbXVrZm9ydW0uY29tL3B1YmxpYy9zdHlsZV9pbWFnZXMvbWFzdGVyL2dyYWRpZW50X2JnLnBuZyIpIHJlcGVhdC14IHNjcm9sbCBsZWZ0IDUwJSAjMUQzNjUyOw0KICAgIGNvbG9yOiNGRkZGRkY7DQogIH0NCg0KICAuZm9ybS1yb3cgeyBvdmVyZmxvdzogaGlkZGVuOyBwYWRkaW5nOiA4cHggMTJweDsgZm9udC1zaXplOiAxMXB4OyBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2VlZTsgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2VlZTsgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjt9DQogIC5mb3JtLXJvdyBpbWcsIC5mb3JtLXJvdyBpbnB1dCB7IHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7IH0NCiAgLmZvcm0tZmllbGQgeyBmbG9hdDogbGVmdDsgfQ0KICAuYWxpZ25lZCBsYWJlbCB7IHBhZGRpbmc6IDAgMWVtIDNweCAwOyBmbG9hdDogbGVmdDsgd2lkdGg6IDhlbTsgfQ0KICAuY2hlY2tib3gtcm93IGxhYmVsIHsgcGFkZGluZzogMDsgZmxvYXQ6IG5vbmU7IHdpZHRoOiBhdXRvOyBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmOyB9DQogIC5zdWJtaXQtcm93IHsgcGFkZGluZzogOHB4IDEycHg7IHRleHQtYWxpZ246IHJpZ2h0OyBib3JkZXItYm90dG9tOiAxcHggc29saWQgI2VlZTsgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2VlZTsgYmFja2dyb3VuZC1jb2xvcjogI2ZmZjsgfQ0KDQogIHVsIHsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwIDAgMCAxLjVlbTsgbGluZS1oZWlnaHQ6IDEuNWVtOyB9DQogIGxpIHsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyB9DQoNCiAgc3Bhbi5jb250cm9sIHsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7IGN1cnNvcjogcG9pbnRlcjsgY29sb3I6ICMwMGY7IH0NCg0KICA8L3N0eWxlPg0KPC9oZWFkPg0KPGJvZHk%2BDQoNCjxmb3JtIG5hbWU9InByZWZlcmVuY2VzIiBpZD0icHJlZmVyZW5jZXMiIGNsYXNzPSJhbGlnbmVkIj4NCiAgPGRpdiBjbGFzcz0ibW9kdWxlIj4NCiAgICA8aDI%2BVG9waWMgSWdub3JlIExpc3QgUHJlZmVyZW5jZXM8L2gyPg0KICAgIDxkaXYgY2xhc3M9ImZvcm0tcm93IGNoZWNrYm94LXJvdyI%2BDQogICAgICA8bGFiZWwgZm9yPSJjb2xsYXBzZV9pZ25vcmVkX3RvcGljcyI%2BPGlucHV0IHR5cGU9ImNoZWNrYm94IiBuYW1lPSJjb2xsYXBzZV9pZ25vcmVkX3RvcGljcyIgaWQ9ImNvbGxhcHNlX2lnbm9yZWRfdG9waWNzIj4gQ29sbGFwc2UgSWdub3JlZCBUb3BpY3Mgc2VjdGlvbiBieSBkZWZhdWx0PC9sYWJlbD4NCiAgICA8L2Rpdj4NCiAgPC9kaXY%2BDQoNCiAgPGRpdiBjbGFzcz0ibW9kdWxlIj4NCiAgICA8ZGl2IGNsYXNzPSJzdWJtaXQtcm93Ij4NCiAgICAgIDxpbnB1dCB0eXBlPSJidXR0b24iIHZhbHVlPSJDbG9zZSIgbmFtZT0iY2xvc2VfYnV0dG9uIiBpZD0iY2xvc2VfYnV0dG9uIj4NCiAgICAgIDxpbnB1dCB0eXBlPSJidXR0b24iIHZhbHVlPSJTYXZlIFByZWZlcmVuY2VzIiBuYW1lPSJzYXZlX2J1dHRvbiIgaWQ9InNhdmVfYnV0dG9uIj4NCiAgICA8L2Rpdj4NCiAgPC9kaXY%2BDQo8L2Zvcm0%2BDQoNCjwvYm9keT4NCjwvaHRtbD4%3D",
// This will only be called when running on GreaseMonkey, as the Chrome
// extension will use a page action to display the preferences dialogue.
show: function(e)
{
if (e)
{
e.preventDefault();
e.stopPropagation();
}
var blocker = document.createElement("div");
this.blocker = blocker;
blocker.id = "til_blocker";
blocker.style.position = "fixed";
blocker.style.top = "0px";
blocker.style.right = "0px";
blocker.style.bottom = "0px";
blocker.style.left = "0px";
blocker.style.backgroundColor = "#000";
blocker.style.opacity = "0.5";
blocker.style.zIndex = "10000";
document.body.appendChild(blocker);
var prefs = document.createElement("iframe");
prefs.addEventListener("load", this.preferenceDocumentLoadHandler.bind(this), false);
this.prefs = prefs;
document.body.appendChild(prefs);
prefs.id = "til_preferences";
prefs.name = "til_preferences";
prefs.style.position = "fixed";
prefs.style.top = "1em";
prefs.style.left = "0px";
prefs.style.right = "0px";
prefs.style.border = "none";
prefs.style.height = "100%";
prefs.style.width = "100%";
prefs.style.overflow = "hidden";
prefs.style.zIndex = "10001";
prefs.src = this.PREFS_HTML;
},
hide: function(e)
{
if (isGM)
{
document.body.removeChild(this.prefs);
document.body.removeChild(this.blocker);
this.prefs = null;
this.blocker = null;
}
else
{
window.close();
}
},
getDocument: function()
{
return (isGM ? this.prefs.contentDocument : document);
},
preferenceDocumentLoadHandler: function()
{
var form = this.getDocument().forms.namedItem("preferences");
// Set up form state
form.elements.namedItem("collapse_ignored_topics").checked = TIL.Config.getCollapseIgnoredTopics();
// Set up event handlers
form.elements.namedItem("close_button").addEventListener("click", this.hide.bind(this), false);
form.elements.namedItem("save_button").addEventListener("click", this.saveConfigurationHandler.bind(this), false);
},
saveConfigurationHandler: function()
{
var form = this.getDocument().forms.namedItem("preferences");
TIL.Config.setCollapseIgnoredTopics(form.elements.namedItem("collapse_ignored_topics").checked);
this.hide();
}
};
// Chrome will use another content script to initialise the script.
if (isGM)
{
TIL.init();
}