MiloticScale / dAmn Tab Logger

// ==UserScript==
// @name        dAmn Tab Logger
// @namespace   MiloticScale
// @author 		Jonathon Braswell
// @description Allows you to see all messages directed toward you neatly in a floating modal.
// @include     http://chat.deviantart.com/chat/*
// @version     1.1
// ==/UserScript==

// Parent function to append to the DOM on init
var script = document.createElement('script');
var helperScript = freeFunctionString((function(){

	// Pertinent object to house the utility functions.
	dAmn_TabLogger = {
		// version
		// Returns: String
		// Purpose: Shortcut for version
		version: "v.1.1",
		// testers
		// Returns: Array
		// Purpose: Keeps track of script testers who helped out. Might implement something special
		//		for them at a later time.
		testers: ['pickley', 'deviant-garde', 'darkalchemistninja', 'romaji', 'farand', 'ricsmond', 'lady-ra', 'princejose', 'moronicglasskiller', 'kristenspork'],
		// updateStatus
		// Arguments: Boolean status
		// Returns: void
		// Purpose: Toggle text and styling of the on/off button in the modal
		updateStatus: function(status) {
			if(status)
				$('#dAmn_TabLoggerStatus')
					.removeAttr('style')
					.css(dAmn_TabLogger.CSS.status)
					.css(dAmn_TabLogger.CSS.onStatus)
					.text("on");
			else
				$('#dAmn_TabLoggerStatus')
				.removeAttr('style')
				.css(dAmn_TabLogger.CSS.status)
				.css(dAmn_TabLogger.CSS.offStatus)
				.text("off");
		},
		// addLog
		// Arguments: Object info
		// Returns: void
		// Purpose: Collect message information from the DOM and reflect it in the modal. Calls this.playSound
		addLog: function(info) {
			$('#dAmn_TabLoggerLogs .no-logs').hide();
			var infoString = info.time + " [" + info.chat + "] <<i></i>" + info.from + "<i></i>> " + info.message;
			var newLog = $('<span/>')
				.addClass('log')
				.html(infoString + "<hr>")
				.appendTo($('#dAmn_TabLoggerLogs'));
		},
		// clearLog
		// Arguments: none
		// Returns: void
		// Purpose: Empty the modal of logs
		clearLog: function() {
			$('#dAmn_TabLoggerLogs .log, #dAmn_TabLoggerLogs hr').remove();
			$('#dAmn_TabLoggerLogs .no-logs').fadeIn();
			this.notify("Cleared tab logs");
		},
		// showLog
		// Arguments: none
		// Returns: void
		// Purpose: Display the modal
		showLog: function() {
			$('#dAmn_TabLoggerModal').fadeIn();
		},
		// hideLog
		// Arguments: none
		// Returns: void
		// Purpose: Hide the modal
		hideLog: function() {
			$('#dAmn_TabLoggerModal').fadeOut();
		},
		// showPreferences
		// Arguments: none
		// Returns: void
		// Purpose: Show the preferences modal
		showPreferences: function() {
			$('#dAmn_TabLoggerLogs').hide('fast');
			$('#dAmn_TabLoggerPreferencesModal').show('fast');
		},
		// hidePreferences
		// Arguments: none
		// Returns: void
		// Purpose: Hide the preferences modal
		hidePreferences: function() {
			$('#dAmn_TabLoggerPreferencesModal').hide('fast');
			$('#dAmn_TabLoggerLogs').show('fast');
		},
		// start
		// Arguments: Boolean doNotify
		// Returns: void
		// Purpose: Turns on logging, instantiates a MutationObserver. The MutationObserver
		//		calls this.addLog when the user is tabbed. Flips the on bit in localStorage.
		//		Calls this.notify and this.updateStatus
		startLog: function(doNotify) {
			if(localStorage.dAmn_TabLoggerStatus !== "on") {
				localStorage.dAmn_TabLoggerStatus = "on";
				this.updateStatus(true);
				if(doNotify)
					this.notify("Started logging tabs");
			}
		},
		// stop
		// Arguments: Boolean doNotify
		// Returns: void
		// Purpose: Deletes the MutationObserver on the chat window. Flips the bit
		//		in localStorage. Calls this.notify and this.updateStatus
		stopLog: function(doNotify) {
			if(localStorage.dAmn_TabLoggerStatus !== "off") {
				localStorage.dAmn_TabLoggerStatus = "off";
				this.updateStatus(false);
				if(doNotify)
					this.notify("Stopped logging tabs");
			}
		},
		// notify
		// Arguments: String message
		// Returns: void
		// Purpose: Appends message to the chat window. Attaches onclick event to the message
		//		which removes the element from the DOM. Make sure the window stays at the bottom, too.
		notify: function(message) {
			var notification = $('<div/>')
				.addClass('msg part')
				.css('font-weight', 'bold')
				.html('<span style="font-size: 90%">' + this.getTimestamp() + '</span>' + '  *** ' + message + ' *');
			$(notification).appendTo($('.damncrc-chat'));
			$('.damncrc-chat-window').each(function(i, cWindow) {
				$(cWindow).animate({ scrollTop: $('.damncrc-chat').eq(i).height() }, 'fast');
			});
		},
		// getTimestamp
		// Arguments: none
		// Returns: String
		// Purpose: Generates a timestamp
		getTimestamp: function() {
			function checkTime(time) {
				return (time < 10 ? "0" + time : time);
			}
			var now = new Date();
			var h = now.getHours();
			h = h % 12;
			h = checkTime(h);
			h = h ? h : 12;
			h = (h == "00" ? 12 : h);
			var m = checkTime(now.getMinutes());
			var s = checkTime(now.getSeconds());
			var ampm = (now.getHours() >= 12 ? 'PM' : 'AM');
			return (h + ':' + m + ':' + s + ' ' + ampm);
		},
		// CSS
		// Purpose: House all CSS styling necessary in this user script as objects
		CSS: {
			logModal: {
				'position': 'absolute',
				'top': '50%',
				'left': '50%',
				'margin-top': '-200px',
				'margin-left': '-35%',
				'z-index': '9999',
				'border': '1px solid #000',
				'border-radius': '8px',
				'text-align': 'center',
				'display': 'none',
				'background': '#bbc2bb',
				'box-shadow': '0px 0px 10px #000',
				'width': '60%',
				'min-height': '100px'
			},
			logContainer: {
				'padding': '20px',
				'margin': '10px',
				'background': '#fafffa',
				'color': '#000',
				'font-size': '13px',
				'text-align': 'left',
				'overflow': 'auto',
				'min-height': '80%',
				'min-width': '80%',
				'max-height': '300px'
			},
			preferencesModal: {
				'z-index': '99999',
				'text-align': 'left',
				'box-shadow': '0px 0px 5px #000',
				'padding': '20px',
				'width': '30%',
				'position': 'initial',
				'margin': '0 auto',
				'margin-top': '20px',
				'margin-bottom': '20px',
				'background': '#fafffa'
			},
			status: {
				'margin': '10px',
				'border': '1px solid #000',
				'border-radius': '50px',
				'height': '15px',
				'width': '35px',
				'float': 'left',
				'cursor': 'pointer',
				'color': '#fafffa',
				'text-align': 'center'
			},
			onStatus: {
				'background': '#00cc00'
			},
			offStatus: {
				'background': '#cc0000'
			},
			clearButton: {
				'margin': '10px',
				'border': '1px solid #000',
				'border-radius': '50px',
				'height': '15px',
				'width': '35px',
				'position': 'absolute',
				'top': '0',
				'left': '50px',
				'cursor': 'pointer',
				'background': '#fafffa',
				'text-align': 'center'
			},
			preferencesButton: {
				'margin': '10px',
				'border': '1px solid #000',
				'border-radius': '50px',
				'height': '15px',
				'width': '85px',
				'position': 'absolute',
				'top': '0',
				'left': '100px',
				'cursor': 'pointer',
				'background': '#bbc2bb',
				'text-align': 'center'
			}
		},
		// bindCommands
		// Arguments: none
		// Returns: void
		// Purpose: Binds chat commands to the relevant object methods
		// Full credit for this model to electricjonny - http://electricjonny.deviantart.com
		bindCommands: function() {
			// Bind the chat commands to our object
			dAmnChanChat.prototype.loggerCommands_Init = dAmnChanChat.prototype.Init;
			dAmnChanChat.prototype.Init = function(cr, name, parent_el) {
				this.loggerCommands_Init(cr, name, parent_el);
				var cie = this.input;
				cie.cmds['tablogger']=[0];
			};
			dAmnChatInput_onKey_loggerCommands = dAmnChatInput_onKey;
			dAmnChatInput_onKey = function(e, kc, force) {
				var el = this.chatinput_el;
				if (kc == 13 && (force || !this.multiline || e.shiftKey || e.ctrlKey)) {
					var input = el.value;
					var args = /^\/(\S*)\s*(.*)$/i.exec(input);
					if (args) {
						var cmd = args[1];
						var param = args[2];
						var did = false;
						if (cmd) {
							if(cmd == "tablogger" && !param) {
								dAmn_TabLogger.showLog();
							}
							switch (param) {
								case 'on':
								case 'start':
									dAmn_TabLogger.startLog(true);
									did = true;
									break;
								case 'off':
								case 'stop':
									dAmn_TabLogger.stopLog(true);
									did = true;
									break;
								case 'clear':
								case 'empty':
									dAmn_TabLogger.clearLog();
									break;
							}
							if (did) {
								if (el.value) {
									el.value = '';
									el.focus();
								};
							};
						};
					};
				};
				if (!did) {
					return this.onKey_loggerCommands(e, kc, force) ? true: false;
				} else {
					return false;
				};
			};
			dAmnChatInput.prototype.onKey = dAmnChatInput_onKey;
			dAmnChatInput.prototype.onKey_loggerCommands = dAmnChatInput_onKey_loggerCommands;
		},
		// bindTabs
		// Arguments: none
		// Returns: void
		// Purpose: Binds message events.
		bindTabs: function() {
			dAmnChat_onData_alert = dAmnChat_onData;
			dAmnChat_onData = function ( pkt ){
				this.onData_alert(pkt);
				if (pkt.param == this.ns) {
					switch (pkt.cmd) {
					case 'recv':
						var recv = dAmn_ParsePacket(pkt.body);
						var ns = pkt.param;
						var isPchat = (ns[0] == "p");
                            var whiteListOn;
                            var isInWhiteList;
						// Make sure the packet being parsed here is not just data retrieval
						if(pkt.body.substring(0,3) == "msg" || pkt.body.substring(0,6) == "action") {
							// The below line is taken from dAmn's own code to determine whether your username is in the message
							// Added "i" argument for case-insensitive
							if(-1 != recv.body.search(RegExp("([^A-Za-z]+|^)" + dAmn_Client_Username + "([^A-Za-z]+|$|s$|s[^A-Za-z]+)" , "i")) || isPchat){
								if(localStorage.dAmn_TabLoggerStatus == "on" && !isPchat) {
									var info = {};
									info.time = dAmn_TabLogger.getTimestamp();
									info.from = (typeof dAmnChats[ns].members.members[recv.args.from].name !== "undefined" ? dAmnChats[ns].members.members[recv.args.from].name : dAmn_Client_Username);
									info.chat = ns.replace("chat:", "#");
									// Need to parse thumbs, links, emotes, etc.
									dAmnChanChat.prototype.FormatMsg(recv.body, function(t) {
										info.message = t;
									}.bind(this));
									// Don't log if you tab yourself.
									// Or if you have white list enabled and the user is not in it.
									if(info.from !== dAmn_Client_Username) {
										whiteListOn = dAmn_TabLoggerPreferences.whiteList;
										isInWhiteList = $.inArray(info.from.toLowerCase(), localStorage.dAmn_TabLoggerWhiteList.split(',')) > -1;
										if((whiteListOn && isInWhiteList) || !whiteListOn) {
											dAmn_TabLogger.addLog(info);
										}
									}
								}
								// Don't make the noise if you tab yourself.
								// Or if you have white list enabled and the user is not in it.
								if(dAmn_TabLoggerPreferences.tabNoise && (recv.args.from !== dAmn_Client_Username)) {
									whiteListOn = dAmn_TabLoggerPreferences.whiteList;
									isInWhiteList = $.inArray(recv.args.from.toLowerCase(), localStorage.dAmn_TabLoggerWhiteList.split(',')) > 1;
									if((whiteListOn && isInWhiteList) || !whiteListOn) {
										dAmn_TabLogger.playSound();
									}
								}
								// Switch the active chat tab to the source chat if preferred
								if(dAmn_TabLoggerPreferences.autoSwitch && (recv.args.from !== dAmn_Client_Username) && !isPchat)
									$('.tabbar').find('a:contains("' + ns.replace("chat:", "#") + '")').click();
							}
						}
					}
				}
			};
			dAmnChat.prototype.onData=dAmnChat_onData;
			dAmnChat.prototype.onData_alert=dAmnChat_onData_alert;		
		},
		// savePreferences
		// Arguments: none
		// Returns: void
		// Purpose: Constructs an object based on the preferences modal and stores in localStorage
		savePreferences: function() {
			var savedPreferences = {
				onAway: $('input.onAway').is(':checked'),
				onBack: $('input.onBack').is(':checked'),
				bindCommands: $('input.bindCommands').is(':checked'),
				tabNoise: $('input.tabNoise').is(':checked'),
				autoSwitch: $('input.autoSwitch').is(':checked'),
				whiteList: $('input.whiteList').is(':checked')
			};
			localStorage.dAmn_TabLoggerPreferences = JSON.stringify(savedPreferences);
			$('.preferenceMessage').html('<b>Saved</b>');
			this.getPreferences();
			if(dAmn_TabLoggerPreferences.whiteList) this.bindWhiteList();
		},
		// playSound
		// Arguments: none
		// Returns: void
		// Purpose: Play the audio element
		playSound: function() {
			document.getElementById('dAmn_TabLoggerSound').play();
		},
		// getPreferences
		// Arguments: none
		// Returns: void
		// Purpose: Load existing preferences for future use.
		getPreferences: function() {
			dAmn_TabLoggerPreferences = JSON.parse(localStorage.dAmn_TabLoggerPreferences);
		},
		// dependencies
		// Purpose: House all the filepaths of this script's dependencies
		dependencies: {
			js: [
				'//code.jquery.com/ui/1.11.4/jquery-ui.js',
			],
			css: [
				'//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css',
				'//code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css',
			]
		},
		// includeDependencies
		// Arguments: none
		// Returns: void
		// Purpose: Include all this script's dependencies
		includeDependencies: function() {
			var jsFiles = this.dependencies.js;
			var cssFiles = this.dependencies.css;
			$(cssFiles).each(function(i, ext) {
				$('<link/>').attr('href', ext).appendTo('head');   
			});
			$(jsFiles).each(function(i, ext) {
				$('<script/>').attr('type', 'text/javascript').attr('src', ext).appendTo('body');
			});
		},
		// bindWhiteList
		// Arguments: none
		// Returns: void
		// Purpose: Bind event on the correct DOM elements for whitelist management
		bindWhiteList: function() {
			// Add event listener to username spans to control whitelist if enabled
			$(document).click(function(event) {
				if(dAmn_TabLoggerPreferences.whiteList && $(event.target).hasClass('username')) {
					if(event.shiftKey && $(event.target).text() !== dAmn_Client_Username) {
						event.preventDefault();
						event.stopPropagation();
						var message, response, recipient, isInWhiteList, whiteList;
						whiteList = (localStorage.dAmn_TabLoggerWhiteList === "" ? [] : localStorage.dAmn_TabLoggerWhiteList.split(','));
						recipient = $(event.target).text().toLowerCase();
						isInWhiteList = $.inArray(recipient, whiteList) > -1;
						message = "Would you like to " + (isInWhiteList ? "remove" : "add") + " " + recipient + " " + (isInWhiteList ? "from" : "to") + " your dAmn Tab Logger White List?";
						response = confirm(message);
						if(response) {
							if(!isInWhiteList) {
								whiteList.push(recipient);
							} else  {
								whiteList.reverse();
								whiteList.splice(recipient, 1);
							}
							dAmn_TabLogger.saveWhiteList(whiteList);
						}
					}
				}
			});
		},
		// saveWhiteList
		// Arguments: Object whitelist
		// Returns: void
		// Purpose: Save the passed in object as the new white list
		saveWhiteList: function(whitelist) {
			localStorage.dAmn_TabLoggerWhiteList = whitelist.join(',');
			this.notify("White List updated");
		},
		// init
		// Arguments: none
		// Returns: void
		// Purpose: Does everything. :D
		init: function() {
			// Create our "database" to store the on/off bit.
			localStorage.dAmn_TabLoggerStatus = "";
			if(!localStorage.dAmn_TabLoggerPreferences) localStorage.dAmn_TabLoggerPreferences = JSON.stringify({onAway: false, onBack: false, bindCommands: false, tabNoise: false, autoSwitch: false, whiteList: false});
			if(!localStorage.dAmn_TabLoggerWhiteList) localStorage.dAmn_TabLoggerWhiteList = "";
			this.getPreferences();
			
			// Bind commands
			if(dAmn_TabLoggerPreferences.bindCommands)
				this.bindCommands();
			
			// Start listening for tabs
			this.bindTabs();
			
			// Include the script dependencies
			this.includeDependencies();
			
			// Initialize context menus
			if(dAmn_TabLoggerPreferences.whiteList)
				this.bindWhiteList();
			
			// jQuery extension to re-center the modal
			$.fn.center = function () {
				$(this).css(dAmn_TabLogger.CSS.logModal);
				return $(this).fadeIn();
			};
            
			// jQuery extension to tell if the modal header text is fully visible on the screen
			$.fn.isFullyVisible = function() {
				var off, et, el, eh, ew, wh, ww, wx, wy;
				off = this.offset();
				et = off.top;
				el = off.left;
				eh = this.height();
				ew = this.width();
				wh = window.innerHeight;
				ww = window.innerWidth;
				wx = window.pageXOffset;
				wy = window.pageYOffset;
				return (et >= wy && el >= wx && et + eh <= wh + wy && el + ew <= ww + wx);  
			};
            
			// Create div element that will serve as the header of the modal.
			// Append the header text and the X button to close the modal.
			var modalTitle = $('<div/>')
				.css('border-bottom','1px solid #000')
				.css('background', 'linear-gradient(to bottom, #9eb1a3, #607465)')
				.css('border-top-left-radius', '8px')
				.css('border-top-right-radius', '8px')
				.attr('id', 'dAmn_TabLoggerModalHeader')
				.append($('<div class="modalHeader" style="display: inline-block; margin: 10px; font-size: 20px;">dAmn Tab Logger</div>'))
				.append($('<span style="float: right; font-size: 25px; margin-right: 10px; cursor: pointer;" onclick="dAmn_TabLogger.hideLog()">&times;</span>'));
			// Create div element to display when there are no logs stored in the modal.
			var noLogs = $('<div/>')
				.addClass('no-logs')
				.html('<center>No logs!</center>')
				.css('font-style', 'italic');
			// Create the modal div.
			// Append modalTitle and the div to house the actual log text.
			// Append credit div to the bottom of the modal.
			// Implement jQuery draggable. Only drag by the modal header, do not allow
			//		the modal to let the page scroll, and reposition the entire modal
			//		to the default position if any part of the modal header is off screen
			//		when the modal is dropped by the user.
			var resultsModal = $('<div/>')
				.attr('id', 'dAmn_TabLoggerModal')
				.css(dAmn_TabLogger.CSS.logModal)
				.append(modalTitle)
				.append($('<div/>')
					.attr('id', 'dAmn_TabLoggerLogs')
					.css(dAmn_TabLogger.CSS.logContainer)
					.append(noLogs))
				.append($('<sub/>')
					.attr('id', 'dAmn_TabLoggerInfo')
					.attr('style', 'color: #98a39f !important; float: left;')
					.html(dAmn_TabLogger.version + ' by <a href="http://miloticscale.deviantart.com">MiloticScale</a><span class="user-symbol senior"></span></a><br/>')
					.after($('<div style="clear:both;display:none;"></div>')))
				.appendTo($('body'))
				.draggable({
					scroll: false,
					handle: '#dAmn_TabLoggerModalHeader',
					stop: function() {
						if(!$('#dAmn_TabLoggerModalHeader .modalHeader').isFullyVisible()) $(this).center();
					}
				});
			// Create div element that will serve as a clear button. Add onclick event listener to call this.clearLog
			var clearButton = $('<div/>')
				.attr('id', 'dAmn_TabLoggerClear')
				.css(dAmn_TabLogger.CSS.clearButton)
				.html('clear')
				.click(function() {
					dAmn_TabLogger.clearLog();
				})
				.prependTo(resultsModal);
			// Create a link element and attach an onclick event listerner to it that shows the modal.
			// Ensure that all chat windows get the link via setInterval.
			// Pseudo-detection of /setaway appended
			var insertLink = setInterval(function() {
				$('.damncrc-iconbar-ctrls').each(function(i, el) {
					if($(el).find('.dAmn_TabLoggerLink').length < 1) {
						if(localStorage.dAmn_TabLogger == "on") {
							dAmn_TabLogger.stopLog(false);
							dAmn_TabLogger.startLog(false);
						}
						var loggerLink = $('<a/>')
							.addClass('dAmn_TabLoggerLink')
							.css('cursor', 'pointer')
							.html('Tab Log')
							.click(function() {
								dAmn_TabLogger.showLog();
							})
							.prependTo($(el))
							.after(' <b>|</b> ');
						// Monitor the ctrls span and start logging on pseudo-detection of /setaway
						var awayTargets = document.querySelectorAll('.superdamn-awayspace');
						var awayObserver = new MutationObserver(function(mutations) {
							$(mutations).each(function(i, mutation) {
								if(mutation.addedNodes.length > 0) {
									if(dAmn_TabLoggerPreferences.onAway) dAmn_TabLogger.startLog(true);
								} else {
									if(dAmn_TabLoggerPreferences.onBack) dAmn_TabLogger.stopLog(true);
								}
							});    
						});
						$(awayTargets).each(function(i, target) {
							awayObserver.observe(target, {attributes: true, childList: true, characterData: true});
						});
					}
				});
			}, 1000);
			// Create div element that will serve as the status icon in the modal.
			// Give it some text based on our bit in storage.
			// Attach onclick event listener to it which starts/stops logging
			//		depending on our bit in storage.
			// Prepend this to the modal.
			var statusIcon = $('<div/>')
				.attr('id','dAmn_TabLoggerStatus')
				.css(dAmn_TabLogger.CSS.status)
				.css(localStorage.dAmn_TabLoggerStatus == "on" ? dAmn_TabLogger.CSS.onStatus : dAmn_TabLogger.CSS.offStatus)
				.html(localStorage.dAmn_TabLoggerStatus == "on" ? "on" : "off")
				.click(function() {
					if(localStorage.dAmn_TabLoggerStatus == "on")
						dAmn_TabLogger.stopLog(true);
					else
						dAmn_TabLogger.startLog(true);
				})
				.prependTo(resultsModal);
			// Create div element that will serve as the preferences button in the modal.
			var preferencesButton = $('<div/>')
				.attr('id', 'dAmn_TabLoggerPreferencesButton')
				.css(dAmn_TabLogger.CSS.preferencesButton)
				.html('preferences')
				.click(function() {
					dAmn_TabLogger.showPreferences();
				})
				.prependTo(resultsModal);
			// Create preferences modal
			var preferencesModal = $('<div/>')
				.attr('id', 'dAmn_TabLoggerPreferencesModal')
				.css(dAmn_TabLogger.CSS.logModal)
				.css(dAmn_TabLogger.CSS.preferencesModal)
				.append(typeof MiddleMan !== "undefined" ? '<div class="preference"><input type="checkbox" class="onAway" /> Automatically start logging on /setaway</div><hr />' : '')
				.append(typeof MiddleMan !== "undefined" ? '<div class="preference"><input type="checkbox" class="onBack" /> Automatically stop logging on /setback</div><hr />' : '')
				.append('<div class="preference"><input type="checkbox" class="autoSwitch" /> Automatically switch to a chat I\'m tabbed in</div><hr />')
				.append('<div class="preference"><input type="checkbox" class="bindCommands" /> Enable chat commands</div><hr />')
				.append('<div class="preference"><input type="checkbox" class="whiteList" /> Enable white list</div><hr />')				
				.append('<div class="preference"><input type="checkbox" class="tabNoise" /> Play sound when I\'m tabbed <br/><sub>(Volume: Shift + &uarr;/&darr;)</sub></div><hr/>')
				.append('<div style="float: left; border: 1px solid #000; border-radius: 5px; color: #fafffa; background: #00cc00; padding: 5px; cursor: pointer;" onclick="dAmn_TabLogger.savePreferences()">Save</div>')
				.append('<div style="display: inline-block; margin-left: 10px; border: 1px solid #000; border-radius: 5px; color: #000; background: #fafffa; padding: 5px; cursor: pointer;" onclick="dAmn_TabLogger.hidePreferences(); $(\'.preferenceMessage\').html(\'\');">Close</div>')
				.append('<div style="margin-left: 30px; display: inline-block" class="preferenceMessage"></div>')
				.appendTo(resultsModal);
			// Check the checkboxes based on current preferences
			var set = ['onAway', 'onBack', 'bindCommands', 'tabNoise', 'autoSwitch', 'whiteList'];
			$(set).each(function(i, s) {
				$('input.' + s).prop('checked', dAmn_TabLoggerPreferences[s]);
			});
			// Create audio element to play when tabbed.
			$('<audio/>')
				.attr('id', 'dAmn_TabLoggerSound')
				.attr('src', 'http://jonbraswell.com/scripts/tabbed.wav')
				.attr('preload', 'auto')
				.appendTo($('body'));
			// Add event listener to the document to control the sound volume.
			$(document).on('keyup', function(event) {
				if(dAmn_TabLoggerPreferences.tabNoise) {
					var sound = document.getElementById('dAmn_TabLoggerSound');
					var shift = event.shiftKey;
					var plus = (event.keyCode == '38');
					var minus = (event.keyCode == '40');
					if(shift && plus) {
						if(sound.volume < 1) {
							sound.volume += 0.1;
							dAmn_TabLogger.notify("Notification sound volume increased");
							sound.play();
						} else {
							dAmn_TabLogger.notify("Max notification sound volume reached");
						}
					}
					if(shift && minus) {
						if(sound.volume > 0.1) {
							sound.volume -= 0.1;
							dAmn_TabLogger.notify("Notification sound volume decreased");
							sound.play();
						} else {
							dAmn_TabLogger.notify("Min notification sound volume reached");
						}
					}
				}
			});
		}
	};
	DWait.ready(['jms/pages/chat07/chatpage.js', 'jms/pages/chat07/dAmn.js', 'jms/pages/chat07/dAmnChat.js'], function() {
		// Initialize! 
		dAmn_TabLogger.init();
	});
}).toString());

// Append our housing function to to the DOM as a child of the head element.
script.appendChild(document.createTextNode(helperScript));
document.getElementsByTagName('head')[0].appendChild(script);

// from electricnet's SuperdAmn.
function freeFunctionString(str){
	return str.replace(/^\s*function\s*\(\)\s*\{/, "").replace(/\}\s*$/, "");
}