$.ajaxSetup({
	type: 'POST',
	url: 'http://'+window.location.hostname+((window.location.port != "")?(":"):(""))+window.location.port+"/index.pl",
	// hostname included for Guest User on non-canonical domain with canonical base element in html head
	cache: false,
	dataType: 'text',
	timeout: e2.timeout * 1000
});

e2.ajax = {
// legacy non-production E2AJAX.functions are in [Additional Everything2 Ajax] (in Javascript Repository)

// ======================= Utility

	pending: {},
	htmlcode: function(htmlcode, params, callback, pendingId){
	// htmlcode is the name of an htmlcode. See [ajax update page] for security requirements.
	// params is comma-separated args for htmlcode, or an object optionally containing query
	// parameters (query), htmlcode args (args) and/or ajax parameters (ajax)
		if (typeof params != 'object') params = {args: (params || '').toString()};
		var ajax = params.ajax || {};
		ajax.data = ajax.data || params.query || {};
		ajax.data.args = ajax.data.args || params.args || '';
		ajax.data.htmlcode = htmlcode ;
		ajax.data.node_id = ajax.data.node_id || e2.node_id;
		ajax.data.displaytype = 'ajaxupdate';
		ajax.complete = ajax.complete || ajaxFinished;

		if (pendingId && !ajax.data.ajaxIdle)
			e2.ajax.pending[pendingId] = {htmlcode: htmlcode, query: ajax.data};
		$.ajax(ajax);

		function ajaxFinished(request, statusText){
			if (pendingId) delete e2.ajax.pending[pendingId];
			if (!callback) return;
			var success = statusText == 'success' && (ajax.dataType=='json' ||
				/<[!]-- AJAX OK/.test(request.responseText)); // [!] = fix for brain-dead GPRS network
			if (success)
				var str = request.responseText.replace( /<[!]-- AJAX OK[\s\S]*/ , '' );
			else if (statusText == 'success')
				statusText = 'incomplete response';
	   		else if (statusText != 'timeout')try{
				if (request.status==0)
					throw 'sometimes even the test throws an exception';
				else if (request.status >= 500)
					statusText = 'server error';
			}catch(e){
				statusText = 'connection error';
			}
			callback((success ? str : request), success, statusText || '(unknown error)');
		}
	},

	update: function(id, htmlcode, args, replaceID, callback) {
		var ol = $('#'+id), nu;
		if (!ol[0]) return;
		if (replaceID == null) replaceID = 1;
		e2.ajax.htmlcode(htmlcode, args, ajaxFinished, id);

		function ajaxFinished(result, success, statusText){
			if (success){
				if (replaceID) {
					nu = $(result);
					ol.replaceWith(nu);
				}else{
					ol.html(result);
					nu = $('*', ol);
				}
				e2(nu);
			}
			if (callback) callback(result, success, statusText);
		}
	},

	varChange: function(name, value, callback){
	// NB: name and value have to pass check in htmlcode ajaxVar
		e2.ajax.htmlcode('ajaxVar', name+','+value, callback);
		e2.deleteCookie(name);
	},

	starRateNode: function (node_rate, weight, seed, nonce) {
		$.ajax({data:{
			op : "starRate",
			rating_node : node_rate,
			rating: weight,
			starrating_seed: seed,
			starrating_nonce: nonce,
			displaytype: 'ajaxupdate'
		}});
	},

// ======================= Sleep/wake

	addRobot: function(x){
		// first time called, set up sleep and wake. Subsequent times just add x to list.
		e2.ajax.addRobot = function(y){ robots.push(y); };

		e2.inactiveWindowMarker = e2.inactiveWindowMarker || '';
		var	windowId = e2.getUniqueId(),
			lastActive = e2.now(),
			lastZzz = 'wake',
			wakeEvents = 'focusin focus mouseenter mousemove mousedown keydown keypress scroll click',
			wakeWatch = $(document).add(window).bind(wakeEvents, wake).blur(monitor),
			robots = [new e2.periodical(monitor, 60), x]; // monitor will put itself to sleep

		function monitor(){
			wakeWatch.unbind(wakeEvents, wake).bind(wakeEvents, wake);
			var myCookie = e2.getCookie('lastActiveWindow');
			if (e2.now() - lastActive > e2.sleepAfter * 60000 ||
				(!e2.isChatterlight && myCookie && myCookie != windowId)) zzz('sleep');
		}

		function wake(e){
			// anything this event should do inside the page has been done. Once is enough for this:
			e && e.stopImmediatePropagation();
			wakeWatch.unbind(wakeEvents, wake); // once a minute is enough
			lastActive = e2.now();
			e2.setCookie('lastActiveWindow', windowId);
			zzz('wake');
		}

		function zzz(z){
			if (lastZzz == z) return;
			$(robots).each(function(){this[z]();});
			lastZzz = z;
			titleLength = document.title.length - e2.inactiveWindowMarker.length;
			document.title = (z == 'sleep' ? document.title + e2.inactiveWindowMarker
				: document.title.substr(0, titleLength));
		}
	},

// ======================= list management

	lists: {},

	addList: function(listName, listSpec){
		e2.ajax.lists[listName] = listSpec;
		e2('#'+listName, function(){
			if (!e2.ajax.lists[this.id].manager){
				e2.ajax.lists[this.id].manager = new e2.ajax.listManager(this.id);
			}else{
				e2.ajax.lists[this.id].manager.restart();
			}
		});
	},

	listManager: function(listName){
		function updateThisList(){
			e2.ajax.updateList(listName);
		}
		e2.periodical.call(this, updateThisList,
			e2.ajax.lists[listName].period || e2.defaultUpdatePeriod * 60,
			e2.ajax.lists[listName].stopAfter, e2.ajax.lists[listName].die);
		e2.ajax.addRobot(this);
	},

	updateList: function(listName, query, callback){
		var container = $('#'+listName);
		var list = e2.ajax.lists[listName];
		if (!container[0] || !list) return;
		$('.markedForRemovalNextTime', container).remove();
		if (query == null) query = {ajaxIdle: 1}; // query only passed if user action causes update
		var listCallback = false;
		e2.ajax.htmlcode(list.getJSON,
			{args: list.args, query: query, ajax:{dataType: 'json', success: doJSON}}, callback);

		function doJSON(data){
			for (var i = 1, keep = {}; data[i]; i++){
				var id = list.idGroup + data[i].id;
				keep[id] = true;
				if (!$('#'+id)[0]){
					var el = $(data[i].value);
					if (!el.attr('id'))
						el.attr('id', id);
					if (data[i].timestamp)
						el.addClass('timestamp_' + data[i].timestamp);
					e2.ajax.insertListItem(container, el, data[i].timestamp, list.ascending);
					listCallback = list.callback;
				}
			}
			container.children().each(function(){
				if (!keep[this.id] && this.id.indexOf(list.idGroup)==0 &&
					(!list.preserve || !e2(list.preserve, this)[0])){
					listCallback = list.callback;
					e2.ajax.removeListItem(this);
				}
			});
			if (listCallback) e2(listCallback).defer();
		}
	},

	insertListItem: function(container, content, timestamp, ascending){
		content.hide();
		if (ascending || timestamp){
			container.append(content);
		}else{
			container.prepend(content);
		}
		if (timestamp){
			var dir = (ascending ? 1 : -1 );
			// place above first younger (ascending) or older (descending) item in list
			// if none found, it belongs below them all, where it is
			container.children().each(function(){
				var match = /\btimestamp_(\d+)\b/.exec(this.className);
				if (match && dir*match[1] > dir*timestamp){
					$(this).before(content);
					return false;
				}
			});
		}
		// defer here & in removeListItem so it all happens at once: smoother
		// e2ify content after revealing so expandable inputs have dimensions
		e2(function(){
			content.slideDown(e2.fxDuration, function(){e2(content);});
		}).defer();
	},

	removeListItem: e2(
		function(el){
			// hide the first time, remove in updateList the next time:
			// avoid incoming list reinstating dismissed item
			$(el).slideUp(e2.fxDuration).addClass('markedForRemovalNextTime');
		}
	).defer,

	dismissListItem: function(event){
		var targetId = /\bdismiss\s+(\S+)/.exec(this.className)[1];
		// find which list we're in:
		for (var parent = this.parentNode, spec; parent; parent = parent.parentNode){
			if (parent.id && (spec = e2.ajax.lists[parent.id])) break;
		}
		if (spec && spec.dismissItem)
			e2.ajax.htmlcode(spec.dismissItem, targetId.replace(/\D/g, ''));
		e2.ajax.removeListItem('#'+targetId);
		event.preventDefault();
	},

// ======================= Update Triggers

	periodicalUpdater: function(instructions, period, lifetime, expired){
		this.className = 'ajax ' + instructions ;
		e2.periodical.call(this, function(){$(this).trigger('click');}, // triggerHandler() fails
			period||e2.defaultUpdatePeriod * 60, lifetime, expired);
		e2.ajax.updateTrigger.call(this);
		e2.ajax.addRobot(this);
	},

	updateTrigger: function(){
		if (this.disabled) return;

		var tag = (this.tagName || '').toLowerCase(); // no tagName if periodical updater
		var $this = $(this);
		if (tag) var type = $this.attr('type');

		if ($this.hasClass('instant')){
			e2.ajax.triggerUpdate.call(this);
		}else if (tag == 'textarea' || type == 'hidden' || type == 'text'){
			$(this.form).unbind('.ajax').bind('submit.ajax', formTrigger);
			this.originalValue = this.value;
		}else{
			var bindEvent = (tag != 'select' ? 'click' : 'change');
			if (/[?&]confirmop=/.test(this.href) && tag != 'a')// links already done
				$this.bind(bindEvent, e2.confirmop);
			$this.bind(bindEvent, e2.ajax.triggerUpdate);
		}

		function formTrigger(event){
			var formSpec = $(this).serializeArray();
			$.each(formSpec, function(n){ // serialize() screws up encoding (1.4.3)
				// put values into object later not string now to avoid encoding snarfs
				formSpec[n] = this.name + '=/';
			});
			formSpec = formSpec.join('&');
			var nothingHappened = true;
			$('textarea.ajax, input[type=text].ajax, input[type=hidden].ajax', this)
			.each(function(){
				if (!this.value) return;
				nothingHappened = e2.ajax.triggerUpdate.call(this, event, formSpec) && nothingHappened;
			});
			return nothingHappened;
		}
	},

	triggerUpdate: function(event, formSpec){
		if (event && event.isDefaultPrevented && event.isDefaultPrevented() && !formSpec){ // confirmop may have done this
			if ( this.checked ) this.checked = false ;
			if ( this.blur ) this.blur() ;
			return false;
		}

		// earlybeforeunload for multi-step processes which will end with a page unload
		// -- don't annoy user by only warning them at the end
		if ($(this).hasClass('earlybeforeunload')){
			var msg = window.onbeforeunload({earlyCall: true});
			if (msg && !confirm(msg + '\n\nIf you continue you will end up '
			  + 'leaving the page. Do you want to continue?')) return false;
		}

		// get ajax instructions from class & chop them up
		var params = ('[0] '+this.className).split(/\bajax\s+/);
		if (!params[1]) return true; // no instructions
		params = params[1].split(/\s+/)[0].replace(/\+/g,' ').split(':');
		if (!params[1]) return true; // no htmlcode

		// target:
		var updateTarget = params[0];
		var updateOnly = (updateTarget.charAt(0) == '(');
		if (updateOnly) updateTarget = updateTarget.slice(1,-1);
		var target = $('#'+updateTarget)
			.find('.error').remove().end(); // only show most recent error
		if (!target[0]) return true;

		var args = params[2] || '';

		// htmlcode & query:
		params = params[1].split('?');
		var htmlcode = params[0];
		if (!params[1] && this.href) params = this.href.split('#')[0].split('?');
		var q = decodeURIComponent(params[1] || formSpec || '').split(/[&=]/);

		// get query values from string/form/prompt
		var query = {};
		for (var i=0; q[i]; i+=2){
			var name = q[i], value = q[i+1];
			if (value && value.charAt(0) == '/' ){
				if ( value.charAt(1) != '#' ){ // get value from form
					var el = this.form[value.substr(1) || name];
					if (el && el.length && el[0].type=='radio') el = $(el).filter(':checked')[0];
					if (el && /radio|checkbox/.test(el.type) && el.checked == false) el = null;
					value = el && el.value;
				} else { // get value from prompt
					value = prompt(value.substr(2)+':' , query[name] || '') ;
					if (!value) return false ;
				}
			}
			if (value != null)
				query[name] = (value && value.replace(
					/[^ -~]/gm, function(x) {return "&#" + x.charCodeAt(0) + ";";})
					) || query[name] || '';
		}

		// remember a couple of things for later:
		var sentValue = this.value;
		$(this).addClass('pending');
		var hadFocus = e2.getFocus(target);
		var isPeriodic = !this.tagName;

		if (!isPeriodic && htmlcode != '#'){
		// disable, but avoid disabled controls when returning from history...
			var ersatz = target.clone().addClass('pending');
			target.replaceWith(ersatz);
			(updateOnly ? $ : e2)('*', ersatz) // e2 = inclusive select: include container if it's to be replaced
			.addClass('pending')
			.each(function(){
				if (this.name){
					if (target[this.name]) this.value = target[this.name].value; // still see changed values
					 // ... by changing name on disabled controls:
					$(this).attr({disabled:'disabled', name:'xxx'});
				}
				this.disabled = true;
				if (this.href && /\bajax\s/.test(this.className)) this.href = '#';
			});
			try { e2(ersatz) } catch(e){};
		}

		// now do it
		var e = this;
		if (htmlcode == '#'){
			e2.ajax.updateList(updateTarget, query, doneUpdate);
		}else{
			if (isPeriodic) query.ajaxIdle = 1;
			e2.ajax.update(updateTarget, htmlcode,
				{query: query , args: args}, !updateOnly, doneUpdate);
		}
		if (event && event.preventDefault) event.preventDefault();
		return false;

		// tidy up/report error afterwards
		function doneUpdate(stringIfsuccessfulOrRequestIfNot, success, statusText){
			$(e).add(ersatz).removeClass('pending') ;

			if (success) {
				if ( e.originalHeight ) e.style.height = e.originalHeight ;
				if ( e.originalValue != null && e.value == sentValue ) e.value = e.originalValue ;
			} else if ( !isPeriodic ) { // periodic updater dies quietly
				var tag = target.css('display') == 'block' ? 'div' : 'span';
				target.append('<'+tag+' class="error"><strong>Error:</strong> '+statusText+'</'+tag+'>');
				if (e.htmlcode == '#') return;
				ersatz.replaceWith(target);
				e2(target); // jquery unbinds all the magic when you take it out of the DOM
			}

			if (hadFocus && $('#'+hadFocus).length)
				e2(function(){$('#'+hadFocus).focus();}).defer() ; // IE8 fails without defer
		}
	}
// END e2.ajax = {
};

// automation
if (!e2.guest){

	e2.ajax.addList('notifications_list',{ // id of list container
		getJSON: "notificationsJSON", // htmlcode for list data (required)
		args: 'wrap', // htmlcode arguments for getJSON
		idGroup: "notified_", // id stub for individual list items (required):
		// N.B.: items sent with an id keep it. If it doesn't match the idGroup they will never be removed.
		period: 45, // seconds between updates (default is above)
		dismissItem: 'ajaxMarkNotificationSeen' // htmlcode run when item dismissed. arg is id from json
	});

	e2.ajax.addList('chatterbox_messages', {
		ascending: true, // put newest at bottom (default is newest at top)
		getJSON: 'showmessages',
		args: ',j',
		idGroup: 'message_',
		preserve: 'input:checked', // don't remove list items which match or whose contents match this
		period: 23,
		callback: function(){ // called after update iff anything changed
			if ( $('#chatterbox_messages *')[0] && !$('#formcbox hr').length )
				$('#chatterbox_chatter').before('<hr width="40%">');
		}
	});

 	e2.ajax.addList('messages_messages', {
		ascending: true, // put newest at bottom (default is newest at top)
		getJSON: 'testshowmessages',
		args: ',j',
		idGroup: 'message_',
		preserve: '.showwidget .open', // don't remove list items which match or whose contents match this
		period: 23
	});
			
	e2.ajax.addList('chatterbox_chatter', {
		ascending: true,
		getJSON: 'showchatter',
		args: 'json',
		idGroup: 'chat_',
		period: e2.autoChat ? 11 : -1, // -1 creates periodical function in stopped state
//		preserve: '.chat', // never remove chat items
		callback:(function(){
			// scroll down as chat updated.
			// NB: Without this, slide down of chat is unreliable in IE8, even without scrollbar

			var chat, userScrolled = false,

			scrollChat = new e2.periodical(function(){
				chat.scrollTop = chat.scrollHeight;
			}, -1);


			// tell scrollChat what to scroll, or not to scroll if user has scrolled up
			e2('#chatterbox_chatter', function(){
				scrollChat.restart(jQuery.fx.interval/1000, e2.fxDuration*3/1000);
				$(this)
//				.addClass('autochat') // limits height and adds scroll bar if needed
				.scroll(function(e){
					userScrolled = (this.scrollHeight - this.scrollTop - this.clientHeight > 16);
				});
				chat = this;
			});

			return function(){
				if (!userScrolled) scrollChat.restart();
			};
		})(),

		stopAfter: e2.sleepAfter * 60, // seconds
		die: function(){
			$('#autoChat').each(function(){this.checked=false;});
			$('#chatterbox *').blur(); // '#chatterbox :focus' fails if window is not focussed
			e2.ajax.insertListItem($('#chatterbox_chatter'), $(
				'<p id="chat_stopped"><strong>Chatterbox refresh stopped.</strong></p>'),0,1);
		}
	});

	e2('.dismiss', 'click', e2.ajax.dismissListItem);

	new e2.ajax.periodicalUpdater('newwriteups:updateNodelet:New+Writeups', 300);
	new e2.ajax.periodicalUpdater('otherusers:updateNodelet:Other+Users');
}

// replace other inputs with push buttons
e2('input.replace', function (){
		var label = this.parentNode ;
		if ( this.name.substr(0,6)=='vote__' &&
				( this.getAttribute( 'value' )=='0' || this.disabled ) )
			return label.parentNode.removeChild(label) ;
		var value = label.innerHTML.replace( /^(<.*>|\s)*|(<.*>|\s)*$/g , '' );
		if ( this.name.substr(0,6)=='vote__' ) value = (value=='+' ? 'Up' : 'Down');
		var b=$('<input type="button" class="' + this.className.replace( /\breplace\b/ , 'replaced' ) +
			'" name="' + this.name + '" title="' + this.title + '" value="' + value + '">')[0];
		$(label).replaceWith(b);
		if (b.outerHTML) b.outerHTML=b.outerHTML; // update DOM in IE7

		// tidy up around replaced inputs
		e2('.vote_buttons .replaced', b).each(function(){
			if (this.previousSibling && this.previousSibling.nodeType==3)
				this.parentNode.removeChild(this.previousSibling);});
});

// update nodelet only, not whole page
e2('div.nodelet', function(){ // div.nodelet to filter out body on nodelet's own page
	if (!(this.nodeletName = $('h2', this)[0]) ) return; // probably a failed nodelet
	this.nodeletName = this.nodeletName.innerHTML;
	var omo = 'ajax '+this.id+':updateNodelet:'+this.nodeletName.replace(/ /g, '+');

	$('form', this).each( function(){
		if (this.onsubmit || this.ajaxTrigger || this.passwd || this.node ||
			(this.node_id && this.node_id.type != 'hidden')) return ;
		dummy = $('<input type="hidden" name="ajaxTrigger" value="1" class="'+omo+'">');
		$(this).append(dummy) ;
		if (dummy.outerHTML) dummy.outerHTML=dummy.outerHTML ; // update DOM in IE7
	});

	if (this.nodeletName == 'Master Control') return; // need pageload to see things happen
	$('a', this).each(function(){
		if (this.href.indexOf(e2.pageUrl+'?')==0 &&
				!/\bajax\b/.test(this.className) &&
				!/[&?]displaytype=|[&?]op=logout|[&?]op=randomnode/.test(this.href))
			$(this).addClass(omo);
	});
});

// draggable nodelets
e2('h2.nodelet_title', 'click', function(e){
	// disables nodelet collapser when nodelet is being dropped
	if ($(this).hasClass('stopClick')) e.stopImmediatePropagation();
});

e2('#sidebar:not(.pagenodelets)', 'sortable', {
	axis: 'y',
	containment: 'document',
	items: 'div.nodelet',
	opacity: 0.5,
	handle: 'h2.nodelet_title',
	tolerance: 'pointer',
	cursor: 'n-resize',
	stop: function(e, ui){
		// disable nodelet collapser when nodelet is being dropped
		ui.item.find('h2').addClass('stopClick');
		e2(function(){
			ui.item.find('h2').removeClass('stopClick')
		}).defer();
	},
	update: function(e, ui){
		e2.ajax.htmlcode('movenodelet', ui.item[0].nodeletName + ',' +
			ui.item.prevAll('.nodelet').length,
			null, ui.item[0].nodeletName);
	}
});

// activate update triggers
e2('.wuformaction.ajax', 'unbind'); // ajax makes function to fiddle with form values redundant
e2('.ajax', e2.ajax.updateTrigger);

e2.beforeunload['ajax pending'] = function(e){
	e = e || window.event || {}; // for IE/unknown fail
	if (e.earlyCall) return; // window isn't really going to unload until user has done more

	var warnings = {
	// name: htmlcode; value: code to eval to get warning. false = ignore. List from [ajax update page]
	// id = updateTarget, x = update parameters
		ilikeit: '"Your message to the author hasn\'t arrived yet"',
		writeupmessage: "'Your message is being sent'",
		zenDisplayUserInfo: "'Your message is being sent'",
		weblogform: "'A usergroup page is being updated'",
		categoryform: "'A category is being updated'",
		writeupcools: "'Your C! is being noted. You may lose it if you don\\'t let it finish'",

		updateNodelet: "'The '+x.query.args+' nodelet is updating'",
		coolit: "'This page is being ' +" +
			"(x.query.coolme ? 'dunked in liquid helium' : 'thawed')",
		bookmarkit: "(x.query.bookmark_id == e2.node_id ? 'This page' : 'A writeup') +" +
			"' is being added to your bookmarks'",

		favorite_noder: "opcode(x.query.op)",
		voteit: "opcode(x.query.op)",
		movenodelet: "'The ' + id + ' nodelet\\'s new position is being recorded'",
		// these we ignore:
		listnodecategories: 'false', // instant ajax for info/confirmation only
		nodeletsettingswidget: 'false', // user lost interest in settings
		 // these shouldn't be needed:
		changeroom: '', showmessages: '',
		showchatter: '', displaynltext2: ''
	};

	var str;
	function opcode(op){
		if (!op) return '';
		switch(op){
			case 'vote': return 'Your vote is being recorded';
			case 'favorite': str = 'added to';
			case 'unfavorite': return $('#pageheader h1').text()  +' is being ' +
				(str || 'removed from') + ' your favorite users list';
			case 'hidewriteup': str = 'hidden'; break;
			case 'unhidewriteup': str = 'unhidden'; break;
		 	case 'massacre': str ='nuked'; break;
		 	case 'insure': str = (/undo/.test($('#'+id+' a').text()) ? 'un' : '') +
			 	'insured'; break;
		 	default: str = 'processed';
		}
		return 'A writeup is being ' + str;
	}

	for (var id in e2.ajax.pending){
		var x = e2.ajax.pending[id];
		if (warnings[x.htmlcode] && (str = eval(warnings[x.htmlcode]))) break;
	};
	if (x && str !== false) str = // was something pending, and not to be ignored
		(str || 'Something on this page is being updated') +
		". Please wait a moment.";
	return str;
};

// rename vote button: no longer needed, but can still send form
$(function(){
	$('#votebutton').each(function(){
		this.value = 'blab!' ;
		this.title = 'send writeup message(s)' ;
	});
});

// switch automatic chatter update on and off
e2('#chatterbox:not(.pending)', function(){
	function onOff(on){
		if (on){
			e2.ajax.updateList('chatterbox_chatter');
			e2.ajax.lists.chatterbox_chatter.manager.restart(11);
		}else{
			e2.ajax.lists.chatterbox_chatter.manager.restart(-1);
  		}
	}

	// checkbox to turn it on and off
	$('#autoChat').parent().remove(); // may be one left over if update failed
	$('#message_send').after(
		' <label title="Keep chat up to date even if the focus/cursor is somewhere else">'+
		'<input type="checkbox" value="1" name="autoChat" id="autoChat"'+
		(e2.autoChat ? ' checked="checked"' : '') + '>Keep updating</label>')
	.next().find('input') // click can't go on label or you get two clicks
	.click(function(e){
		onOff(e2.autoChat = this.checked);
		if (!e2.isChatterlight) e2.setCookie('autoChat', e2.autoChat ? '1' : '0');
		$('#message').focus();
	});

	// always update when focus is in chatterbox
	$(this)
	.focusin(function(){
		onOff(true); // do this even if autoChat is on, to restart inactivity countdown
		if (e2.getFocus(this) == 'autoChat') return; // don't check it or the click will uncheck it
		$('#autoChat', this)[0].checked = e2.autoChat; // is unchecked if autoChat has died
	})
	.focusout(function(){
		if (!e2.autoChat) onOff(false);
	});
});

if (e2.isChatterlight) e2('#message', e2(function(){
	$(this).css('width','').focus();
}).defer); // defer for IE 
