




// _*_ Mode: JavaScript; tab-width: 8; indent-tabs-mode: true _*_
; // $Id$

/*global setFirehoseAction firehose_get_updates tagsHideBody tagsShowBody
	firehose_fix_up_down firehose_toggle_tag_ui_to ajax_update json_handler
	json_update getFirehoseUpdateInterval
	firehose_play firehose_add_update_timerid firehose_collapse_entry
	vendorStoryPopup vendorStoryPopup2 firehose_save_tab check_logged_in
	firehose_set_cur firehose_style_switch
	firehose_style_switch_handler */

$.ajaxSetup({
	url:			'/ajax.pl',
	type:			'POST',
	contentType:	'application/x-www-form-urlencoded'
});

// Ensure our "namespaces" exist
window.Slash || (window.Slash = {});
Slash.Firehose || (Slash.Firehose = {});

var reskey_static = '';
var global_returnto = '';

// Global settings, but a firehose might use a local settings object instead
var firehose_settings = {
  startdate:		'',
  duration:			'',
  mode:				'',
  color:			'',
  orderby:			'',
  orderdir:			'',
  view:				'',
  viewtitle:		'',
  tab:				'',
  fhfilter:			'',
  base_filter:		'',
  user_view_uid:	'',

  issue:			'',
  is_embedded:		0,
  not_id:			0,
  section:			0,
  sectionname:		'',
  more_num:			0,
  metamod:			0,
  admin_filters:	0
};

var	firehose_sitename		= "",
	firehose_slogan			= "",
	firehose_smallscreen            = 0;

// Settings to port out of settings object
var	firehose_item_count		= 0,
	firehose_future			= null,
	firehose_more_increment	= 10;

// Globals we haven't yet decided to move into |firehose_settings|
var	fh_play					= 0,
	fh_is_timed_out			= 0,
	fh_update_timerids		= [],
	fh_is_admin				= 0,
	console_updating		= 0,
	fh_ticksize,
	fh_colors				= [],
	fh_idle_skin			= 0,
	vendor_popup_timerids	= [],
	vendor_popup_id			= 0,
	firehose_exists			= 0;

// Ads
var	fh_adTimerSecsMax   = 15,
	fh_adTimerClicksMax = 0,
	fh_adTimerUrl       = '';
	//fh_adTimerUrl		= '/images/iframe/firehose.html'; // testing

var FHID_PREFIX=/^(firehose|editor)-/;

var view;
(function(){ // function view( what, how ): smoothly, minimally scroll what entirely into view
// view(false) to stop all current and pending views()

// how.x|.y:	scroll only on the named axis
// how.hint:	calculate the goal as if: view(how.hint); view(what)
// how.speed=0:	scroll immediately to the goal, no animation (jQuery>=1.3)
// how.focus:	on scroll-complete, $(what).focus()

var $body, $html_body, el_q=[];
// el_q has a matching DOM element for each queued call to animate()
// el_q.length > 0 means a view() animation is in-progress, scrolling to reveal el_q[0].

function DOM_descendant( ancestor, descendant ){
	return $(descendant).eq(0).parents().index(ancestor)>=0;
}

function offset( el, b, how ){
	var $el=$(el), e=new Bounds($el);
	if ( !Bounds.empty(e) ) {
		if ( TypeOf.element($el[0]) ) {
			$.each({ top:-1, left:-1, bottom:1, right:1 }, function(edge, scale){
				e[edge] += scale*parseInt($el.css('margin-'+edge));
			});
		}

		if ( how.axis!='y' && !Bounds.contain(Bounds.x(b), e) ) {
			var dx = e.left<=b.left || b.width()<=e.width() ? e.left-b.left : e.right-b.right;
			b.left+=dx; b.right+=dx;
		}
		if ( how.axis!='x' && !Bounds.contain(Bounds.y(b), e) ) {
			var dy = e.top<=b.top || b.height()<=e.height() ? e.top-b.top : e.bottom-b.bottom;
			b.top+=dy; b.bottom+=dy;
		}
	}
	return b;
}

view = function( what, how ){
	var stop=(what===false), start=!stop, $el, el;
	if ( start ) {
		how || (how = {});
		'speed' in how || (how.speed = 'normal');
		if ($.browser.opera) { how.speed = 0 }

		$el=$any(what); el=$el[0];
		if ( Bounds.empty($el) ) {
			start = false;	// ...because we have no destination.
		} else if ( el_q.length && (!how.speed || !DOM_descendant(el_q[el_q.length-1], el)) ) {
			stop = true;	// ...because the new request is synchronous, or else unrelated to current/pending.
		}
	}

	if ( stop ) {	// All-stop.  Clear the animation queue.  Hopefully no one else is animating body.
		$html_body.stop(true);
		el_q.length=0;
	}

	if ( start ) {	// Queue a new animation; keep el_q synchronized with the 'fx' queue on body.
		el_q.push(el);
		$body.queue('fx', function(){
			var w=new Bounds(window);
			how.hint && !Bounds.empty($el) && offset(how.hint, w, how);
			offset($el, w, how);
			$html_body.animate({ scrollTop:w.top, scrollLeft:w.left }, how.speed, function(){
				how.focus && $el.focus();
				// Dequeue; keep el_q synchronized with the 'fx' queue on body.
				el_q.shift();
				$body.dequeue('fx');
			});
		});
	}

	return $el;
}

$(function(){
	$body=$('body');
	$html_body=$('html,body');
});
})();


function more_possible( text ){
	anchor_fh_pag_menu(true);
	$('#more-experiment a').trigger('more-possible');
}


function createPopup(pos_selector, titlebar, name, contents, message, onmouseout) {
	function div( kind, html ){
		return $('<div id="'+name+'-'+kind+'" class="popup-'+kind+'">'+(html||'')+'</div>');
	}

	var	pos	= Position(pos_selector),
		$popup	= $('<div id="'+name+'-popup" class="popup" style="position:absolute; top:'+pos.top+'px; left:'+pos.left+'px; z-index:100">').
				appendTo('body').
				append('<iframe>').
				append(div('title', titlebar)).
				append(div('contents', contents)).
				append(div('message', message));

	TypeOf.fn(onmouseout) && $popup.mouseleave(onmouseout);
	return $popup[0];
}

function createPopupButtons() {
	return '<span class="buttons"><span>' + $.makeArray(arguments).join('</span><span>') + '</span></span>';
}

function closePopup(id, refresh) {
	$any(id).remove();
	if (refresh) {
		window.location.reload();
	}
}

function handleEnter(ev, func, arg) {
	if (!ev) {
		ev = window.event;
	}
	var code = ev.which || ev.keyCode;
	if (code == 13) { // return/enter
		func(arg);
		ev.returnValue = true;
		return true;
	}
	ev.returnValue = false;
	return false;
}



function after_article_moved( article ){
	var data = article ? $(article).nextAll(':visible').andSelf() : null;
	$any('firehoselist').trigger('articlesMoved', data);
	anchor_fh_pag_menu(true);
}

function before_article_removed( article, if_also_trigger_moved ){
	var next_article = article ? $(article).next(':visible')[0] : null;
	$any('firehoselist').trigger('beforeArticleRemoved', article);
	if ( if_also_trigger_moved ) {
		after_article_moved(next_article);
	}
}

function firehose_toggle_advpref() {
	$any('fh_advprefs').toggleClass('hide');
}

function firehose_open_prefs() {
	$any('fh_advprefs').removeClass();
}

function toggleIntro(id, toggleid) {
	var new_class = 'condensed';
	var new_html = '[+]';
	if ( $any(id).setClass(applyMap('introhide', 'intro')).hasClass('intro') ) {
		new_class = 'expanded';
		new_html = '[-]';
	}
	$any(toggleid).setClass(new_class).html(new_html);
}


function tagsToggleStoryDiv(id, is_admin, type) {
	if ( $any('toggletags-body-'+id).hasClass('tagshide') ) {
		tagsShowBody(id, is_admin, '', type);
	} else {
		tagsHideBody(id);
	}
}

function tagsHideBody(id) {
	$any('toggletags-body-'+id).setClass('tagshide');	// Make the body of the tagbox vanish
	$any('tagbox-title-'+id).setClass('tagtitleclosed');	// Make the title of the tagbox change back to regular
	$any('tagbox-'+id).setClass('tags');			// Make the tagbox change back to regular.
	$any('toggletags-button-'+id).html('[+]');		// Toggle the button back.
	after_article_moved(elemAny('firehose-'+id));
}

function tagsShowBody(id, unused, newtagspreloadtext, type) {

	type = type || "stories";

	if (type == "firehose") {
		setFirehoseAction();
		if (fh_is_admin) {
			firehose_get_admin_extras(id);
		}
	}

	//alert("Tags show body / Type: " + type );
	$any('toggletags-button-'+id).html("[-]");		// Toggle the button to show the click was received
	$any('tagbox-'+id).setClass("tags");			// Make the tagbox change to the slashbox class
	$any('tagbox-title-'+id).setClass("tagtitleopen");	// Make the title of the tagbox change to white-on-green
	$any('toggletags-body-'+id).setClass("tagbody");	// Make the body of the tagbox visible
	after_article_moved(elemAny('firehose-'+id));
}

function tagsOpenAndEnter(id, tagname, unused, type) {
	// This does nothing if the body is already shown.
	tagsShowBody(id, unused, tagname, type);
}

function reportError(request) {
	// replace with something else
	alert("error");
}

//Firehose functions begin

function is_body_expanded( el ){
	return $(el).closest('.fhitem').is(':has(>[id^=fhbody-]:not(.empty,.hide))');
}

function toggle_fh_body_wrap_return( el ) {
	return firehose_settings.view==='stories' && is_body_expanded(el)
		|| toggle_firehose_body.apply(null, arguments) && false;
}

function toggle_firehose_body( el, unused, /*optional:*/toggle_to, dont_next ) {
	setFirehoseAction();

	var	$fhitem		= $(el).closest('.fhitem'),
		fhid		= $fhitem.attr('id').replace(FHID_PREFIX, ''),
		$body		= $fhitem.children('[id^=fhbody-]'),
		body_is_empty	= $body.is('.empty'),
		toggle_from	= sign(!body_is_empty && !$body.is('.hide') || -1);


	// normalize toggle_to to a number: toggle_to>0 => show, toggle_to==0 => toggle, toggle_to<0 => hide
	if ( toggle_to === false ) {
		// from boolean: true=>show, false=>hide
		toggle_to = -1;
	} else if ( typeof(toggle_to)==='string' ) {
		// from string: 'show'=>show, 'hide'=>hide, else toggle
		toggle_to = { show:1, hide:-1 }[toggle_to];
	}
	// from anything else, use sign(toggle_to); resolve cases that toggle now
	toggle_to = sign(toggle_to||-toggle_from);
	if ( toggle_to == toggle_from ) {
		return;
	}


	var showing = toggle_to>0;

	if ( body_is_empty ) {
		var handlers = {};
		fh_is_admin && (handlers.onComplete = function(){
			firehose_get_admin_extras(fhid);
		});
		ajax_update({ op:'firehose_fetch_text', id:fhid, reskey:reskey_static }, $body.attr('id'), handlers);
	} else if ( fh_is_admin && showing ) {
		firehose_get_admin_extras(fhid);
	}

	$body.	removeClass('body empty hide').
		addClass(showing ? 'body' : 'hide');

	$fhitem.removeClass('article briefarticle adminmode usermode').
		addClass((showing ? 'article ' : 'briefarticle ') + (fh_is_admin ? 'adminmode' : 'usermode'));

	if (showing) {
		view($fhitem, { speed:50 });
	}

	if (!dont_next && !showing && $fhitem.is('.currfh')) {
		firehose_go_next();
	}

	after_article_moved($fhitem);
	inlineAdFirehose(showing && $fhitem);
	return false;
}
toggle_firehose_body.SHOW	= 1;
toggle_firehose_body.TOGGLE	= 0;
toggle_firehose_body.HIDE	= -1;

function toggleFirehoseTagbox(id) {
	$any('fhtagbox-'+id).setClass(applyMap('tagbox', 'hide'));
	after_article_moved(elemAny('firehose-'+id));
}

function use_skin( link ){
	// Change the current "skin" of the page, e.g., to the CSS of the "Games" section.
	// Some sections don't have skins (e.g., "Slashdot", "Science"): link will be empty.
	// Otherwise, link is a <link type=text/css...> to install in <head> (if not already there).

	// Disable _all_ currently installed skins.
	var $installed_skins=$('head link.data-skin').attr('disabled', true), $link, $new_skin;
	// Now we're back down to the bare green metal of the Slashdot page.

	// If we are enabling a skin...
	if ( link ) {
		$link = $(link);

		// ...search the installed skins: we may already have it.
		$new_skin = $installed_skins.filter('[title=' + $link.attr('title') + ']');

		if ( !$new_skin.length ) {		// Not found; let's install it ourselves.
			$new_skin = $link.
				addClass('data-skin').	// Ensure we can _always_ search only for 'data-skin'.
				attr('disabled', true).	// Safari: if inserting new, must come in disabled.
				appendTo('head');		// Install it!
		}
		$new_skin.attr('disabled', false);
	}
}

function firehose_style_switch( section_id ){
	// If we've cached the skin-info in the section-metadata, use that.
	var	$item	= firehose_section_menu_item(section_id),
		section	= $item.length && $item.metadata();

	if ( 'skin' in section || section_id==='unsaved' ) {
		use_skin(section.skin);
		return;
	}

	// Otherwise, ask the server for the right skin.
	ajax_update({
		op: 'firehose_section_css',
		reskey: reskey_static,
		layout: 'yui',
		section: section_id
	}, '', {
		onComplete: function( xhr ){
			var json = eval_response(xhr)||{};
			use_skin(json.css_includes);
			section && (section.skin=json.css_includes);
		}
	});
}

var sprite_rules, use_sprites;
(function(){
function need_rule( $expr ){ return ($expr.css('background-image')||'none')==='none'; }
sprite_rules = function( rules ){
	var $test=$('<div style="display:none">').appendTo('body');
	(rules=core.grep(rules, function( classAttr ){
		return need_rule($test.attr('className', classAttr));
	}).join('\n')) && $('<style type="text/css">'+rules+'</style>').appendTo('head');
	$test.remove();
};
use_sprites = function( root ){
	$('div.maybe-sprite', root).each(function(){
		var $div=$(this).removeClass('maybe-sprite');
		$div.children('span.no-sprite').each(function(){
			need_rule($div) && $div.attr('style', $(this).text());
		}).remove();
	});
};
})();


(function(){ // Keep a class on the body corresponding to the current firehose view.
var ALL_VIEWS='stories-view recent-view popular-view daddypants-view search-view userhomepage-view';
function reflect( v ){ $('body').removeClass(ALL_VIEWS).addClass(v+'-view'); }

// Set the class initially on document ready.
$(function(){ reflect(firehose_settings.view); });

// Update the class when the view changes.
$(document).bind('firehose-setting-view', function( e, view ){ reflect(view); });
})();

(function(){
var	NEXT_ID=1, NONE={ id:-Infinity, rank:-Infinity, content:'' }, DISPLAYED=NONE, AVAILABLE={}, $DISPLAY,
	HINT_RE=/-(mode|warning|error)$/, RANK={ mode:2, warning:3, error:4 },
	CLASS_FOR_RANK=[ 'banner-rank', 'message-rank', 'mode-rank', 'warning-rank', 'error-rank' ],
	RANK_CLASSES=CLASS_FOR_RANK.join(' ');
function Message( o ){
	var id=NEXT_ID++, hint;
	(TypeOf.scalar(o) || o.content===void(0)) && (o={ content:o });
	hint = (HINT_RE.exec(o.key)||{})[1];
	$.extend(this, { rank:RANK[hint]||1, key:id, content:'' }, o, { id:id });
	return AVAILABLE[this.key]=this;
}
function display(){
	// Private.  Search through all available messages for the most important; update
	// the display accordingly.  Nothing fancy.  Expected worst-case is a handful of
	// messages: the base, some in-progress status, and an error.
	var best=NONE;
	core.each(AVAILABLE, function(){ (this.rank-best.rank || this.id-best.id)>0 && (best=this); });
	if ( best!==DISPLAYED ) {
		$DISPLAY.html((DISPLAYED=best).content);
		$('#firehose-message-tray').removeClass(RANK_CLASSES).addClass( CLASS_FOR_RANK[best.rank] );
	}
}
Slash.message = function( o ){
	// Public.  Show a new message (if it is important enough) in the firehose message-tray.
	// Call with the content you want to display: html (string), DOM element, or jQuery selection
	//	OR, an object with a content member (same rules as above) which also allows you to specify a key and/or rank
	// Returns the message key for use in Slash.clear_message.
	$DISPLAY || (DISPLAYED=new Message({ rank:0, key:'default', content:($DISPLAY=$('#firehose-message-tray')).children() }));
	return o && (o=new Message(o)) && (display(), o.key);
};
Slash.clear_message = function( key ){
	// Public.  Remove the message with the given key and update the display.
	try { delete AVAILABLE[key] && display(); } catch ( e ) { }
};
Slash.has_message = function( key ){
	return !!AVAILABLE[key];
};
})();


function addfhfilter(text) {
	if (has_hose()) {
		firehose_set_options('addfhfilter', text);
		return false;
	}
	return true;
}

function setfhfilter(text) {
	if (has_hose()) {
		firehose_set_options('setfhfilter', text);
		return false;
	}
	return true;
}

var firehose_set_options;
(function(){
var	loading_msg	= { key:'loading', content:'<span class="loading_msg">Loading New Items...</span>' },
	removes_all	= Qw.as_set('firehose_usermode mixedmode mode nocolors nothumbs section setfhfilter setsearchfilter tab view startdate issue'),
	start_over	= $.extend(Qw.as_set('startdate color addfhfilter'), removes_all),
	uses_setfield	= Qw.as_set('mixedmode nobylines nocolors nocommentcnt nodates nomarquee noslashboxes nothumbs'),
	sets_param	= $.extend(Qw.as_set('color duration issue pagesize pause startdate tab tabtype usermode'), uses_setfield),
	flags_param	= {	fhfilter:	'filterchanged',
				more_num:	'ask_more',
				section:	'sectionchanged',
				addfhfilter: 	'filterchanged',
				setfhfilter:	'filterchanged',
				setsearchfilter:'searchtriggered',
				tab:		'tabchanged',
				usermode:	'setusermode',
				view:		'viewchanged'
			},
	sets_directly	= Qw.as_set('color duration issue mode orderby orderdir section startdate tab view'),
	sets_indirectly	= {	setfhfilter:	'fhfilter',
				setsearchfilter:'fhfilter',
				tabsection:	'section'
			},
	resets_pagemore	= Qw.as_set('fhfilter view tab issue pagesize section setfhfilter setsearchfilter'),
	update_handlers	= {	onComplete: function(transport) {
					json_handler(transport);
					firehose_get_updates({ oneupdate: 1 });
				}
			};

// Grab a reference to #firehoselist as soon as possible...
var $fhl = $([]);	// ...but no sooner.
$(function(){ $fhl = $any('firehoselist'); });

function set_fhfilter_from( expr ){
	$(expr).each(function(){
		firehose_settings.fhfilter = this.value;
	});
}

function add_to_fhfilter(text) {
	var seen = {};

	var finaltext = $.map($.trim((firehose_settings.fhfilter||'') + ' ' + text).split(ws), function( term ){
		if ( !(term in seen) ) { return seen[term]=term; }
	}).join(' ')

	firehose_settings.fhfilter = finaltext;
	set_filter_inputs(finaltext);
}

function set_filter_inputs(text) {
	$('form[name=firehoseform] input[name=fhfilter], #searchquery').each(function(){
		this.value = text;
	});
}

firehose_set_options = function(name, value, context) {
	// Exit early for trouble.
	if(name==='color' && !value ) {
		return;
	}
	if (!logged_in && name == "color") {
		show_login_box();
		return;
	}

	// Perl thinks true and false are strings, so never let booleans get to the server.
	typeof(value)==='boolean' && (value = sign(value));


	// Set values in params and firehose_settings; mostly table-driven...
	var params={};

	// hardcoding because i am lazy and stupid -- pudge
	if (name==='setfhfilter searchfu') {
		name = 'setfhfilter';
		params.searchtriggered = 1;
	}

	uses_setfield[name]	&& (params.setfield = 1);
	sets_param[name]	&& (params[name] = value);
	flags_param[name]	&& (params[flags_param[name]] = 1);
	sets_directly[name]	&& (firehose_settings[name] = value);
	sets_indirectly[name]	&& (firehose_settings[sets_indirectly[name]] = value);
	resets_pagemore[name]	&& (firehose_settings.page = firehose_settings.more_num = 0);

	// ...and a few exceptions "by hand".
	switch ( name ) {
		case 'fhfilter':		set_fhfilter_from('form[name=firehoseform] input[name=fhfilter]'); break;
		case 'issue':			firehose_settings.startdate=value; firehose_settings.duration=1; break;
		case 'mode':			fh_view_mode=value; break;
		case 'tabsection':		params.tabtype='tabsection'; break;
		case 'view':			set_fhfilter_from('#searchquery'); break;
		case 'addfhfilter':		add_to_fhfilter(value); break;
	}

	if ( start_over[name] ) {
		view($('body'), { speed:0 });
		params.start_over = 1;
	}
	// We own #firehoselist and its contents; no need to pull _this_ UI code out into an event handler.
	if ( removes_all[name] ) {
		$('div.paginate').
			hide().
			addClass('paginatehidden');

		// Fade the list; and empty its contents / clear previous itemsreturned message
		$fhl.fadeOut().html('');
		$('#itemsreturned').html('');
		Slash.message(loading_msg);
	}


	// Tell the server (asynchronously).
	ajax_update($.extend({
			op:		'firehose_set_options',
			reskey:		reskey_static,
			setting_name:	name,
			context:	context,
			section:	firehose_settings.section
		}, params, firehose_settings),
		'', update_handlers
	);

	// Tell the UI.
	$(document).trigger('firehose-setting-' + name, value);

	// Note: when setting a new section, we don't actually know the new color, filter,
	// or view until we get the response.  The firehose_sections code, though, _does_
	// That code can bind to firehose-setting-section to trigger the component
	// firehose-setting-{color,view,setfhfilter} events that will update the UI.
};
})();

function firehose_fix_up_down( id, new_state ){
	// Find the (possibly) affected +/- capsule.
	var $updown = $any('updown-'+id);

	if ( $updown.length && ! $updown.hasClass(new_state) ) {
		// We found the capsule, and it's state needs to be fixed.
		$updown.setClass(new_state);
	}
}

(function(){
// Fix the nod/nix capsule when the server reports a changed vote.
var CLASS_FOR_VOTE={ nod:'votedup', metanod:'votedup', nix:'voteddown', metanix:'voteddown', none:'vote' };

$(document).bind('vote-assigned', function( event, vote ){
	firehose_fix_up_down($(event.target).attr('id').replace(FHID_PREFIX, ''), CLASS_FOR_VOTE[vote||'none']);
});
})();


function firehose_click_nodnix_reason( event ) {
	var	$fhitem	= $(event.target).closest('.fhitem'),
		fhid	= $fhitem.attr('id').replace(FHID_PREFIX, '');

	if ( (fh_is_admin || firehose_settings.metamod) && ($any('updown-'+fhid).is('.voteddown') || $fhitem.is('.fhitem-comment')) ) {
		firehose_collapse_entry(fhid);
	}

	return true;
}


function firehose_remove_tab(tabid) {
	setFirehoseAction();
	ajax_update({
		op:		'firehose_remove_tab',
		tabid:		tabid,
		reskey:		reskey_static,
		section:	firehose_settings.section
	}, '', { onComplete: json_handler });

}


//
// firehose + tag_ui
//

var $related_trigger = $().filter();

var kExpanded=true, kCollapsed=false;

function tag_ui_in( $fhitem ){
	var $toolbar=$fhitem.find('menu.edit-bar'), $twisty=$toolbar.find('a.edit-toggle span.button');
	return {
		$toolbar:	$toolbar,
		$input:		$toolbar.find('input.tag-entry'),
		$toggle:	$twisty,
		is_expanded:	$twisty.is('.expand')
	};
}

function firehose_toggle_tag_ui_to( want_expanded, el, dont_next ){
	var	$fhitem		= $(el).closest('.fhitem'), // assert($fhitem.length)
		fhid		= $fhitem.attr('id').replace(FHID_PREFIX, ''),
		tag_ui		= tag_ui_in($fhitem),
		toggle		= tag_ui.is_expanded == !want_expanded; // force boolean conversion

	if ( toggle ) {
		if (want_expanded) { // need to expand
			if ($fhitem.find('div[id^=fhbody-]').is('.empty,.hide')) {
				toggle_firehose_body($fhitem, 0, true, dont_next);
				$fhitem.data('tags-opened-body', true);
			}
		}

		setFirehoseAction();
		want_expanded && Tags.fetch($fhitem[0]);

		tag_ui.$toolbar.toggleClass('expanded', !!want_expanded);
		tag_ui.$input.toggle(!!want_expanded);
		tag_ui.$toggle.setClass(applyToggle({
			expand:	want_expanded,
			collapse: !want_expanded
		}));

		$fhitem.find('#toggletags-body-'+fhid).setClass(applyToggle({tagbody:want_expanded, tagshide:!want_expanded}));

		if (!want_expanded && $fhitem.data('tags-opened-body')) { // is expanded, and parent was expanded by us
			toggle_firehose_body($fhitem, 0, false);
			$fhitem.removeData('tags-opened-body');
		}

		after_article_moved($fhitem[0]);
	}

	// always focus for expand request, even if already expanded
	want_expanded && view(tag_ui.$input, { hint:$fhitem, focus:true, speed:50 });
	return tag_ui.$toolbar;
}

function firehose_toggle_tag_ui( el ) {
	var $fhitem = $(el).closest('.fhitem');
	firehose_toggle_tag_ui_to(!tag_ui_in($fhitem).is_expanded, $fhitem);
}

var search_eligible;
(function(){
var context_search_eligible = {
	user:	true,
	top:	true,
	system:	true
};
search_eligible = function( tag_el ){
	var $li		= $(tag_el).closest('li'),
		context	= $li.closest('span.tag-display').attr('context') || 'unknown';

	return context_search_eligible[context] && $li.is(':not(.p,.w,.b,.suggestion)');
};
})();

function user_intent( intent, data ){
	intent && $(document).trigger('user-intent-'+intent, data);
}

function shift_select( el ){
	var	$fhitems	= $('div.fhitem:visible'),
		id		= $(el).closest('.fhitem').attr('id'),
		$bounds		= $('div.currfh,#'+id),
		start_idx	= $fhitems.index($bounds[0]),
		stop_idx	= $fhitems.index($bounds[ $bounds.length-1 ]);

	$fhitems.slice(start_idx, stop_idx+1).addClass('currfh');
}

function apply_tags( item, tags, shift_key ){
	var $target=$(item);
	fh_is_admin && $target.is('.currfh') && shift_key && ($target = $('div.fhitem.currfh:visible'));
	$target.each(function(){ Tags.submit(this, tags); });
}

$(function(){
var PREFIX=/^user-intent-/;
function interest( event, item, original_event ){
	if ( !item ) {
		// nothing to do
	} else if ( !fh_is_admin || !original_event || !original_event.shiftKey ) {
		firehose_set_cur($(item), (event.type||'').replace(PREFIX, ''));
	} else {
		shift_select(item);
	}

	setTimeout(function(){ inlineAdFirehose(); }, 0);
}
$(document).
	bind('user-intent-interest', interest).
	bind('user-intent-control', interest);
});

function page_click_handler( event ) {
	var	$target	= $(event.target),
		$fhitem	= $target.closest('.fhitem'),
		leaving	= !!$target.closest('a[href]:not([href=#],[onclick],[rel=tag]),.advertisement').length,
		control	= !leaving && !!$target.closest('menu,a,[data-intent-control]').length,
		intent	= !leaving && (control ? 'control' : 'interest'),
		command, click_handled = false;

	$related_trigger = $target;

	if ( $target.is('a.up') ) {
		command = 'nod';
	} else if ( $target.is('a.down') ) {
		command = 'nix';
	} else if ( $target.is('.sodify,.skin,.topic *') ) {
		// mostly already handled by its own click-handler, but...
		intent = 'search';
	} else {
		$related_trigger = $([]);
	}

	if ( leaving ) {
		user_intent(intent);
	} else {
		user_intent(intent, [$fhitem[0], event]);
		// _any_ click can trigger, but click-specific ad will win
		setTimeout(function(){ inlineAdFirehose(); }, 0);
	}

	if ( command && (click_handled=true) && check_logged_in() ) {
		apply_tags($fhitem[0], command, event.shiftKey);
	}

	return !click_handled;
}


function firehose_handle_nodnix( commands ){
	if ( commands.length ) {
		var fhitem=this;
		$.each(commands.slice(0).reverse(), function(i, cmd){
			if ( cmd=='nod' || cmd=='nix' ) {
				firehose_fix_up_down(
					fhitem.getAttribute('data-fhid'),
					{ nod:'votedup', nix:'voteddown' }[cmd] );
				return false;
			}
		});
	}

	return commands;
}

function firehose_handle_comment_nodnix( commands ){
	if ( commands.length ) {
		var fhitem=this, handled_underlying=false;
		commands = $.map(commands.reverse(), function( cmd ){
			var match = /^([\-!]*)(nod|nix)$/.exec(cmd);
			if ( match ) {
				var modifier = match[1], vote = match[2];
				cmd = modifier + 'meta' + vote;
				if ( !handled_underlying && !modifier ) {
					var id = fhitem.getAttribute('data-fhid');
					firehose_fix_up_down(
						id,
						{ nod:'votedup', nix:'voteddown' }[vote] );
					firehose_collapse_entry(id);
					handled_underlying = true;
				}
			}
			return cmd;
		}).reverse();
	}

	return commands;
}


$(function(){
var $FHL=$any('firehoselist');


//
// Page initialization.
//

$.browser.chrome = $.browser.safari && /chrome/.test( navigator.userAgent.toLowerCase() );

$('#fhsearch').show();

if (!firehose_smallscreen) {
	var $roots = $('div.fhroot');
	($roots.length ? $roots : $('div.article')).click(page_click_handler);
}

// .live() binds these handlers to all current _and_ future firehose items
$('#firehoselist > div.fhitem').
	live('blur-article', function(){
		var $fhitem = $(this);
		if ( $fhitem.data('blur-closes-item') ) {		toggle_firehose_body($fhitem, 0, false, true);
		} else if ( $fhitem.data('blur-closes-tags') ) {	firehose_toggle_tag_ui_to(false, $fhitem, true);
		}
		// optional, will focus before next blur
		$fhitem.removeData('blur-closes-item').
			removeData('blur-closes-tags');
	}).
	live('focus-article', function(){
		var $fhitem = $(this);
		$fhitem.data('blur-closes-tags', !tag_ui_in($fhitem).is_expanded).
			data('blur-closes-item', $fhitem.find('[id^=fhbody-]').is('.empty,.hide'));
	});

$('#firehoselist a.more').
	live('mousedown', function(){ // pos_logger
		var $item=$(this).closest('.fhitem'), pos=$FHL.children().index($item)+1;
		this.href += (this.search ? '&' : '?') + 'art_pos=' + pos;
		return true;
	});

	anchor_fh_pag_menu(true);
	$(window).bind('resize', shorten_fh_pag_menu);
	if (!$.browser.msie) {
		$(window).bind('scroll', anchor_fh_pag_menu);
	}

});
// firehose functions end

// helper functions
function ajax_update(request_params, id, handlers, options) {
	// make an ajax request to request_url with request_params, on success,
	//  update the innerHTML of the element with id
	if ( !options ) {
		options = {};
	}

	var opts = {
		data: request_params
	};

	if ( options.request_url ) {
		opts.url = options.request_url;
	}

	if ( options.async_off ) {
		opts.async = false;
	}

	if ( id ) {
		opts.success = function(html){
			$any(id).html(html);
		};
	}

	if ( handlers && handlers.onComplete ) {
		opts.complete = handlers.onComplete;
	}

	if ( handlers && handlers.onError ) {
		opts.error = handlers.onError;
	}

	jQuery.ajax(opts);
}

function ajax_periodic_update(interval_in_seconds, request_params, id, handlers, options) {
	setInterval(function(){
		ajax_update(request_params, id, handlers, options);
	}, interval_in_seconds*1000);
}

function eval_response( xhr ) {
	return evalExpr(xhr.responseText);
}

function json_handler(xhr) {
	var response = eval_response(xhr);
	response && json_update(response);
	return response;
}

function json_update(response) {
	if ( ! response ) {
		return;
	}

	$.globalEval(response.eval_first);

	// Server says:

	// ...replace the content of these elements
	$.each(response.html||[], function(elem_id, new_html){
		$any(elem_id).
			html(new_html);
	});

	// ...set new values in these elements
	$.each(response.value||[], function(elem_id, new_value){
		$any(elem_id).
			each(function(){
				if ( this !== gFocusedText ) {
					$(this).val(new_value);
				}
			});
	});

	// ...append content to these elements
	$.each(response.html_append||[], function(elem_id, new_html){
		$any(elem_id).
			append(new_html);
	});

	// ...append content after these elements
	$.each(response.html_add_after||[], function(elem_id, add_html){
		$any(elem_id).
			after(add_html);
	});

	// ...append content after these elements
	$.each(response.html_add_before||[], function(elem_id, add_html){
		$any(elem_id).
			before(add_html);
	});




	// ...replace with new content (and display it)
	$.each(response.html_replace||[], function(elem_id, new_html) {
		$any(elem_id).replaceWith(new_html).show();
	});

	// ...replace the specially marked "tail-end" (or else append) content of these elements
	$.each(response.html_append_substr||[], function(elem_id, new_html){
		$any(elem_id).
			each(function(){
				var	$this		= $(this),
					old_html	= $this.html(),
					truncate_at	= old_html.search(/<span class="?substr"?> ?<\/span>[\s\S]*$/i);
				if ( truncate_at != -1 ) {
					old_html = old_html.substr(0, truncate_at);
				}
				$this.html(old_html + new_html);
			});
	});

	// ...trigger events on these elements (do this last to include any content added above)
	$.each(response.events||[], function(){
		if ( this.event ) {
			$(this.target||document).trigger(this.event, this.data);
		}
	});

	$.globalEval(response.eval_last);
}


function adsToggle(val) {
	var params = {};
	params.op = 'enable_maker_adless';
	if (!val) {
		params.off = 1;
	}
	params.reskey = reskey_static;
	ajax_update(params, '', { onComplete: json_handler });

}

var update_firehose_content;
(function(){
var	MARK_ADDING			= 'data-add-ready',
	MARK_REMOVING		= 'data-remove-ready',

	MAX_OFFSCREEN_CHUNK	= 5,

	CHANGES_RE			= /\bdata-(add|remove)-ready\b/;


var	D=document, U=void(0), $FHL, FHL;
$(function(){
	$FHL = $any('firehoselist'); FHL=$FHL[0];
});



function Run(){ return this; }
Run.prototype = {
	head:	function(){ return this._run[ 0 ]; },
	lhead:	function(){ return this.head(); },
	headId:	function(){ return (this.lhead()||{}).id; },

	tail: function(){
		var len = this._run && this._run.length;
		return len && this._run[ len-1 ];
	},
	ltail:	function(){ return this.tail(); },
	tailId:	function(){ return (this.ltail()||{}).id; },

	_manip: function( parent, next ){
		var el = next;
		while ( el && el.nodeType!==1 ) {
			el = el.nextSibling;
		}
		if ( !el ) {
			this.appendTo(parent);
		} else if ( el !== this.head() ) {
			this.insertBefore(next);
		}
		return this;
	},

	prependTo: function( parent ){
		return this._manip(parent, parent.firstChild);
	},

	insertBefore: function( next ){
		this.tail().nextSibling!==next && $(this._run).insertBefore(next);
		return this;
	},

	insertAfter: function( prev ){
		return this._manip(prev.parentNode, prev.nextSibling);
	},

	appendTo: function( parent ){
		var tail=this.tail();
		(tail.parentNode!==parent || tail.nextSibling) && $(this._run).appendTo(parent);
		return this;
	}
};


function DocumentRun(){
	this._run = [];
	return this;
};
DocumentRun.prototype = $.extend(new Run, {
	lhead: function(){ return this._lhead; },
	ltail: function(){ return this._ltail; },

	push: function( el, logical ) {
		this._run.push(el);
		if ( logical ) {
			this._lhead || (this._lhead = el);
			this._ltail = el;
		}
		return this;
	}
});


function DocumentFragmentRun(){
	this._fragment = D.createDocumentFragment();
	this._run = this._fragment.childNodes;
	return this;
}
DocumentFragmentRun.prototype = $.extend(new Run, {
	push: function( el ){
		this._fragment.appendChild(el);
		return this;
	},

	insertBefore:	function( next ){ next.parentNode.insertBefore(this._fragment, next); },
	insertLast:		function( parent ){ parent.appendChild(this._fragment); }
});



function insert_runs_after( prev_run, runs ){
	// |prev_run| is already in place.  While we can find a run in |runs| that matches
	// the current tail: insert that new run in place, now match against _that_ tail.

	var next_run, tail_id, after_el;
	while ( prev_run && (tail_id=prev_run.tailId()) && (next_run=runs[tail_id]) ) {
		next_run.insertAfter(prev_run.tail())
		prev_run = next_run;
	}
}


function prepare( html ){
	return $(html).addClass(MARK_ADDING).css('display', 'none')[0];
}

update_firehose_content = function( updates, sequence ){
	// Assert: FHL, $FHL.length

	// |updates| is a collection of instructions for adding and removing items.
	// |sequence| is an ordered list of ids (well... id fragments anyway) representing
	// the desired final order of the actual items within the hose.

	// This function adds new, removes old (well... marks for later removal), and
	// re-orders existing items to conform to |sequence|.  We only add items here.
	// Animation and removal are handled by <FIXME: what routine animates?>.

	if ( !(updates && updates.length || sequence && sequence.length) ) {
		return;
	}
	// There are (probably content) changes: warn anyone who cares.
	Slash.busy('firehose-content', true);



	// Algorithm:

	// An item mentioned in |sequence| is "out-of-order" if it doesn't immediately
	// follow (ignoring items _not_ mentioned) the thing |sequence| says it should. A
	// "run" is a sequence of in-order items; a maximal run obviously starts with an
	// out-of-order item (or else the very first item in the hose).  Reorder the whole
	// thing by matching run-heads to the right tails.

	// We build a dictionary of maximal runs, |loose_runs|, indexed by the ids of the
	// tails they need to follow.  Roll insertion of new items into the reorder by
	// providing those new items in runs.



	var adding={};
	var removing_sx = $.map(updates, function( update ){	// ...for each entry in |updates|.
		var op=update[0], fhid=update[1], html=update[2];
		switch ( !!fhid && op ) {
			case 'remove':	return '#firehose-'+fhid;		// Assemble a selector for removals.
			case 'add':		adding[fhid] = prepare(html);	// Convert HTML into actual elements, for additions.
		}
	}).join(',');

	// Mark all the removals... they belong to the animator now.
	$(removing_sx, FHL).addClass(MARK_REMOVING);


	// Collect runs of items-to-be-added (saved in |loose_runs|, as described above).
	var loose_runs={}, run, elid_before={}, prev_elid=0;
	$.each(sequence, function( i, fhid ){
		// Does this position in |sequence| refer to an item being added?
		var item = adding[fhid];
		if ( item ) {	// Yes.  Push the addition onto the current run (creating if need be).
			run || (run = loose_runs[prev_elid] = new DocumentFragmentRun());
			run.push(item);
		} else {		// No.  Close the current run, if any.
			run = U;
		}

		// Build a map: element-id => the element-id of the preceding item, so later we can
		// match heads to tails.
		var elid = 'firehose-'+fhid;
		elid_before[elid] = prev_elid;
		prev_elid = elid;
	});


	// Collect maximal runs in the current contents of the hose (again, saved in |loose_runs|).
	var	i=0, $fhitems=$FHL.children(), el=$fhitems[i], sequence_known=el && el.id in elid_before,

		// Unfortunately, the i2 ad complicates things.  Re-inserting it will unavoidably
		// re-runs its scripts.  Therefor, the run that contains the i2 ad must not be moved.
		i2ad_pos=$fhitems.index($('#floating-slashbox-ad', FHL)), fixed_run;

	i2ad_pos<0 && (i2ad_pos=Infinity);

	while ( el ){
		run = new DocumentRun();
		prev_elid = U;
		do {
			sequence_known && (prev_elid = el.id);
			run.push(el, sequence_known);
		} while ( (el=$fhitems[++i]) && (!(sequence_known=el.id in elid_before) || prev_elid===U || elid_before[el.id]===prev_elid) );

		// Does the run we just built contain the i2 ad?
		if ( i > i2ad_pos ) {	// Yes.
			fixed_run = run;			// This run must not be moved.
			i2ad_pos = Infinity;		// No other run can contain the i2 ad.
		} else {				// No.  It may be re-ordered if necessary.
			loose_runs[ elid_before[run.headId()] ] = run;
		}
	}

	// Reorder existing runs, insert new runs.
	(run=loose_runs[0])	&& insert_runs_after(run.prependTo(FHL), loose_runs);
	fixed_run			&& insert_runs_after(fixed_run, loose_runs);

	// Fix-up thumbnails and topic icons
	use_sprites(FHL);

	// Our work here is done, old chum.  Let us away to the Bat Cave.
	Slash.busy('firehose-content', false);
}
})();


(function(){
var INIT_INTERVAL=400, $fhroots, timer;

function step(){
	var $items = firehose_init_note_flags(1);
	if ( !$item.length ) {
		clearInterval(timer);
		timer = 0;
	}
}

fh_is_admin && $(document).bind('firehose-content-end', function(){
	timer || (timer=setInterval(step, INIT_INTERVAL));
});

$(function(){ $fhroots=$('div.fhroot'); });
})();


function user_wants_updates( when ){
	setFirehoseAction();

	apply_updates();
	Slash.clear_message('updates-available');

	when && check_logged_in() && apply_updates_when(when);
}

var apply_updates_when, apply_updates, updates_available, debug_ask, slashCMfn;
(function(){
var	$FHL,
	INSERT_SX='div.data-add-ready', REMOVE_SX='div.data-remove-ready:not(.currfh)',
	NEW_SX='div.fhitem.data-add-ready:hidden', OLD_SX='div.fhitem:not(.data-add-ready,.data-remove-ready:not(.currfh)):last',
	APPLY_WHEN='at-end',
	NUMBERS=[ 'No', 'A', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine' ];

apply_updates_when = function( when, init ){
	APPLY_WHEN = when;
	init || ajax_update({ // save pref
		op:			'firehose_save_autoupdate',
		reskey:		reskey_static,
		autoupdate:	when
	});
};

apply_updates = function( $insert, $remove ){
	$insert!==false && (TypeOf.list($insert) ? $insert.filter(INSERT_SX) : $FHL.children(INSERT_SX)).removeClass('data-add-ready').css('display', '');
	$remove!==false && (TypeOf.list($remove) ? $remove.filter(REMOVE_SX) : $FHL.children(REMOVE_SX)).remove();

	firehose_future && firehose_update_title_count(
		firehose_storyfuture(firehose_future).filter(':visible').length
	);
};

function ask( about ){
	var	$items	= TypeOf.list(about) ? about : $([]),
		n		= $items.length || about/1,
		how, kind;

	if ( n ) {
		how = $FHL.children(NEW_SX+':last').prevAll('div.fhitem:not(.data-add-ready)').length ? [' more ', ' available.'] : [' new ', ' ready.'];
		kind = $items.is(':not(.fhitem-story)') ? ['item is', 'items are'] : ['story is ', 'stories are'];

		Slash.message({
			key: 'updates-available',
			content:
'<p>'+
	'<a href="#" onclick="user_wants_updates(); return false" title="update now">'+
		(NUMBERS[n]||n) + how[0] + kind[ (n!==1)/1 ] + how[1] +
	'</a>'+
	'  '+
	'<a href="#" onclick="user_wants_updates(\'always\'); return false" title="always update, never ask">'+
		'(Automatically Update)'+
	'</a>'+
'</p>'
		});
	}
}

updates_available = function( n ){
	var $last, $tail=true, $remaining;

	APPLY_WHEN==='at-end' && ($last=$FHL.children(OLD_SX)).length && ($tail=$last.nextAll());
	APPLY_WHEN!=='never' && apply_updates($tail, $tail);

	($remaining=$FHL.children(NEW_SX)).length && ask($remaining);
};

$(function(){ $FHL=$any('firehoselist'); });
})();


function firehose_handle_update( updates, sequence ) {

	var	saved_selection		= new $.TextSelection(gFocusedText),
		$menu				= $('div.ac_results:visible'),
		$fhl				= $any('firehoselist'),
		add_behind_scenes	= Slash.has_message('loading');

	add_behind_scenes && $fhl.hide();

	update_firehose_content(updates, sequence);

	if ( add_behind_scenes ) {
		apply_updates();
		$fhl.show().css({ opacity:'' });
		$('div.paginate').show().removeClass('paginatehidden');
		anchor_fh_pag_menu(true);
		Slash.clear_message('loading');
	} else {
		updates_available();
	}
	Slash.busy('firehose-update', false);

	firehose_add_update_timerid(setTimeout(firehose_get_updates, getFirehoseUpdateInterval()));

	saved_selection.restore().focus();
	$menu.show();
}

function firehose_storyfuture( future ){
	// Select all articles in #firehoselist.  Update .story|.future as needed.  Return the complete list.

	var if_not=['h3.future', 'h3.story'], class_if=['story', 'future'];
	return $('div.fhroot>div.fhitem').
		each(function(){
			var is_future = sign(future[this.id.replace(FHID_PREFIX, '')]);
			$(this).find(if_not[is_future]).attr('className', class_if[is_future]);
		});
}

function firehose_update_title_count(num) {
	var newtitle;
	var end;
	var sectionname = "";
	if (firehose_settings.sectionname != "Main") {
		sectionname = " " + firehose_settings.sectionname;
	}
	if (!num) {
		num = $('div.fhroot>div.fhitem').length;
	}
	if (num > 0) {
		end =  " ("  + num + ")";
	} else {
		end = " " + firehose_slogan;
	}
	newtitle = firehose_sitename + sectionname + " " + firehose_settings.viewtitle + end;
	document.title = newtitle;
}

(function(){
var $D=$(document), $B, depth={}, ROOT_RE=/^[^-]+(?=-)/, EVENT=[ '-end', '-begin' ];

//
// Slash.busy --- mark <body> with a class, e.g., 'busy-x', within the range you
//	declare x "busy": busy('x', true)...busy('x', false).  Ranges nest;
//	Slash.busy maintains a logical "busy-depth" per key.
//
function busy( k, more, for_root ){
	var N=depth[k]||0, was_busy=N>0, now_busy; // N guards against depth[k]===undefined.

	// busy(k) is a "getter"
	if ( arguments.length > 1 ) {
		// busy(k, expr) is a (relative) "setter".  Let's deduce the delta...
		if ( TypeOf.number(more)==='number' ) {	// busy(k, number) means depth[k]+=number, except...
			more===0 && (more = -N);	// ...busy(k, 0) means "reset"
		} else {						// For non-numbers (including 'Nan', 'Infinity' --- thank you, TypeOf)
			more = sign(more) || -1;	// busy(k, expr) means ++depth[k] or --depth[k]
		}
		(N+=more) ? depth[k]=N : delete depth[k];
		now_busy = N>0;
		Slash.markBusy(k, now_busy);	// Physical state may differ from logical, so let markBusy decide.

		!for_root && was_busy!==now_busy && $D.trigger(k+EVENT[sign(now_busy)]);
	}
	return was_busy; // Return previous "logical" state: old depth[k] > 0.
}

Slash.busy = function( k, more ){
	var was_busy=busy.apply(null, arguments), m;
	arguments.length>1 && (m=ROOT_RE.exec(k)) && busy(m[0], more, true);
	return was_busy;
};

//
// Slash.markBusy --- ignore the logical depth maintained by Slash.busy, e.g.,
//	when your calls to busy(..., true) and busy(..., false) don't balance.
//
Slash.markBusy = function( k, state ){
	var	was_busy = ($B||($B=$('body'))).is('.busy-'+k),
		now_busy = !!state || arguments.length<2 && depth[k]>0; // markBusy(k) resets mark to depth[k]>0.
	was_busy!==now_busy && $B.toggleClass('busy-'+k);
	return was_busy; // Return previous "physical" state: body had class "busy-"+k.
};

})();

$(function(){
	$(document).
		ajaxStart(function(){ Slash.markBusy('ajax', true); }).
		ajaxStop(function(){ Slash.markBusy('ajax', false); });
});

function dynamic_blocks_list() {
        var boxes = $('#slashboxes div.title').
                map(function(){
                        return this.id.slice(0,-6);
                }).
                get().
                join(',');

                return boxes;
}

function dynamic_blocks_update(blocks) {
	$.each(blocks, function( k, v ) {
		if (k === 'userbio_self') {
			$('#' + k).html(v.block);
		} else {
			$('#'+k+'-title h4').replaceWith(
				'<h4>' + (v.url ? '<a href="'+v.url+'">'+v.title+'</a>' : v.title) + '<span class="closebox">x</span></h4>'
			);
			v.block && $any(k+'-content').html(v.block);
		}
	});
}

function dynamic_blocks_delete_message(val, type) {
	var params = {};
	params.op = 'dynamic_blocks_delete_message';
	params.val = val;
	params.reskey = reskey_static;
	if (type === 'user_bio_messages') {
		params.user_bio_messages = 1;
		params.strip_list = 1;
	}
	ajax_update(
		params,
		'',
		{
			onComplete: function(transport) {
				var response = eval_response(transport);
				var block_content = '';
				if (response != undefined) {
					block_content = response.block;
				}
				$('#userbio_self-messages').html(block_content);
				if ((block_content === '') || (response === undefined)) {
					$('#userbio_self-messages-begin').hide();
				}
			}
		}
	);
}

function firehose_toggle_picker_search() {
	var params = {};
	params.op = 'firehose_toggle_picker_search';
	params.reskey = reskey_static;
	ajax_update(
		params,
		'',
		{
			onComplete: function() {
				$('#fh_filtercontrol_toggle').hide();
				$('#fh_picker_search').show();
				$('#hd').removeClass('nofilter');
				$('#fh_simpledesign_toggle').show();
			}
		}
	);
}

function firehose_toggle_smallscreen_mode(force_ss, is_anon) {
	if (force_ss) {
		var uri = document.location.search;
		var base = document.location.href.replace(/\?.*/, '');

		if (is_anon == 1) {
			if (uri.match("ss=1")) {
				uri = uri.replace(/ss=1/, "ss=0");
			} else {
				if (uri.match(/\?/)) {
					uri = uri + '&ss=0';
				} else {
					uri = '?ss=0';
				}
			}
		} else {
			uri = uri.replace(/\&?ss=1/, "");

			// This was the only param
			if (uri === '?') {
				uri = '';
			}

			// Prevent malformed URI
			uri = uri.replace(/^\?\&/, '?');
		}

		 document.location = (base + uri);
	} else {
		var params = {};
		params.op = 'firehose_toggle_smallscreen_mode';
		params.reskey = reskey_static;
		ajax_update(
			params,
			'',
			{
				onComplete: function() {
					document.location=document.URL;
				}
			}
		);
	}
}

function firehose_toggle_simpledesign_mode(force_sd, is_anon) {
	if (force_sd) {
		var uri = document.location.search;
		var base = document.location.href.replace(/\?.*/,'');
		if (is_anon==1) {
			if (uri.match("sd=1")) {
				uri = uri.replace(/sd=1/,"sd=0");
			} else {
				if (uri.match(/\?/)) {
					uri = uri+'&sd=0';
				} else {
					uri = '?sd=0';
				}
			}
		} else {
			uri = uri.replace(/\&?sd=1/,"");
				if (uri === '?') {
					uri = '';
				}
				uri = uri.replace(/^\?\&/,'?');
		}

		document.location = (base + uri);
	} else {
		var params = {};
		params.op = 'firehose_toggle_simpledesign_mode';
		params.reskey = reskey_static;
		ajax_update(
			params,
			'',
			{
				onComplete: function() {
					document.location = document.URL;
				}
			}
		);
	}
}

function setSlashCMCallback( callback ) {
	slashCMfn = callback;
}

function slashCM(params) {
	if( slashCMfn ) {
		slashCMfn(params);
	}
}

function firehose_get_updates_handler(transport) {
	var response = eval_response(transport);
	if ( !response ){
		return;
	}

	firehose_future = response.future;

	var updated_tags = response.update_data.updated_tags;
	updated_tags && $.each(updated_tags, function( id, content ){
		$('#tagbar-'+id).html(content);
	});

	response.dynamic_blocks && dynamic_blocks_update(response.dynamic_blocks);
	response.html           && json_update(response);
	response.sprite_rules   && sprite_rules(response.sprite_rules);
	response.updates        && firehose_handle_update(response.updates, response.ordered);

	if (firehose_settings.updateTypeCM) {
		slashCM({
			updateType:  firehose_settings.updateTypeCM,
			updateTerms: (firehose_settings.updateTermsCM || ''),
			updateNum:   (response.count || 0)
		});
		firehose_settings.updateTypeCM = firehose_settings.updateTermsCM = '';
	}
}

function firehose_get_item_idstring() {
	return $('#firehoselist > [id]').map(function(){
		return this.id.replace(/firehose-(\S+)/, '$1');
	}).get().join(',');
}


function firehose_get_updates(options) {
	options = options || {};
	if ((fh_play === 0 && !options.oneupdate) || Slash.busy('firehose-ajax')) {
		firehose_add_update_timerid(setTimeout(firehose_get_updates, 2000));
		return;
	}
	if (fh_update_timerids.length > 0) {
		var id = 0;
		while((id = fh_update_timerids.pop())) { clearTimeout(id); }
	}

	Slash.busy('firehose-update', true);
	Slash.busy('firehose-ajax', true);
	ajax_update($.extend({
		op:		'firehose_get_updates',
		ids:		firehose_get_item_idstring(),
		updatetime:	update_time,
		fh_pageval:	firehose_settings.pageval,
		embed:		firehose_settings.is_embedded,
		dynamic_blocks:	dynamic_blocks_list()
	}, firehose_settings), '', {

		onComplete:	function( transport ){
			Slash.busy('firehose-ajax', false);
			firehose_get_updates_handler(transport);
		},

		onError:	firehose_updates_error_handler
	});
}

function firehose_updates_error_handler( xhr, status ) {
	Slash.busy('firehose-update', false);
	fh_is_admin && Slash.message({
		key:		'update-error',
		content:	'<p><a href="#" onclick="firehose_reinit_updates()">[Admin] Slashdot update failed'
						+ (status && status!=='error' ? ' ("'+status+'")' : '')
						+ '.  Click to retry.</a></p>'
	});
}

function setFirehoseAction() {
	var thedate = new Date();
	var newtime = thedate.getTime();
	firehose_action_time = newtime;
	if (fh_is_timed_out) {
		fh_is_timed_out = 0;
		firehose_play();
		firehose_get_updates();
		if (console_updating) {
			console_update(1, 0);
		}
	}
}

function getSecsSinceLastFirehoseAction() {
	var thedate = new Date();
	var newtime = thedate.getTime();
	var diff = (newtime - firehose_action_time) / 1000;
	return diff;
}

function getFirehoseUpdateInterval() {
	var update_speed = 1;
	if (firehose_settings.view == "daddypants" || firehose_settings.view == "recent") {
		update_speed = 2;
	}

	var interval = update_speed == 2 ? 45000 : 1200000;
	if (updateIntervalType == 1) {
		interval = update_speed == 2 ? 30000 : 800000;
	}

	if (update_speed == 2) {
		interval = interval + (5 * interval * getSecsSinceLastFirehoseAction() / inactivity_timeout);
		if (getSecsSinceLastFirehoseAction() > inactivity_timeout) {
			interval = 3600000;
		}
	} else {
		interval = 1200000;
		if (getSecsSinceLastFirehoseAction() > 7200) {
			interval = 1800000;
		} else if (getSecsSinceLastFirehoseAction() > 10800) {
			interval = 3600000;
		}
	}

	return interval;
}

function start_up_hose() {
	firehose_set_options('pause', false);
}

function firehose_play(context) {
	fh_play = 1;
	var wait = 0;
	if (context && context == "init") {
		wait = getFirehoseUpdateInterval();
	}

	setFirehoseAction();
	if (context && context == "init") {
		setTimeout(start_up_hose, wait);
	} else {
		firehose_set_options('pause', false, context);
	}

	$any('message_area').html('');
	$any('pauseorplay').html('Updated');
	$any('play').setClass('hide');
	$any('pause').setClass('show');
}

function firehose_pause(context) {
	fh_play = 0;
	$any('pause').setClass('hide');
	$any('play').setClass('show');
	$any('pauseorplay').html('Paused');
	firehose_set_options('pause', true, context);
}

function firehose_add_update_timerid(timerid) {
	fh_update_timerids.push(timerid);
}

function firehose_collapse_entry(id) {
	$('#firehoselist > #firehose-'+id).
		find('#fhbody-'+id+'.body').
			setClass('hide').
		end().
		removeClass('article').
		addClass('briefarticle');

	tagsHideBody(id);
}

function firehose_remove_entry(id) {
	$('#firehose-'+id).animate({ height: 0, opacity: 0 }, 500, function(){
		after_article_moved(this);
		this.remove();
	});
}

var firehose_cal_select_handler = function(type,args,obj) {
	var selected = args[0];
	firehose_settings.issue = '';
	firehose_set_options('startdate', selected.startdate);
	firehose_set_options('duration', selected.duration);
};


function firehose_swatch_color(){} // does not exist until firehose-color-picker makes it available

function firehose_change_section_anon(section) {
	window.location.href= window.location.protocol + "//" + window.location.host + "/firehose.pl?section=" + encodeURIComponent(section) + "&tabtype=tabsection";
}

function pausePopVendorStory(id) {
	vendor_popup_id=id;
	closePopup('vendorStory-26-popup');
	vendor_popup_timerids[id] = setTimeout(vendorStoryPopup, 500);
}

function clearVendorPopupTimers() {
	clearTimeout(vendor_popup_timerids[26]);
}

function vendorStoryPopup() {
	id = vendor_popup_id;
	var title = "<a href='//intel.vendors.slashdot.org' onclick=\"javascript:pageTracker._trackPageview('/vendor_intel-popup/intel_popup_title');\">Intel's Opinion Center</a>";
	var buttons = createPopupButtons("<a href=\"#\" onclick=\"closePopup('vendorStory-" + id + "-popup')\">[X]</a>");
	title = title + buttons;
	var closepopup = function (e) {
		if (!e) { e = window.event; }
		var relTarg = e.relatedTarget || e.toElement;
		if (relTarg && relTarg.id == "vendorStory-26-popup") {
			closePopup("vendorStory-26-popup");
		}
	};
	createPopup('sponsorlinks', title, "vendorStory-" + id, "Loading", "", closepopup );
	var params = {};
	params.op = 'getTopVendorStory';
	params.skid = id;
	ajax_update(params, "vendorStory-" + id + "-contents");
}

function pausePopVendorStory2(id) {
	vendor_popup_id=id;
	closePopup('vendorStory-26-popup');
	vendor_popup_timerids[id] = setTimeout(vendorStoryPopup2, 500);
}

function vendorStoryPopup2() {
	id = vendor_popup_id;
	var title = "<a href='//intel.vendors.slashdot.org' onclick=\"javascript:pageTracker._trackPageview('/vendor_intel-popup/intel_popup_title');\">Intel's Opinion Center</a>";
	var buttons = createPopupButtons("<a href=\"#\" onclick=\"closePopup('vendorStory-" + id + "-popup')\">[X]</a>");
	title = title + buttons;
	var closepopup = function (e) {
		if (!e) { e = window.event; }
		var relTarg = e.relatedTarget || e.toElement;
		if (relTarg && relTarg.id == "vendorStory-26-popup") {
			closePopup("vendorStory-26-popup");
		}
	};
	createPopup('sponsorlinks', title, "vendorStory-" + id, "Loading", "", closepopup );
	var params = {};
	params.op = 'getTopVendorStory';
	params.skid = id;
	ajax_update(params, "vendorStory-" + id + "-contents");
}

function logToDiv(id, message) {
	$any(id).append(message + '<br>');
}


function firehose_open_tab(id) {
	$any('tab-form-'+id).removeClass();
	$any('tab-input-'+id).focus();
	$any('tab-text-'+id).setClass('hide');
}

function firehose_save_tab(id) {
	var	$tab		= $any('fhtab-'+id),
		new_name	= $tab.find('#tab-input-'+id).val(),
		$title		= $tab.find('#tab-text-'+id),
		$saved		= $title.children().remove(); // please ... think of the children
	// let's not wait for a server response to reflect the name-change
	$title.text(new_name).append($saved);

	// XXX: I'm having problems where the server occasionaly refuses,
	//	resets the title, and gives no explanation as to why
	ajax_update({
		op:		'firehose_save_tab',
		tabname:	new_name,
		section:	firehose_settings.section,
		tabid:		id
	}, '',  { onComplete: json_handler });
	$tab.find('#tab-form-'+id).setClass('hide');
	$title.removeClass();
}

// shared modal dialog box and the login box
// #modal_cover and #login_cover are the elements that dim the screen
// TODO: login box really should use the parts from the modal box... no need to duplicate

function cached_parts( expr ){
	// cache jQuery selection objects in the JS object that _is_ this function
	if ( ! cached_parts[expr] ){
		cached_parts[expr] = $(expr).insertBefore('#top_parent');
	}
	return cached_parts[expr];
}

function get_modal_parts( filter ){
	var $parts = cached_parts('#modal_cover, #modal_box');
	if ( filter ) {
		$parts = $parts.filter(filter);
	}
	return $parts;
}
function custom_modal_box( action_name ){
	$(document).trigger(action_name+'.modal');

	var	custom_fn_name	= '_custom_' + action_name + '_fn',
		$all_parts	= get_modal_parts(),
		$dialog		= $all_parts.filter('#modal_box'),
		dialog_elem	= $dialog[0],
		fn		= dialog_elem[custom_fn_name] || function(){ $all_parts[action_name](); };
	fn($all_parts);
	dialog_elem[custom_fn_name] = undefined;
	$all_parts.filter('#modal_cover').click(hide_modal_box);
	return $all_parts;
}
function show_modal_box(){
	return custom_modal_box('show').
		keyup(function( e ){
			e.which == $.ui.keyCode.ESCAPE && hide_modal_box();
		});
}
function hide_modal_box(){
	// clients may have customized; restore defaults before next use
	custom_modal_box('hide').
		hide().
		attr('style', 'display: none;').
		removeClass().
		removeData('tabbed').
		unbind();

	if (document.forms.modal_prefs && document.forms.modal_prefs.refresh_onclose && document.forms.modal_prefs.refresh_onclose.value) {
		document.location = document.URL;
	}

	return false; // ...so we can be used as a click handler.
}

function get_login_parts(){ return cached_parts('#login_cover, #login_box'); }
//function show_login_box(){ get_login_parts().show(); $('#login_box_content form input[name=unickname]').focus(); }
function show_login_box(){ getModalPrefs('userlogin', 'Log In', 1); }
function hide_login_box(){ get_login_parts().hide(); }

var logged_in = 1;
function check_logged_in(){ return logged_in || (show_login_box(), 0); }

function has_hose() { return firehose_exists }



function getModalPrefs(section, title, tabbed, params){
	var	BUSY_FETCHING_MODAL	= 'modal-fetch',
		$still_open			= get_modal_parts('#modal_box:visible'),
		$bg;

	$still_open.length && $still_open.data('tabbed')!=tabbed && hide_modal_box();

	var formname = section;
	var this_op = 'getModalPrefs';
	var return_to = '';
	if (formname === 'sendPasswdModal' || formname === 'newUserModal' || formname === 'userlogin') {
		this_op = 'getModalPrefsAnonHC';
		if (formname === 'userlogin') {
			return_to = location.toString();
		}
	} else if (formname === 'submit') {
		this_op = 'getModalPrefsAnon';
	} else if ( !reskey_static ) {
		return show_login_box();
	}

	Slash.busy(BUSY_FETCHING_MODAL, true);

	// Dim the background to prevent further modal-triggering-clicks while we wait.
	$bg = get_modal_parts('#modal_cover').css('opacity', 0.75).show();

	// .load ensures we are fetching as HTML, and that <script> elements will be executed
	$any('modal_box_content').load(
		'/ajax.pl',

		$.extend({
			op:		this_op,
			section:	section,
			reskey:		reskey_static,
			tabbed:		tabbed,
			return_to:	return_to
		}, params||null),

		function( response, status, transport ){
			if ( status==='success' ) {
				$any('preference_title').html(title);
				var $modal = show_modal_box().data('tabbed', tabbed);
				tabbed && $modal.addClass("tabbed");
			} else {
				// Can this happen?  Un-dim the background.  Report the error?
				$bg.hide();
			}
			Slash.busy(BUSY_FETCHING_MODAL, false);
		}

	);
}

function firehose_get_media_popup(id) {
	$any('preference_title').html('Media');
	show_modal_box();
	$any('modal_box_content').html("<h4>Loading...</h4><img src='//a.fsdn.com/sd/spinner_large.gif'>");
	ajax_update({
		op:	'firehose_get_media',
		id:	id
	}, 'modal_box_content');
}

function firehose_reinit_updates() {
	firehose_add_update_timerid(setTimeout(firehose_get_updates, 5000));
	Slash.clear_message('update-error');
}

function serialize_multiple( $form ){
	// serialize a form for use in a query, but force all fieldnames to be unique

	// extract the form fields into an array
	var elems = $form.serializeArray();

	// count uses of each fieldname to find those used more than once
	var uses = {};
	$.map(	elems,
		function(el){
			++uses[el.name] || (uses[el.name]=1);
		}
	);

	// return a query string, just as $form.serialize() would, except...
	var salt = 1;
	return $.param(
		$.map(	elems,
			function(el){
				// salt fieldnames used more than once
				if ( uses[el.name] > 1 ) {
					el.name += salt++;
				}
				return el;
			}
		)
	);
}

function show_submit_box(id,type) {
	var params = {};
	if (id) {
		params['from_id'] = id;
	}
	if (type) {
		params['type'] = type;
	}
	getModalPrefs('submit', 'Submit', 0, params);
}

function show_submit_box_after(from_id,type) {
	$('#editor').remove();
	var params = {
		op:  'edit_submit_box_after',
		reskey:	reskey_static
	};

	if (from_id) {
		params['from_id'] = from_id;
	}
	if (type) {
		params['type'] = type;
	}
	$('#firehose-'+from_id).fadeTo("slow", 0.5);

	ajax_update(params, '',  { onComplete: json_handler });
}

function close_inline_editor() {
	$('.edithidden').show().removeClass('edithidden').fadeTo('fast',1);
	$('#editor').hide('slow').remove();
}

function edit_editon(id, type, label) {
	$('#firehose-' + id).hide();
	$('.editonly').removeClass('hide');
	$('.previewonly').addClass('hide');
	$('#editor').removeClass('step2').addClass('step1');
	Tags.fetch($('#editor'));
	
	// Hide pending warnings on edit form
	$('#extra-warnings').addClass('hide');

	// Focus form field from error/warning click
	if (type !== "" && type !== undefined && label !== "" && label !== undefined) {
		$(type + "[name='" + label + "']").focus();
	}
}

function editPreview(save) {
	$('#edit-busy').toggle();
	$("form#slashstoryform .default").attr('value', '');
	var elems = $('#slashstoryform').serializeArray();
	var params = {};
	var multi = {};

	$.map(elems, function(el) {
		if (multi[el.name]===undefined) {
			multi[el.name] = 0;
		} else {
			multi[el.name]++;
		}
	});

	$.map(elems,
		function(el) {
			if (multi[el.name]) {
				if (!params[el.name])
					params[el.name] = [];
				params[el.name].push(el.value);
			} else {
				params[el.name] = el.value;
			}
		}
	);
	if (save) {
		var d = new Date;
		params['submit_time'] = d.getTime();
	}

	params['op'] = save ? 'edit_save' : 'edit_preview';
	ajax_update(params, '', { onComplete: json_handler });
}

function editSave() {
	editPreview(1);
}

function submit_reset(id,state,type) {
	$('#edit-busy').toggle();
	var params = {
		'op': 'edit_reset',
		'new' : 1
	};

	if (id && state == 'inline' ) {
		params['from_id'] = id;
	}
	if (state) {
		params['state'] = state;
	}
	if (type) {
		params['type'] = type;
	}

	ajax_update(params, '', { onComplete: json_handler });
}

function submit_cancel() {
	$('#edit-busy').toggle();
	hide_modal_box();
	close_inline_editor();
}


function resetModalPrefs(extra_param) {
	var params = {
		op:	'saveModalPrefs',
		data: 	serialize_multiple($any('modal_prefs')),
		reset:  1,
		reskey:	(document.forms.modal_prefs && document.forms.modal_prefs.reskey   && document.forms.modal_prefs.reskey.value) || reskey_static
	};

	if (extra_param) {
		params[extra_param] = 1;
	}

	ajax_update(params, '', {
		onComplete: function() {
			hide_modal_box();
			document.location=document.URL;
		}
	});
}

function saveModalPrefs(formname, this_data, this_reskey) {
	formname    = formname    || (document.forms.modal_prefs && document.forms.modal_prefs.formname && document.forms.modal_prefs.formname.value);
	this_data   = this_data   || serialize_multiple($any('modal_prefs'));
	this_reskey = this_reskey || (document.forms.modal_prefs && document.forms.modal_prefs.reskey   && document.forms.modal_prefs.reskey.value  ) || reskey_static;

	var this_op = 'saveModalPrefs';
	if (formname === 'sendPasswdModal' || formname === 'newUserModal') {
		this_op = 'saveModalPrefsAnonHC';
	}

	ajax_update({
		op:	this_op,
		data:	this_data,
		reskey:	this_reskey
	}, '', {
		onComplete: function(transport) {
			var response = eval_response(transport);
			json_update(response);
			if (response === undefined || response.html_replace === undefined) {
				hide_modal_box();
				if (document.forms.modal_prefs.refreshable && document.forms.modal_prefs.refreshable.value) {
					document.location=document.URL;
				}
			}
		}
	});
}

function createacct_check_nick(this_form) {
	var params = {};
	params.op = 'createacct_check_nick';
	params.nickname = this_form.newusernick.value;
	params.reskey = this_form.nick_rkey.value;

	if ((this_form === undefined) || (params.nickname === undefined) || (params.nickname === '')) {
                return false;
        }

	ajax_update(
		params,
		'',
		{
			onComplete: function(transport) {
				var response = eval_response(transport);
				if (response !== undefined && response.html_replace !== undefined) {
					json_update(response);
				}
			}
		}
	);
}

function displayModalPrefHelp(id) {
	var el = $any(id);
	el.css('display', el.css('display')!='none' ? 'none' : 'inline');
}


function toggle_filter_prefs() {
	$('#filter_play_status, #filter_prefs').toggleClass('hide');
}


function firehose_get_cur() {
	return $('#firehoselist > div.fhitem.currfh');
}

function firehose_get_first() {
	return $('#firehoselist > div.fhitem:first');
}

function firehose_set_cur( $new_current, intent ) {
	if (!$new_current || !$new_current.length)
		$new_current = firehose_get_first();

	$new_current = $new_current.eq(0); // only one article may be current at a time
	if ($new_current.is('.currfh'))
		return $new_current;

	var	$old_current	= $new_current.siblings('div.fhitem.currfh'),
		event_data	= { blurring:$old_current, focusing:$new_current };

	$old_current.each(function(){
		// "blur" previous current article, if any (and correct if "multiple current")
		$(this).trigger('blur-article', event_data).
			removeClass('currfh'); // after event
	});

	$new_current.
		addClass('currfh'). // before event
		trigger('focus-article', event_data);

	if ( !intent || intent==='interest' ) {
		var viewhint=false, $fhitems=$('#firehoselist>div.fhitem'), pos=$fhitems.index($new_current);
		if ( pos==0 ) {
			viewhint = $('body');
		} else if ( pos == $fhitems.length-1 ) {
			viewhint = $any('div#fh-paginate');
		}

		view($new_current, { hint:viewhint, speed:50 });
	}

	return $new_current;
}

function firehose_go_next($current) {
	$current = $current || firehose_get_cur();
	if ( fh_is_admin && $current.length > 1 ) {
		return;
	}
	$current = $current.eq( $current.length-1 );

	var $next = $current.nextAll('div.fhitem:first');
	// if no current, pick top; if current but no next, do more
	if ($next[0] || !$current[0]) {
		return firehose_set_cur($next);
	} else {
		view($current, { hint:$any('div#fh-paginate') });
		firehose_more();
	}
}

function firehose_go_prev($current) {
	$current = $current || firehose_get_cur();
	if ( fh_is_admin && $current.length > 1 ) {
		return;
	}
	$current = $current.eq(0);

	return firehose_set_cur(
		$current.prevAll('div.fhitem:first')
	);
}

function firehose_more(noinc) {
	if (!noinc) {
		firehose_settings.more_num = firehose_settings.more_num + firehose_more_increment;
	}

	if (((firehose_item_count + firehose_more_increment) >= 200) && !fh_is_admin) {
		$any('firehose_more').hide();
	}
	if (firehose_user_class) {
		firehose_set_options('more_num', firehose_settings.more_num);
	} else {
		firehose_get_updates({ oneupdate: 1 });
	}

	inlineAdFirehose();
}

function firehose_section_menu_item( section_id ){
	var id = 'fhsection-'+section_id;
	return section_id && $('#links-sections-title,#'+id).
		filter(function(){
			return this.id===id || $(this).metadata().id==section_id;
		});
}


function getSeconds () {
	return new Date().getTime()/1000;
}

function nojscall(f) {
	try {
		f();
	} catch (e) {
		// nothing for now
	}
	return false;
}


// ads!  ads!  ads!
var adTimerSeen   = {};
var adTimerSecs   = 0;
var adTimerClicks = 0;
var adTimerInsert = 0;


function inlineAdReset(id) {
	if (id !== undefined)
		adTimerSeen[id] = 2;
	adTimerSecs   = getSeconds();
	adTimerClicks = 0;
	adTimerInsert = 0;
}


function inlineAdClick(id) {
	//adTimerSeen[id] = adTimerSeen[id] || 1;
	adTimerClicks = adTimerClicks + 1;
}


function inlineAdInsertId(id) {
	if (id !== undefined)
		adTimerInsert = id;
	return adTimerInsert;
}


function inlineAdVisibles() {
	var	visible		= new Bounds(window),
		$visible_ads	= $('li.inlinead').filter(function(){
					if ( Bounds.intersect(visible, this) ) return this;
				});
	return $visible_ads.length;
}


function inlineAdCheckTimer(id, url, clickMax, secsMax) {
	if (!url || !id)
		return 0;

	if (adTimerSeen[id] && adTimerSeen[id] == 2)
		return 0;

	// ignore clicks if adTimerClicksMax == 0
	if (clickMax > 0 && !adTimerSeen[id])
		inlineAdClick(id);

	var ad = 0;
	if (clickMax > 0 && adTimerClicks >= clickMax)
		ad = 1;
	else {
		var secs = getSeconds() - adTimerSecs;
		if (secs >= secsMax)
			ad = 1;
	}

	if (!ad)
		return 0;

	return inlineAdInsertId(id);
}

function inlineAdFirehose($article, show_on_article_regardless_of_visibility) {
	var Fh=Slash.Firehose, Ad=Fh.floating_slashbox_ad, is_combined=Ad.combined_mode();

	if (!fh_adTimerUrl)
		return 0;

	if ($article) {
		if (!show_on_article_regardless_of_visibility)
			$article = Fh.ready_ad_space($article);
	} else {
		$article = Fh.choose_article_for_next_ad();
	}

	if (!$article || !$article.length)
		return 0;

	var id = fhitem_key($article).key;
	if (!id)
		return 0;

	// we need to remove the existing ad from the hash so it can be re-used
	var old_id = inlineAdInsertId();

	if (! inlineAdCheckTimer(id, fh_adTimerUrl, fh_adTimerClicksMax, fh_adTimerSecsMax))
		return 0;

	if (!is_combined && Ad.is_visible())
		return 0;

	var $list		= $article.find('[context=system]'),
		topic		= $list.find('.t2:not(.s1):first .tag').text(),
		skin		= $list.find('.s1:first .tag').text(),
		adUrl		= fh_adTimerUrl
						+ '?skin=' + (skin || 'mainpage')
						+ (topic ? '&topic='+topic : '')
						+ '&pos=84&cat=medrec',
		height		= is_combined ? 250 : 300,
		ad_content	= '<iframe class="advertisement" src="' + adUrl + '" height="' + height + '" width="300" frameborder="0" border="0" scrolling="no" marginwidth="0" marginheight="0"></iframe>';

	Ad($article, ad_content);


	inlineAdReset(id);
	if (old_id)
		adTimerSeen[old_id] = 0;

	return id;
}


;(function($){

//
// Firehose Floating Slashbox Ad
//


var	AD_HEIGHT=300,
	COMBINED_MODE=false,
	generation=0,

	$ad_position=$([]),		// div that holds the current (if any) ad
	ad_target_article=null,	// the article to which that ad is attached
	$ad_offset_parent,		// the container in which the ad _position_ floats (between articles)
	$slashboxes,			// the container (sort of) in which the ad content actually appears (though not as a child)

	is_ad_locked=false;		// ad must be shown for at least N seconds

$(function(){
	$slashboxes = $('#slashboxes, #userboxes').eq(0);

	$slashboxes.length && $('#slug-Top,#slug-Bottom').show();

	$(document).
		bind('firehose-setting-noslashboxes', fix_ad_position);

	$any('firehoselist').
		bind('articlesMoved', fix_ad_position).
		bind('beforeArticleRemoved', notice_article_removed);
});

function notice_article_removed( event, removed_article ){
	if ( ad_target_article === removed_article ) {
		remove_ad();
	}
}

function remove_ad(){
	ad_target_article = null;

	if ( is_ad_locked ) {
		return false;
	}

	$ad_position.remove();
	$ad_position = $([]);
	return true;
}

function insert_ad( $article, ad ){
	if ( !ad || !$article || $article.length != 1 || !remove_ad() ) {
		return;
	}

	++generation;
	ad = ad.replace(/&pos=84&/, '&pos=84&gen='+generation+'&');

	ad_target_article = $article[0];
	$ad_position = $article.
		before('<div id="floating-slashbox-ad" class="Empty" />').
		prev().
			append(ad);

	setTimeout(function(){
		is_ad_locked = false;
		if ( ! ad_target_article ) {
			remove_ad();
		}
	}, 10000); // wait 10 seconds
	is_ad_locked = true;

	if ( ! $ad_offset_parent ) {
		$ad_offset_parent = $article.offsetParent();
	}

	fix_ad_position();
	$ad_position.filter(':not(.Empty)').fadeIn('fast');
}

function verticalAdSpace(){
	var bounds = Bounds.y('#slug-Bottom');
	bounds.top = Position('#slug-Top').top;
	return bounds;
}

function pin_ad(){
	var new_top='';
	$('#slug-' + ($ad_position.is('.Crown') ? 'Crown' : $ad_position.attr('className'))).each(function(){
		new_top = Position(this).top - Position($ad_offset_parent).top;
	});
	$ad_position.css('top', new_top);
}

var NO_SPACE	= 0,
	NOT_PINNED	= 2,
	pinClasses	= [	// index by (pinning+2)||0
		'Empty',	// not enough room to hold an ad
		'Top',		// pinned to the top of the available space, though the natural top is higher
		'No',		// not pinned
		'Bottom'	// pinned to the bottom of the available space, though the natural top would be lower
	];


function should_crown(){
	var answer = false;
	COMBINED_MODE && $('#slug-Crown:visible').each(function(){
		answer = Bounds.intersect(window, this);
	});
	return answer;
}

function scroll_crown(){
	var now_crown = should_crown();
	if ( now_crown !== $ad_position.is('.Crown') ) {
		$ad_position.toggleClass('Crown', now_crown);
		pin_ad();
	}
}

function fix_ad_position(){
	if ( $ad_position.length ) {
		var space = verticalAdSpace();
		if ( !TypeOf.number(space.top) ||  !TypeOf.number(space.bottom) ) {
			return;
		}
		space.bottom-=AD_HEIGHT;

		// the "natural" ad position is top-aligned with the following article
		var natural_top = Position($ad_position.next()).top;
		if ( natural_top===undefined ) {
			// ...or else top-aligned to the previous bottom, I guess... but wouldn't this mean you're at the end the page?
			natural_top = Bounds($ad_position.prev()).bottom;
		}

		var	pinning		= $slashboxes.is(':visible') && between(space.top, natural_top, space.bottom)+NOT_PINNED || NO_SPACE,
			now_empty	= pinning === NO_SPACE,
			now_crown	= !now_empty && should_crown(),
			was_empty	= $ad_position.is('.Empty');

		if ( !(was_empty && now_empty ) ) {
			$ad_position.setClass(pinClasses[pinning] + (now_crown ? ' Crown' : ''));
			pin_ad();
		}
	}
}

function ad_message( e ){
	var match = /^p(.+):height=(\d+)$/.exec(e.data);
	if ( match && match[1]==='84' ) {
		$ad_position.children('iframe:first').attr('height', match[2]);
	}
}

function combined_mode( enable ){
	if ( enable!==void(0) && enable!=COMBINED_MODE ) {
		COMBINED_MODE = !COMBINED_MODE;
		$('#slug-Crown').toggle(COMBINED_MODE);
		$('#slug-Bottom, div.slug .content').css('height', AD_HEIGHT = COMBINED_MODE ? 250 : 300);
		$(window)[ COMBINED_MODE ? 'bind' : 'unbind' ]('scroll', scroll_crown);
		fix_ad_position();
	}
	return COMBINED_MODE;
}

/* (function(){
	function once( event, adPos, enable ){
		if ( enable && adPos==84 ) {
			$(document).unbind('falk', once);
			$(function(){ combined_mode(true) && inlineAdFirehose(); });
		}
	}
	$(document).bind('falk', once);
})(); */

(function(){
var M = Slash.Firehose.floating_slashbox_ad = insert_ad;
M.is_visible = function(){
	return Bounds.intersect(window, $ad_position);
};
M.remove = remove_ad;
M.combined_mode = combined_mode;
M.fix_ad_position = fix_ad_position;
})();

Slash.Firehose.articles_on_screen = function(){
	var	visible = Bounds.y(window),
		lo,	// index within the jQuery selection of the first article visible on the screen
		hi=0;	// index one beyond the last article visible on the screen

	var $articles = $('#firehoselist>div.fhitem:visible').
		// examine articles in order until I _know_ no further articles can be on screen
		each(function(){
			var $this=$(this), this_top=$this.offset().top;
			// hi is the index of this article

			if ( this_top >= visible.bottom ) {
				// ...then this article, and all that follow must be entirely below the screen
				// the last article on screen (if any) must be the previous one (at hi-1)
				return false;
			}

			// until we find the first on-screen article...
			if ( lo === undefined ) {
				var this_bottom = this_top + $this.height();

				// we know this_top is above visible.bottom, so...
				if ( this_bottom > visible.top ) {
					// ...then _this_ article must be (the first) on screen
					lo = hi;
				}

				if ( this_bottom >= visible.bottom ) {
					// ...then we must be the _only_ article on screen
					++hi; // starting one past this article, everything is below the screen
					return false;
				}
			}
			++hi;
		});

	if ( lo === undefined ) {
		return $([]);
	} else if ( lo===0 && hi==$articles.length ) {
		return $articles;
	} else {
		return $(Array.prototype.slice.call($articles, lo, hi));
	}
}

// filter $articles to only those adjacent to available space for an ad
// return empty list if none, or if not enough time has yet passed to place a new ad
Slash.Firehose.ready_ad_space = function( $articles ){
	var $result = $([]);
	try {
		if ( !is_ad_locked && $slashboxes.is(':visible') ) {
			if ( COMBINED_MODE ) {
				return $articles;
			}

			var visible = Bounds.intersection(Bounds.y(window), verticalAdSpace());
			visible.bottom -= AD_HEIGHT;

			$result = $articles.filter(function(){
				return Bounds.contain(visible, Position(this));
			});
		}
	} catch ( e ) {
		// don't throw
	}
		// just tell the caller no articles supplied are at or below ad-space
	return $result;
}

Slash.Firehose.choose_article_for_next_ad = function(){
	var Fh=Slash.Firehose, $articles=Fh.ready_ad_space(Fh.articles_on_screen());
	// prefer current article if available?
	return $articles.eq( Math.floor(Math.random()*$articles.length) );
}

})(Slash.jQuery);


function openInWindow(mylink, samewin) {
	if (!samewin && window.open(mylink, '_blank')) {
		return false;
	}
	window.location = mylink;
	return false;
}

$(function(){
	// firehose only!  but REAL firehose, not the fake one with comments in it!
	var validkeys = {};
	if ( !firehose_smallscreen && $('div.fhroot').length && !$('ul#commentlisting').length ) {
		validkeys = {
			'X' : {           tags    : 1, signoff  : 1, noanon : 1 },
			'T' : {           tags    : 1, tag      : 1, noanon : 1 },
			187 : { chr: '+', tags    : 1, tag      : 1, noanon : 1, nod    : 1 }, // 61, 107
			189 : { chr: '-', tags    : 1, tag      : 1, noanon : 1, nix    : 1 }, // 109

			'R' : {           open    : 1, readmore : 1 },
			'E' : {           open    : 1, edit     : 1 },
			'O' : {           open    : 1, link     : 1 },

			'G' : {           more    : 1 },
			'Q' : {           toggle  : 1 },
			'S' : {           next    : 1 },
			'W' : {           prev    : 1 },

			'F' : {           search  : 1 },
			190 : { chr: '.', slash   : 1 }, // 110

			27  : {           form    : 1, unfocus : 1 } // esc
		};
		validkeys['H'] = validkeys['A'] = validkeys['Q'];
		validkeys['L'] = validkeys['D'] = validkeys['Q'];
		validkeys['K'] = validkeys['W'];
		validkeys['J'] = validkeys['S'];
		validkeys['I'] = validkeys['T'];
		validkeys[107] = validkeys[61] = validkeys[187];
		validkeys[109] = validkeys[189];
		validkeys[110] = validkeys[190];
	}

// down arrow: 40
// left arrow: 37
// enter: 13


	$(document).keydown(function( e ) {
		// no modifiers, except maybe shift
		if (e.ctrlKey || e.metaKey || e.altKey)
			return true;

		var shiftKey = e.shiftKey ? 1 : 0;

		var c    = e.which;
		var key  = validkeys[c] ? c : String.fromCharCode(c);
		var keyo = validkeys[key];

		if (!keyo)
			return true;

		// if keyo.form, only work on form elements; if not, then
		// never work on form elements.
		var is_input = e.target && $(e.target).is(':input');
		if (!keyo.form && is_input)
			return true;
		if (keyo.form && !is_input)
			return true;

		if (keyo.noanon && !check_logged_in())
			return false;

		if (keyo.admin && !fh_is_admin)
			return true;

		var cur = firehose_get_cur();
		var el, id;
		if (cur.length) {
			el = cur[0];
			id = el.id.replace(FHID_PREFIX, '');
		}

		if (keyo.tag && el) {
			firehose_toggle_tag_ui_to(true, el);
			if (keyo.nod)     { Tags.submit(el, 'nod') }
			if (keyo.nix)     { Tags.submit(el, 'nix') }
		}

		if (keyo.signoff && el && tag_admin) {
			Tags.submit(el, 'signoff');
			// we either call set_cur($(el)) as above,
			// or just pass $(el) to go_next()
			firehose_go_next($(el));
		}

		if (keyo.slash)          {
			firehose_set_options('section', $any('links-sections-title').metadata().id);
		}
		if (keyo.unfocus)        { $(e.target).blur()        }
		if (keyo.next)           { firehose_go_next()        }
		if (keyo.prev)           { firehose_go_prev()        }
		if (keyo.more)           { firehose_more()           }
		if (keyo.search)         {
			view($any('searchquery'), { hint:$('body'), focus:true });
		}
		if (keyo.toggle && id)   { toggle_firehose_body(el)  }

		if (keyo.open) {
			var mylink = '';
			var obj;
			//var doc_url = document.location.href.replace(/(\w\/).*$/, '$1');
			if (keyo.link) {
				obj = cur.find('span.external > a:first');
			}
			if (keyo.readmore) {
				obj = cur.find('a.datitle:first');
				//mylink = doc_url + 'firehose.pl?op=view&id=' + id;
			}
			if (keyo.edit) { // && fh_is_admin) {
				obj = cur.find('form.edit > a:first');
				//mylink = doc_url + 'firehose.pl?op=edit&id=' + id;
			}
			if (!mylink.length && obj.length) {
				mylink = obj[0].href;
			}

			if (mylink.length) {
				return openInWindow(mylink, (shiftKey ? 1 : 0));
			} else {
				return true;
			}
		}

		return false;
	});
});


function shorten_fh_pag_menu() {
	while (1) {
		var check = shorten_fh_pag_menu_check();
		if (check < 0) {
			var $spans = $('#fh-paginate span.active:visible,span.inactive:visible');
			var idx = $spans.length;
			idx--; idx--; // second from last item
			if (idx > 0) {
				$($spans[idx]).hide();
			} else {
				return;
			}
		} else if (check > 0) {
			var $spans = $('#fh-paginate span.active:hidden,span.inactive:hidden');
			var idx = $spans.length;
			if (idx > 0) {
				$($spans[0]).show();
				if (shorten_fh_pag_menu_check() < 0) {
					$($spans[0]).hide();
				}
				return;
			} else {
				return;
			}
		} else {
			return;
		}
	}
}

function shorten_fh_pag_menu_check() {
	var $firstitem = $('#fh-pag-div div.currcolor');
	var $lastitem  = $('#fh-paginate span:last');

	if (!$firstitem.length || !$lastitem.length) {
		return 0;
	}

	var firstb = new Bounds($firstitem);
	var lastb  = new Bounds($lastitem);

	if (!firstb || !lastb || !firstb.top || !lastb.top) {
		return 0;
	}

	if (firstb.top != lastb.top) {
		return -1;
	} else {
		return 1;
	}
}

function anchor_fh_pag_menu(modified) {
	if (!$.browser.msie) {
		var $fhl = $('#firehose'); // FH list
		var $fft = $('#fh-pag-div'); // FH footer
		var $pft = $('#ft'); // page footer

		var fhlbounds = new Bounds($fhl);
		var fftbounds = new Bounds($fft);
		var pftbounds = new Bounds($pft);
		var winbounds = new Bounds(window);

		var fhlvis = winbounds.bottom > fhlbounds.bottom; // && winbounds.top < fhlbounds.bottom;
		var pftvis = winbounds.bottom > pftbounds.top;

		// if bottom of FH list is above bottom of page, AND
		// page footer is not visible, float the FH footer
		// otherwise keep it at bottom of page above page footer 
		if (fhlvis && !pftvis) {
			if (!$fft.hasClass('float')) {
				$fft.addClass('float');
				modified = true;
			}
		} else {
			if ($fft.hasClass('float')) {
				$fft.removeClass('float');
				modified = true;
			}
		}
	}

	if (modified) {
		shorten_fh_pag_menu();
		// Safari 3 hack.  hooray or something. any other platform need this?
		if ($.browser.safari && !$("div.paginatehidden").length) {
			setTimeout('$("#fh-pag-div").hide()', 0);
			setTimeout('$("#fh-pag-div").show()', 0);
		}
	}
}


