Thumb

deviantTIDY

By BoffinbraiN Last update Oct 21, 2009 — Installed 4,222 times. Daily Installs: 6, 9, 6, 4, 9, 5, 3, 6, 10, 0, 6, 10, 12, 8, 3, 9, 1, 1, 13, 6, 8, 6, 8, 5, 4, 5, 4, 2, 9, 4, 3, 2

There are 8 previous versions of this script.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name			deviantTIDY
// @namespace		http://boffinbrain.deviantart.com
// @description		Performs a variety of functions on deviantART pages to improve its look and usability.  deviantTIDY also consists of a stylesheet.  For full details, see http://www.deviantart.com/deviation/45622809/
// @version			4.0.3
// @include			http*.deviantart.com/*
// ==/UserScript==


// deviantART body element
var dA = $('deviantART-v6-1');


// Get a DOM element by its ID
function $(id) {return document.getElementById(id);}

// Create a DOM text node
function $S(str) {return document.createTextNode(str);}

// Remove a DOM element
function $R(e) {if(e && e.parentNode) e.parentNode.removeChild(e);}

// Create a DOM element (tag, [properties,] children)
function $E()
{
	if(arguments.length==0) return;

	function applyObj(to, obj)
	{
		for(prop in obj) if(obj.hasOwnProperty(prop))
		{
			if(typeof(obj[prop]) == 'object')
			{
				applyObj(to[prop], obj[prop]);
			}
			else
			{
				to[prop] = obj[prop];
			}
		}
	}

	var elm = document.createElement(arguments[0]);

	[arguments[1], arguments[2]].forEach(function(arg, idx, ary)
	{
		if(typeof(arg)=='object')
		{
			if(arg instanceof Array)
			{
				// Child elements
				arg.forEach(function(append, idx, ary)
				{
					elm.appendChild((typeof(append)=='string') ? $S(append) : append);
				});
			}
			else
			{
				// Properties of element
				for(prop in arg) if(arg.hasOwnProperty(prop))
				{
					if(prop=='events')
					{
						// Add event listeners
						var events = arg[prop];
						for(evt in events) if(events.hasOwnProperty(evt))
						{
							elm.addEventListener(evt.replace(/^on/,''), events[evt], false);
						}
					}
					else
					{
						// Add normal properties
						if(typeof(arg[prop])=='object')
						{
							applyObj(elm[prop], arg[prop]);
						}
						else
						{
							elm[prop]=arg[prop];
						}
					}
				}
			}
		}
	});
	return elm;
}

// Standard XPATH shorthand function
function $X(query, node)
{
	try
	{
		return document.evaluate(query, node?node:document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	}
	catch(e)
	{
		try
		{
			return content.document.evaluate(query, node?node:content.document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		}
		catch(e)
		{
			GM_log("Error in XPATH query");
		}
	}
}


// The deviantTIDY-specific modal interface
var deviantdialog =
{
	identifier: 'devianttidy-dialog',

	open: function(header, body)
	{
		// If dialog is open, close it and start a new one
		if($(this.identifier)) this.close();

		if(typeof header != 'string') return;
		if(typeof body == 'string') body = [$E('div', {className:'ppp c'}, [body])];

		var popup = $E('div', {id:this.identifier, style:{display:'none'}}, [
			$E('div', [
				$E('div', {className:'gr-box gr-genericbox'}, [
					$E('i', {className:'gr1'}),
					$E('i', {className:'gr2'}),
					$E('i', {className:'gr3'}),
					$E('div', {className:'gr-top'}, [
						$E('i', {className:'tri'}),
						$E('div', {className:'gr'}, [
							$E('h2', [$E('a', {href:devianttidy.url, title:'deviantTIDY Homepage'}, [
								$E('img', {className:'dialog-icon', src:devianttidy.icons.dt})]), 
								header
							]),
							$E('img', {className:'dialog-close', src:devianttidy.icons.close, events:{'click':function(){deviantdialog.close();}}})
						])
					]),
					$E('div', {className:'gr-body'}, [
						$E('div', {className:'ppp'}, body)
					]),
					$E('i', {className:'gr3 gb'}),
					$E('i', {className:'gr2 gb'}),
					$E('i', {className:'gr1 gb gb1'})
				])
			])
		]);

		dA.appendChild(popup);
		var gr = popup.childNodes[0];
		gr.style.marginTop = (gr.offsetHeight ? -gr.offsetHeight/2 : -250)+"px";

		// Display warning about lack of CSS. The warning is hidden by the CSS when loaded.
		dA.appendChild($E('div', {
			id:'devianttidy-no-css',
			style:{
				position:'fixed', top:'50%', left:'20%', right:'20%', zIndex:300,
				background:'#fed', textAlign:'center', padding:'1em'
			},
			events:{'click':function(){$R($('devianttidy-no-css'));}}
		}, [
			'deviantTIDY CSS not loaded!', $E('br'), 'To get the CSS, install ',
			$E('a', {href:"https://addons.mozilla.org/en-US/firefox/addon/2108"}, ['Stylish']),
			' and go to the ', $E('a', {href:devianttidy.url}, ['deviantTIDY Homepage']),
			' to grab the stylesheet.'
		]
		));
	},

	close: function()
	{
		$R($(this.identifier));
	}
};


// The deviantTIDY application
var devianttidy =
{
	version:	'4.0.3',
	debug:		false,
	extension:	false,
	url:		'http://www.deviantart.com/deviation/45622809/',
	dialog:		deviantdialog,

	icons:
	{
		dt:		'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAACB0RVh0U29mdHdhcmUATWFjcm9tZWRpYSBGaXJld29ya3MgTVi7kSokAAAAFnRFWHRDcmVhdGlvbiBUaW1lADA1LzI1LzA5eCfTMQAAAPJJREFUeJzllD2KQkEQhL8SE4UNvIEnMBNTYSM9x7KHMDbyAkYaGnkHAxEWI28gwmK2ZsZtoGj7GN4vA8oWdDDTPVXM1HTLzIiJWlT2/yMgaSppI2lfVEB5TJZ0LzIzFRGI/kT10KakHjAAusCwkoKZ3QPoAzPAbvEHLNzabnVbYAl8+/Oh8OQjTwRMXC4p8LTOFAC+EuTjxM3KCwBt4OQO/QaKSgvUgTnQcrY0JK14/LB2moeSTsAHcAyk1wLOQDONxMPM5PsiA4casMtLXgI/uTq5CoKNloUio+M1pmkVlHoioAN8ch0tqYhu8vt7EF3gAq5S40Q1cBgMAAAAAElFTkSuQmCC',
		close:	'data:image/gif;base64,R0lGODlhDwAPAJEAAP///9vg2kdSS////yH5BAUUAAMALAAAAAAPAA8AAAIrnC2Zx6O/GJxnWpRAUAEox2lCt1mjJpoJqa5oabHsp6TnB7ZC1TZqw8MdCgA7'
	},

	log: function(message)
	{
		if(this.debug) GM_log(message);
	},

	start: function()
	{
		if(!dA)
		{
			GM_log("deviantTIDY only works on v6.1 pages.");
			return;
		}
		
		if(unsafeWindow.devianttidy)
		{
			alert("Another instance of deviantTIDY has already loaded!  Please check your installed scripts and extensions.");
			return;
		}

		this.manifest();
		this.dispatch();
	},

	manifest: function()
	{
		// Fresh update?
		if(GM_getValue('version') != this.version)
		{
			GM_setValue('version', this.version);
			devianttidy.dialog.open('deviantTIDY '+this.version+' Installed', [
				$E('div', {className:'pp'}, ["Congratulations; deviantTIDY is installed and running!"]),
				$E('div', {className:'pp'}, [
					"You can view all available options by clicking 'deviantTIDY' on the footer of any deviantART page, or ",
					$E('a', {href:"javascript:;", className:'a', events:{click:devianttidy.preferences}}, ["set your preferences right away"]),
					"."
				]),
				$E('div', {className:'pp'}, ["You will not see this message again.  Close this panel to continue browsing."]),
			]);
		}

		// Silently look for updates every 2 days - alert user if new version is available
		var now = new Date();
		var last = GM_getValue('last_updated');
		if(!last || Date.parse(last).valueOf() < now - 48*3600*1000)
		{
			this.update(true);
			GM_setValue('last_updated', now.toString());
		}

		// Add Greasemonkey menu item
		GM_registerMenuCommand("deviantTIDY Options", this.preferences);

		// Add the Options link to the page footer
		var depths = $X("//div[@id='depths']//div[@class='footer_tx_links']").snapshotItem(0);
		if(depths)
		{
			depths.insertBefore($S(' | '), depths.childNodes[0]);
			depths.insertBefore($E('a', {
				id:'devianttidy-options-link',
				href:'javascript:;',
				events:{click: this.preferences}
			}, ['deviantTIDY']), depths.childNodes[0]);
		}

		// Allow this application and its dialog manager to be accessed by other scripts through unsafeWindow
		unsafeWindow.devianttidy = this;
	},

	preferences: function()
	{
		var controls = [];

		// Generate options controls
		for(var o in devianttidy.options)
		{
			// Options without descriptions are hidden functions, but their preferences can be set manually
			option = devianttidy.options[o];
			if(option.description)
			{
				var control;
				var control_id = 'devianttidy-control-'+o;
				var description = [option.description, $E('b', [(option.custom ? ' (add-on)' : '')])];
				if(option.choices)
				{
					// If a list of choices is provided, the options are radio buttons
					control = [$E('div', description)];
					for(var c=0; c<option.choices.length; c++)
					{
						control.push($E('div', [
							$E('input', {type:'radio', id:control_id+c, name:o, value:c, checked:(option.pref==c), events: {
								change: function(){devianttidy.options[this.name].pref = this.value;}
							}}),
							$E('label', {htmlFor:control_id+c}, [option.choices[c]])
						]));
					}
				}
				else
				{
					// Otherwise, use a checkbox
					control = [
						$E('input', {type:'checkbox', id:control_id, name:o, checked:option.pref, events: {
							change: function(){devianttidy.options[this.name].pref = this.checked ? 1 : 0;}
						}}),
						$E('label', {htmlFor:control_id}, description),
					];
				}
				if(option.hint)
				{
					control.push($E('div', {className:'hint'}, [option.hint]));
				}
				controls.push($E('div', {className:'pp dialog-control'}, control));
			}
		}

		devianttidy.dialog.open('Options',[
			$E('div', {className:'p r'}, [
				$E('a', {href:devianttidy.url}, ["deviantTIDY"]),
				' version '+devianttidy.version+'. ',
				$E('a', {href:"javascript:;", events:{'click':function() {devianttidy.update();}}}, ["Look for updates"])
			]),
			$E('div', {className:'pp dialog-controls'}, controls),
			$E('div', {className:'dialog-buttons'}, [
				$E('button', {events:{'click':function() {devianttidy.save(); devianttidy.reload()}}}, ['Save']),
				$E('button', {events:{'click':function() {if(devianttidy.reset()) devianttidy.reload();}}}, ['Reset']),
				$E('button', {events:{'click':function() {devianttidy.dialog.close();}}}, ['Cancel'])
			])
		]);
	},

	load: function()
	{
		for(var o in this.options) {this.options[o].pref = GM_getValue(o, this.options[o].initial);}
		this.log("Loaded options");
	},

	save: function()
	{
		for(var o in this.options) {GM_setValue(o, this.options[o].pref);}
		this.log("Saved options");
	},

	reset: function()
	{
		if(confirm("This will reset all deviantTIDY options to their default values."))
		{
			for(var o in this.options) {this.options[o].pref = this.options[o].initial;}
			this.save();
			this.log("Reset options");
			return true;
		}
		return false;
	},

	dispatch: function()
	{
		this.load();
		this.log("Dispatching...");
		var dispatch_start = new Date();
		var dispatch_count = 0;
		for(var o in this.options)
		{
			// A lazy option should only run when the pref is not 0.
			if(this.options[o].lazy && this.options[o].pref == 0) continue;
			
			// If dispatcher is run again, don't repeat functions that were already run.
			if(this.options[o].dispatched) continue;

			var dispatch_time = new Date();
			var dispatch_result = this.options[o].method();
			this.options[o].dispatched = true;
			dispatch_count++;
			this.log("  Dispatch '"+o+"' returned "+dispatch_result+" in "+(new Date() - dispatch_time)+"ms");
		}
		this.log("Dispatched "+dispatch_count+" function(s) in "+(new Date() - dispatch_start)+"ms");
	},

	reload: function()
	{
		devianttidy.dialog.open('Reloading...', "Reloading page.  Please wait...");
		location.reload();
	},

	update: function(quiet)
	{
		this.log("Looking for updates...");

		var update_error = function()
		{
			if(!quiet)
			{
				devianttidy.dialog.open('Error', [
					$E('div', {className:'ppp c'}, [
						"Unable to get the version information from ",
						$E('a', {href:devianttidy.url}, [devianttidy.url]),
						".  Please try visiting the page yourself to check for updates."
					]),
				]);
			}
		};

		if(!quiet)
		{
			devianttidy.dialog.open('Looking for Updates...', "Checking for a new version of deviantTIDY.  Please wait...");
		}

		GM_xmlhttpRequest
		({
			method: "GET",
			url: this.url,
			onload: function(response)
			{
				var version_text = response.responseText.match(/<b><\/b><b><i>version ([\d.]+)<\/i><\/b><b><\/b>/);
				if(response.status == 200 && version_text)
				{
					var message;
					var version_number = version_text[1];

					if(version_text[1] == devianttidy.version)
					{
						devianttidy.log("No newer version available.");
						if(quiet) return;
						message = ["Your version of deviantTIDY is up to date!"];
					}
					else
					{
						devianttidy.log("Newer version available.");
						if(devianttidy.extension)
						{
							message = [
								"deviantTIDY "+version_number+" is available. ",
								"Open your Firefox Add-Ons window and click Check For Updates."
							];
						}
						else
						{
							message = [
								"deviantTIDY "+version_number+" is available. ",
								$E('a', {href:devianttidy.url}, ["Go to the deviantTIDY homepage"]),
								" to update your style and script."
							];
						}
					}
					devianttidy.dialog.open('Update Status', [$E('div', {className:'ppp c'}, message)]);
				}
				else
				{
					update_error();
				}
			},
			onerror: update_error
		});
	},

	extend: function(object)
	{
		// Use this function to add your own options to deviantTIDY.
		// Call unsafeWindow.devianttidy.extend(...) from a user script to add your custom routine.
		// This will automatically load and execute the routine.
		// Description and method (function) are required.
		if(typeof object.name != 'string' || typeof object.method != 'function' || typeof object.description != 'string')
		{
			this.log("Attempt to extend with a malformed add-on");
			alert(
				"deviantTIDY doesn't like the structure of the option you tried to add.\n"+
				"Please check that you have the required parameters and that their types are correct."
			);
			return;
		}
		else if(this.options[object.name])
		{
			this.log("Attempt to extend resulted in a name-clash.");
			alert("The deviantTIDY option '"+object.name+"' already exists.  You cannot extend it.");
			return;
		}

		if(typeof object.initial == 'undefined') object.initial = 1;
		object.custom = true;
		this.options[object.name] = object;
		this.log("Extended with custom function '"+object.name+"'");

		// We must delegate this to a timeout because executing it under unsafeWindow will
		// result in access violations when calling GM functions
		window.setTimeout(function(){devianttidy.dispatch();}, 1);
	},

	options:
	{
		'short_titles':
		{
			description: "Shorten page titles by removing deviantART prefixes and suffixes",
			hint: "For instance, a window or tab named 'Spyed on deviantART' will be shortened to 'Spyed'.",
			initial: 1,
			lazy: true,
			method: function() {document.title = document.title.replace(/^(deviantART): where ART meets application!$|^deviantART: | on deviantART$| deviantART( \w+)$/i, '$1$2'); return 1;}
		},
		'scroll_comments':
		{
			description: "Add scrollbars to long comments and notes...",
			initial: 2,
			lazy: true,
			choices: ["Never", "When comment is over 15 lines long", "When comment is over 30 lines long"],
			method: function() {dA.className += " dt-scroll-comments s" + this.pref; return 1;}
		},
		'limit_width':
		{
			description: "Limit the maximum width of deviantART pages on wide screens",
			initial: 0,
			lazy: true,
			choices: ['No limit', '1200 pixels', '1400 pixels', '1600 pixels'],
			method: function() {dA.className += " dt-limit-width l"+this.pref; return 1;}
		},
		'no_moods':
		{
			description: "Hide all mood icons in comments and mood selectors in reply boxes",
			initial: 0,
			lazy: true,
			method: function() {dA.className += " dt-no-moods"; return 1;}
		},
		'strip_outgoing_links':
		{
			description: "Strip deviantART redirects from external links",
			initial: 1,
			lazy: true,
			method: function()
			{
				var links = dA.getElementsByTagName('a');
				for (var i=0, j=0; i<links.length; i++)
				{
					var link = links[i];
					if(link.href && link.href.indexOf('http://www.deviantart.com/users/outgoing?')==0)
					{
						link.href = link.href.substring(41);
					}
				}
				return j;
			}
		},
		'collapse_sidebar':
		{
			description: "Collapse the folders/categories sidebar on galleries and browse pages",
			initial: 0,
			lazy: true,
			method: function() {dA.className += " dt-collapse-sidebar"; return 1;}
		},
		'redirect_on_login':
		{
			description: "After logging in...",
			initial: 0,
			lazy: true,
			choices: ['Do not redirect', 'Go to your Message Center', 'Go to your Channels page, like dAv5'],
			method: function()
			{
				if(location.href=="http://www.deviantart.com/?loggedin=1")
				{
					var redirects = {
						1: {url:'http://my.deviantart.com/messages/', name:'Message Center'},
						2: {url:'http://www.deviantart.com/channels/', name:'Channels'}
					};
					devianttidy.dialog.open("Logged In", "Going to "+redirects[this.pref].name+"...");
					location.href = redirects[this.pref].url;
					return 1;
				}
				return 0;
			}
		},
		'no_prints':
		{
			description: "Skip the Prints stage when submitting deviations.",
			initial: 1,
			lazy: true,
			method: function()
			{
				if(location.href.indexOf("http://www.deviantart.com/submit/sell/?ty=1&deviationId=")==0)
				{
					devianttidy.dialog.open("No Prints Please!", "Skipping straight to deviation...");
					location.href = location.href.replace(/submit\/sell\/\?ty=1&deviationId=/, "deviation/");
					return 1;
				}
				return 0;
			}
		},
		'show_forum_icons':
		{
			description: "Show forum thread icons",
			initial: 0,
			lazy: true,
			method: function() {dA.className += " dt-forum-icons"; return 1;}
		},
		'disable_dnd':
		{
			description: "Disable drag-and-drop thumbnail collecting",
			hint: "Note that while drag-and-drop is disabled, you will be unable to perform actions like selecting and moving items in your messages or gallery.",
			initial: 0,
			lazy: true,
			method: function() {if(unsafeWindow.DDD) {delete unsafeWindow.DDD; return 1;} else {return 0;}}
		},
		'divide_signatures':
		{
			description: "Dividing lines between comments and signatures",
			initial: 1,
			lazy: true,
			method: function()
			{
				var time = new Date();
				var boundary = "<br><br>--<br>";
				var i, j=0, node, parts;
				// Get comment nodes
				var nodes = $X("//div[@class='thought block']/div[@class='body']|//div[contains(@class,'ccomment')]//div[@class='text text-ii']");
				for (i=0; node=nodes.snapshotItem(i); i++) if(node.innerHTML.indexOf(boundary)>0)
				{
					// Get the comment and signature portions as 2-element array
					parts = node.innerHTML.split(boundary);
					if(parts.length > 2)
					{
						devianttidy.log("A comment was found with more than one signature boundary ("+(parts.length-1)+")");
						continue;
					}
					else
					{
						node.innerHTML = parts[0] + '<div class="box dt-deviant-signature">' + parts[1] + '</div>';
						j++;
					}
				}
				return j;
			}
		},
		'divide_notes':
		{
			description: "Dividing lines between individual replies in notes",
			initial: 1,
			lazy: true,
			method: function()
			{
				if(!$("notes")) return -2;
				node = $X("//div[@class='item']/div[@class='trailing item-body']/p").snapshotItem(0);
				if(!node) return -1;
				var divider = "<br>----------<br>";
				var parts = node.innerHTML.split(divider);
				if(parts.length>1) node.innerHTML = parts.join('<div class="box dt-deviant-signature">&nbsp;</div>');
				return parts.length-1;
			}
		},
		'floating_comment':
		{
			initial: 1,
			lazy: true,
			method: function()
			{
				var link, form, box, close, body;
				
				link = $X("//h2/a[@href='#commentbody']").snapshotItem(0); // v5 pages
				if(!link) link = $X("//a[@href='#commentbody']", $('deviation-links')).snapshotItem(0); // v6 deviations

				if(form = $('cooler-comment-submit')) box = form.parentNode.parentNode.parentNode;
				else if(form = $('commentsubmit')) {box = form; box.childNodes[1].className += ' bubbleview';}
				else return -2;

				if(!(close = $X("//img[@class='cx']|//a[@class='x']", box).snapshotItem(0))) return -1;
				body = $('commentbody');

				var icon_down = 'data:image/gif;base64,R0lGODlhDwAPAKIAAP///9zh29vg2trf2UhTTEdSS0ZRSv///yH5BAEHAAcALAAAAAAPAA8AAAMyeKrVvfC4+SC962pFNJVeWAABYQpAdw0kgQrqSgICFTds/d3FgB28DUcUMdkIkcUtkgAAOw==';
				var icon_up = 'data:image/gif;base64,R0lGODlhDwAPAJEAAP///9vg2kdSS////yH5BAEHAAMALAAAAAAPAA8AAAIknC2Zx6O/GJxnWgQDnQFoq3QiKIyj5YUAyTps9EYu2dANpjQFADs=';

				if(close.tagName=='A')
				{
					close.removeAttribute('onclick');
					close.setAttribute('href', 'javascript:;');
					close.setAttribute('style', 'display:block; background:url('+icon_up+') !important;');
				}
				else
				{
					close.src = icon_up;
					close.setAttribute('style', 'display:block; float:right;');
				}

				var box_toggle = function(){
					if(box.hasAttribute('style'))
					{
						box.removeAttribute('style');
						box.className = box.className.substr(0, box.className.length-20);
						if(close.tagName!='A') close.src = icon_up;
						else close.setAttribute('style', 'display:block; background:url('+icon_up+') !important;');
					}
					else
					{
						box.setAttribute('style', 'position:fixed; bottom:0; left:10px; right:10px; z-index:200;');
						box.className += ' dt-floating-comment';
						if(close.tagName!='A') close.src = icon_down;
						else close.setAttribute('style', 'display:block; background:url('+icon_down+') !important;');
						window.setTimeout(function(){body.focus();}, 100);
					}
					return false;
				};

				close.addEventListener('click', box_toggle, false);
				close.title = 'Toggle floating comment box';
				close.setAttribute('accesskey', 'r');
				
				if(link)
				{
					link.href = "javascript:;";
					link.removeAttribute('onclick');
					link.addEventListener('click', box_toggle, false);
					return 2;
				}
				else
				{
					return 1;
				}
			}
		}
	}
};


// Go!
devianttidy.start();