//************************************************/
/* IPB3 Javascript								*/
/* -------------------------------------------- */
/* ipb.js - Global code							*/
/* (c) IPS, Inc 2008							*/
/* -------------------------------------------- */
/* Author: Rikki Tissier						*/
/************************************************/

/* ===================================================================================================== */
/* IPB3 JS Debugging */

var Debug = {
	write: function( text ){
		if( jsDebug && !Object.isUndefined(window.console) ){
			console.log( text );
		}
	},
	dir: function( values ){
		if( jsDebug && !Object.isUndefined(window.console) ){
			console.dir( values );
		}
	},
	error: function( text ){
		if( jsDebug && !Object.isUndefined(window.console) ){
			console.error( text );
		}
	},
	warn: function( text ){
		if( jsDebug && !Object.isUndefined(window.console) ){
			console.warn( text );
		}
	},
	info: function( text ){
		if( jsDebug && !Object.isUndefined(window.console) ){
			console.info( text );
		}
	}
}

/* ===================================================================================================== */
/* OVERWRITE getOffsetParent TO CORRECT IE8 PROTOTYPE ISSUE */

Event.observe( window, 'load', function(e){
	Element.Methods.getOffsetParent = function( element ){
		//alert( "Using overloaded getOffsetParent" );
		if (element.offsetParent && element.offsetParent != document.body) return $(element.offsetParent);
		if (element == document.body) return $(element);

		while ((element = element.parentNode) && element != document.body)
		  if (Element.getStyle(element, 'position') != 'static')
		    return $(element);

		return $(document.body);
	}
});

function _getOffsetParent( element )
{
	//alert( "Using overloaded getOffsetParent" );
	if (element.offsetParent && element.offsetParent != document.body) return $(element.offsetParent);
	if (element == document.body) return $(element);

	while ((element = element.parentNode) && element != document.body)
	  if (Element.getStyle(element, 'position') != 'static')
	    return $(element);

	return $(document.body);
}

/* Set up version specifics */
Prototype.Browser.IE6 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 6;
Prototype.Browser.IE7 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 7;
Prototype.Browser.IE8 = Prototype.Browser.IE && !Prototype.Browser.IE6 && !Prototype.Browser.IE7;

/* Add in stuff prototype does not include */
Prototype.Browser.Chrome = Prototype.Browser.WebKit && ( navigator.userAgent.indexOf('Chrome/') > -1 );

/* ===================================================================================================== */
/* MAIN ROUTINE */

window.IPBoard = Class.create({
	namePops: [],
	vars: [],
	lang: [],
	templates: [],
	editors: $A(),
	initDone: false,
	
	initialize: function()
	{
		Debug.write("IPBjs is loading...");
		
		document.observe("dom:loaded", function(){
			
			this.Cookie.init();
			// Show a little loading graphic
			Ajax.Responders.register({
			  onLoading: function() {
			    if( !$('ajax_loading') )
				{
					if( !ipb.templates['ajax_loading'] ){ return; }
					$('ipboard_body').insert( ipb.templates['ajax_loading'] );
				}
				
				var effect = new Effect.Appear( $('ajax_loading'), { duration: 0.2 } );
			  },
			  onComplete: function() {
			
				if( !$('ajax_loading') ){ return; }
			    var effect = new Effect.Fade( $('ajax_loading'), { duration: 0.2 } );
			  }
			});
			
			// Initialize our delegation manager
			ipb.delegate.initialize();
			
			ipb.initDone = true;
			
		}.bind(this));
	},
	positionCenter: function( elem, dir )
	{
		if( !$(elem) ){ return; }
		elem_s = $(elem).getDimensions();
		window_s = document.viewport.getDimensions();
		window_offsets = document.viewport.getScrollOffsets();

		center = { 	left: ((window_s['width'] - elem_s['width']) / 2),
					 top: ((window_s['height'] - elem_s['height']) / 2)
				}
		
		if( typeof(dir) == 'undefined' || ( dir != 'h' && dir != 'v' ) )
		{
			$(elem).setStyle('top: ' + center['top'] + 'px; left: ' + center['left'] + 'px');
		}
		else if( dir == 'h' )
		{
			$(elem).setStyle('left: ' + center['left'] + 'px');
		}
		else if( dir == 'v' )
		{
			$(elem).setStyle('top: ' + center['top'] + 'px');
		}
		
		$(elem).setStyle('position: fixed');
	},
	showModal: function()
	{
		if( !$('ipb_modal') )
		{
			this.createModal();
		}
		this.modal.show();
	},
	hideModal: function()
	{
		if( !$('ipb_modal') ){ return; }
		this.modal.hide();		
	},
	createModal: function()
	{
		this.modal = new Element('div', { id: 'ipb_modal' } ).hide().addClassName('modal');
		this.modal.setStyle("width: 100%; height: 100%; position: absolute; top: 0px; left: 0px; overflow: hidden; z-index: 1000; opacity: 0.2");
		$('ipboard_body').insert({bottom: this.modal});
	},
	alert: function( message )
	{
		if( !$('ipb_alert') )
		{
			this.createAlert();
		}
		
		this.showModal();
		
		$('ipb_alert_message').update( message );
	},
	createAlert: function()
	{
		wrapper = new Element('div', { id: 'ipb_alert' } );
		icon = new Element('div', { id: 'ipb_alert_icon' } );
		message = new Element('div', { id: 'ipb_alert_message' } );
		ok_button = new Element('input', { 'type': 'button', 'value': "OK", id: 'ipb_alert_ok' });
		cancel_button = new Element('input', { 'type': 'button', 'value': "Cancel", id: 'ipb_alert_cancel' });
		
		wrapper.insert( {bottom: icon} ).insert( {bottom: message} ).insert( {bottom: ok_button} ).insert( {bottom: cancel_button} ).setStyle('z-index: 1001');
		
		$('ipboard_body').insert({bottom: wrapper});
		
		this.positionCenter( wrapper, 'h' );
	},
	editorInsert: function( content, editorid )
	{
		// If no editor id supplied, lets use the first one
		if( !editorid )	{
			Debug.dir( ipb.editors );
			var editor = $A( ipb.editors ).first();
			
			Debug.write( editor );
		} else {
			var editor = ipb.editors[ editorid ];
		}
		
		if( Object.isUndefined( editor ) )
		{
			Debug.error( "Can't find any suitable editor" );
			return;
		}
		
		editor.insert_text( content );
		editor.editor_check_focus();
	}
});

/* ===================================================================================================== */
/* IPB3 Delegation manager */
/* Simple class that allows us to specify css selectors and an associated function to run */
/* when an appropriate element is clicked */

IPBoard.prototype.delegate = {
	store: $A(),
	
	initialize: function()
	{
		document.observe('click', function(e){

			if( Event.isLeftClick(e) || Prototype.Browser.IE ) // IE doesnt provide isLeftClick info for click event
			{
				var elem = null;
				var handler = null;
			
				var target = ipb.delegate.store.find( function(item){
					elem = e.findElement( item['selector'] );
					if( elem ){
						handler = item;
						return true;
					} else {
						return false;
					}
				});
			
				if( !Object.isUndefined( target ) )
				{				
					if( handler )
					{
						Debug.write("Firing callback for selector " + handler['selector'] );
						handler['callback']( e, elem, handler['params'] );
					}
				}
			}
        })
	},
	
	register: function( selector, callback, params )
	{
		ipb.delegate.store.push( { selector: selector, callback: callback, params: params } );
	}
}

/* ===================================================================================================== */
/* IPB3 Cookies */

/* Meow */
IPBoard.prototype.Cookie = {
	store: [],
	initDone: false,
	
	set: function( name, value, sticky )
	{
		var expires = '';
		var path = '/';
		var domain = '';
		
		if( !name )
		{
			return;
		}
		
		if( sticky )
		{	
			if( sticky == 1 )
			{
				expires = "; expires=Wed, 1 Jan 2020 00:00:00 GMT";
			}
			else if( sticky == -1 ) // Delete
			{
				expires = "; expires=Thu, 01-Jan-1970 00:00:01 GMT";
			}
			else if( sticky.length > 10 )
			{
				expires = "; expires=" + sticky;
			}
		}
		if( ipb.vars['cookie_domain'] )
		{
			domain = "; domain=" + ipb.vars['cookie_domain'];
		}
		if( ipb.vars['cookie_path'] )
		{
			path = ipb.vars['cookie_path'];
		}
		
		document.cookie = ipb.vars['cookie_id'] + name + "=" + escape( value ) + "; path=" + path + expires + domain + ';';
		
		ipb.Cookie.store[ name ] = value;
		
		Debug.write( "Set cookie: " + ipb.vars['cookie_id'] + name + "=" + value + "; path=" + path + expires + domain + ';' );
	},
	get: function( name )
	{
		/* Init done yet? */
		if ( ipb.Cookie.initDone !== true )
		{
			ipb.Cookie.init();
		}
		
		if( ipb.Cookie.store[ name ] )
		{
			return ipb.Cookie.store[ name ];
		}
		
		return '';
	},
	doDelete: function( name )
	{
		Debug.write("Deleting cookie " + name);
		ipb.Cookie.set( name, '', -1 );
	},
	init: function()
	{
		// Already init?
		if ( ipb.Cookie.initDone )
		{
			return true;
		}
		
		// Init cookies by pulling in document.cookie
		skip = ['session_id', 'ipb_admin_session_id', 'member_id', 'pass_hash'];
		cookies = $H( document.cookie.replace(" ", '').toQueryParams(";") );
	
		if( cookies )
		{
			cookies.each( function(cookie){
				cookie[0] = cookie[0].strip();
				
				if( ipb.vars['cookie_id'] != '' )
				{
					if( !cookie[0].startsWith( ipb.vars['cookie_id'] ) )
					{
						return;
					}
					else
					{
						cookie[0] = cookie[0].replace( ipb.vars['cookie_id'], '' );
					}
				}
				
				if( skip[ cookie[0] ] )
				{
					return;
				}
				else
				{
					ipb.Cookie.store[ cookie[0] ] = unescape( cookie[1] || '' );
					Debug.write( "Loaded cookie: " + cookie[0] + " = " + cookie[1] );
				}				
			});
		}
		
		ipb.Cookie.initDone = true;	
	}
};

/* ===================================================================================================== */
/* Form validation */

IPBoard.prototype.validate = {
	// Checks theres actually a value
	isFilled: function( elem )
	{
		if( !$( elem ) ){ return null; }
		return !$F(elem).blank();
	},
	isNumeric: function( elem )
	{
		if( !$( elem ) ){ return null; }
		return $F(elem).match( /^[\d]+?$/ );
	},
	isMatching: function( elem1, elem2 )
	{
		if( !$( elem1 ) || !$( elem2 ) ){ return null; }
		return $F(elem1) == $F(elem2);
	},
	email: function( elem )
	{
		if( !$( elem ) ){ return null; }
		if( $F( elem ).match( /^.+@.+\..{2,4}$/ ) ){
			return true;
		} else {
			return false;
		}
	}
	/*isURL: function( elem )
	{
		if( !$(elem) ){ return null; }		
		return ( $F(elem).match( /^[A-Za-z]+:\/\/[A-Za-z0-9-_]+\\.[A-Za-z0-9-_%&\?\/.=]+$/ ) == null ) ? true : false;
	}*/
};

/* ===================================================================================================== */
/* AUTOCOMPLETE */

IPBoard.prototype.Autocomplete = Class.create( {
	
	initialize: function(id, options)
	{
		this.id = $( id ).id;
		this.timer = null;
		this.last_string = '';
		this.internal_cache = $H();
		this.pointer = 0;
		this.items = $A();
		this.observing = true;
		this.objHasFocus = null;
		this.options = Object.extend({
			min_chars: 3,
			multibox: false,
			global_cache: false,
			classname: 'ipb_autocomplete',
			templates: 	{ 
							wrap: new Template("<ul id='#{id}'></ul>"),
							item: new Template("<li id='#{id}'>#{itemvalue}</li>")
						}
		}, arguments[1] || {});
		
		//-----------------------------------------
		
		if( !$( this.id ) ){
			Debug.error("Invalid textbox ID");
			return false;
		}
		
		this.obj = $( this.id );
		
		if( !this.options.url )
		{
			Debug.error("No URL specified for autocomplete");
			return false;
		}
		
		$( this.obj ).writeAttribute('autocomplete', 'off');
		
		this.buildList();
		
		// Observe keypress
		$( this.obj ).observe('focus', this.timerEventFocus.bindAsEventListener( this ) );
		$( this.obj ).observe('blur', this.timerEventBlur.bindAsEventListener( this ) );
		$( this.obj ).observe('keypress', this.eventKeypress.bindAsEventListener( this ) );
		
	},
	
	eventKeypress: function(e)
	{
		if( ![ Event.KEY_TAB, Event.KEY_UP, Event.KEY_DOWN, Event.KEY_LEFT, Event.KEY_RIGHT, Event.KEY_RETURN ].include( e.keyCode ) ){
			return; // Not interested in anything else
		}
		
		if( $( this.list ).visible() )
		{
			switch( e.keyCode )
			{
				case Event.KEY_TAB:
				case Event.KEY_RETURN:
					this.selectCurrentItem(e);
				break;
				case Event.KEY_UP:
				case Event.KEY_LEFT:
					this.selectPreviousItem(e);
				break;
				case Event.KEY_DOWN:
				case Event.KEY_RIGHT:
					this.selectNextItem(e);
				break;
			}
			
			Event.stop(e);
		}	
	},
	
	// MOUSE & KEYBOARD EVENT
	selectCurrentItem: function(e)
	{
		var current = $( this.list ).down('.active');
		this.unselectAll();
		
		if( !Object.isUndefined( current ) )
		{
			var itemid = $( current ).id.replace( this.id + '_ac_item_', '');
			if( !itemid ){ return; }
			
			// Get value
			var value = this.items[ itemid ].replace('&amp;', '&');
			
			if( this.options.multibox )
			{
				// some logic to get current name
				if( $F( this.obj ).indexOf(',') !== -1 )
				{
					var pieces = $F( this.obj ).split(',');
					pieces[ pieces.length - 1 ] = '';

					$( this.obj ).value = pieces.join(',') + ' ';
				}
				else
				{
					$( this.obj ).value = '';
				}
				
				$( this.obj ).value = $F( this.obj ) + value + ', ';
			}
			else
			{
				$( this.obj ).value = value;
				var effect = new Effect.Fade( $(this.list), { duration: 0.3 } );
				this.observing = false;
			}			
		}
		
		$( this.obj ).focus();
	},
	
	// MOUSE EVENT
	selectThisItem: function(e)
	{
		this.unselectAll();
		
		var items = $( this.list ).immediateDescendants();
		var elem = Event.element(e);
		
		// Find the element
		while( !items.include( elem ) )
		{
			elem = elem.up();
		}
		
		$( elem ).addClassName('active');
	},
	
	// KEYBOARD EVENT
	selectPreviousItem: function(e)
	{
		var current = $( this.list ).down('.active');
		this.unselectAll();
		
		if( Object.isUndefined( current ) )
		{
			this.selectFirstItem();
		}
		else
		{
			var prev = $( current ).previous();
			
			if( prev ){
				$( prev ).addClassName('active');
			}
			else
			{
				this.selectLastItem();
			}
		}
	},
	
	// KEYBOARD EVENT
	selectNextItem: function(e)
	{
		// Get the current item
		var current = $( this.list ).down('.active');
		this.unselectAll();
		
		if( Object.isUndefined( current ) ){
			this.selectFirstItem();
		}
		else
		{
			var next = $( current ).next();
			
			if( next ){
				$( next ).addClassName('active');
			}
			else
			{
				this.selectFirstItem();
			}
		}				
	},
	
	// INTERNAL CALL
	selectFirstItem: function()
	{
		if( !$( this.list ).visible() ){ return; }
		this.unselectAll();
		
		$( this.list ).firstDescendant().addClassName('active');		
	},
	
	// INTERNAL CALL
	selectLastItem: function()
	{
		if( !$( this.list ).visible() ){ return; }
		this.unselectAll();
		
		var d = $( this.list ).immediateDescendants();
		var l = d[ d.length -1 ];
		
		if( l )
		{
			$( l ).addClassName('active');
		}
	},
	
	unselectAll: function()
	{
		$( this.list ).childElements().invoke('removeClassName', 'active');
	},
	
	// Ze goggles are blurry!
	timerEventBlur: function(e)
	{
		window.clearTimeout( this.timer );
		this.eventBlur.bind(this).delay( 0.6, e );
	},
	
	// Phew, ze goggles are focussed again
	timerEventFocus: function(e)
	{
		this.timer = this.eventFocus.bind(this).delay(0.4, e);
	},
	
	eventBlur: function(e)
	{
		this.objHasFocus = false;
		
		if( $( this.list ).visible() )
		{
			var effect = new Effect.Fade( $(this.list), { duration: 0.3 } );
		}
	},
	
	eventFocus: function(e)
	{
		if( !this.observing ){ return; }
		this.objHasFocus = true;
		
		// Keep loop going
		this.timer = this.eventFocus.bind(this).delay(0.6, e);
		
		var curValue = this.getCurrentName();
		if( curValue == this.last_string ){ return; }
		
		if( curValue.length < this.options.min_chars ){
			// Hide list if necessary
			if( $( this.list ).visible() )
			{
				var effect = new Effect.Fade( $( this.list ), { duration: 0.3, afterFinish: function(){ $( this.list ).update() }.bind(this) } );
			}
			
			return;
		}
		
		this.last_string = curValue;
		
		// Cached?
		json = this.cacheRead( curValue );
		
		if( json == false ){
			// No results yet, get them
			var request = new Ajax.Request( this.options.url + escape( curValue ),
								{
									method: 'get',
									evalJSON: 'force',
									onSuccess: function(t)
									{
										if( Object.isUndefined( t.responseJSON ) )
										{
											// Well, this is bad.
											Debug.error("Invalid response returned from the server");
											return;
										}
										
										if( t.responseJSON['error'] )
										{
											switch( t.responseJSON['error'] )
											{
												case 'requestTooShort':
													Debug.warn("Server said request was too short, skipping...");
												break;
												default:
													Debug.error("Server returned an error: " + t.responseJSON['error']);
												break;
											}
											
											return false;
										}
										
										if( t.responseText != "[]" )
										{
										
											// Seems to be OK!
											this.cacheWrite( curValue, t.responseJSON );
											this.updateAndShow( t.responseText.evalJSON() );
										}
									}.bind( this )
								}
							);
		}
		else
		{
			this.updateAndShow( json );
		}				
		
		//Debug.write( curValue );
	},
	
	updateAndShow: function( json )
	{
		if( !json ){ return; }
		
		this.updateList( json );

		if( !$( this.list ).visible() && this.objHasFocus )
		{
			Debug.write("Showing");
			var effect = new Effect.Appear( $( this.list ), { duration: 0.3, afterFinish: function(){ this.selectFirstItem(); }.bind(this) } );
		}
	},
	
	cacheRead: function( value )
	{
		if( this.options.global_cache != false )
		{
			if( !Object.isUndefined( this.options.global_cache[ value ] ) ){
				Debug.write("Read from global cache");
				return this.options.global_cache[ value ];
			}
		}
		else
		{
			if( !Object.isUndefined( this.internal_cache[ value ] ) ){
				Debug.write("Read from internal cache");
				return this.internal_cache[ value ];
			}
		}
		
		return false;
	},
	
	cacheWrite: function( key, value )
	{
		if( this.options.global_cache !== false ){
			this.options.global_cache[ key ] = value;
		} else {
			this.internal_cache[ key ] = value;
		}
		
		return true;
	},
	
	getCurrentName: function()
	{
		if( this.options.multibox )
		{
			// some logic to get current name
			if( $F( this.obj ).indexOf(',') === -1 ){
				return $F( this.obj ).strip();
			}
			else
			{
				var pieces = $F( this.obj ).split(',');
				var lastPiece = pieces[ pieces.length - 1 ];
				
				return lastPiece.strip();
			}
		}
		else
		{
			return $F( this.obj ).strip();
		}
	},
	
	buildList: function()
	{
		if( $( this.id + '_ac' ) )
		{
			return;
		}
		
		var ul = this.options.templates.wrap.evaluate({ id: this.id + '_ac' });
		$$('body')[0].insert( {bottom: ul} );
		
		var finalPos = {};
		
		// Position menu to keep it on screen
		var sourcePos = $( this.id ).viewportOffset();
		var sourceDim = $( this.id ).getDimensions();
		var delta = [0,0];
		var parent = null;
		var screenScroll = document.viewport.getScrollOffsets();
		
		if (Element.getStyle( $( this.id ), 'position') == 'absolute')
		{
			parent = element.getOffsetParent();
			delta = parent.viewportOffset();
	    }
	
		finalPos['left'] = sourcePos[0] - delta[0];
		finalPos['top'] = sourcePos[1] - delta[1] + screenScroll.top;
		
		// Now try and keep it on screen
		finalPos['top'] = finalPos['top'] + sourceDim.height;
		
		$( this.id + '_ac' ).setStyle('position: absolute; top: ' + finalPos['top'] + 'px; left: ' + finalPos['left'] + 'px;').hide();
		
		
		this.list = $( this.id + '_ac' );
	},
	
	updateList: function( json )
	{
		if( !json || !$( this.list ) ){ return; }
	
		var newitems ='';
		this.items = $A();
		
		json = $H( json );
		
		json.each( function( item )
			{		
				var li = this.options.templates.item.evaluate({ id: this.id + '_ac_item_' + item.key,
				 												itemid: item.key,
				 												itemvalue: item.value['showas'] || item.value['name'],
				 												img: item.value['img'] || '',
																img_w: item.value['img_w'] || '',
																img_h: item.value['img_h'] || ''
															});
				this.items[ item.key ] = item.value['name'];
	
				newitems = newitems + li;
			}.bind(this)
		);
		
		$( this.list ).update( newitems );
		$( this.list ).immediateDescendants().each( function(elem){
			$( elem ).observe('mouseover', this.selectThisItem.bindAsEventListener(this));
			$( elem ).observe('click', this.selectCurrentItem.bindAsEventListener(this));
			$( elem ).setStyle('cursor: pointer');
		}.bind(this));
		
		if( $( this.list ).visible() )
		{
			this.selectFirstItem();
		}
	}
				
});

/* ===================================================================================================== */
/* Values for the IPB text editor */

IPBoard.prototype.editor_values = $H({
	'templates': 		$A(),
	'colors_perrow': 	8,
	'colors': [ 		'000000' , 'A0522D' , '556B2F' , '006400' , '483D8B' , '000080' , '4B0082' , '2F4F4F' ,
						'8B0000' , 'FF8C00' , '808000' , '008000' ,	'008080' , '0000FF' , '708090' , '696969' ,
						'FF0000' , 'F4A460' , '9ACD32' , '2E8B57' , '48D1CC' , '4169E1' , '800080' , '808080' ,
						'FF00FF' , 'FFA500' , 'FFFF00' , '00FF00' ,	'00FFFF' , '00BFFF' , '9932CC' , 'C0C0C0' ,
						'FFC0CB' , 'F5DEB3' , 'FFFACD' , '98FB98' ,	'AFEEEE' , 'ADD8E6' , 'DDA0DD' , 'FFFFFF'
					],
	
	// You can add new fonts here if you wish, HOWEVER, if you use
	// non-standard fonts, users without those fonts on their computer
	// will not see them. The default list below has the generally accepted
	// safe fonts; add others at your own risk! 
	'primary_fonts': $H({ 	arial:					"Arial",
							arialblack:				"Arial Black",
							arialnarrow:			"Arial Narrow",
							bookantiqua:			"Book Antiqua",
							centurygothic:			"Century Gothic",
							comicsansms:			"Comic Sans MS",
							couriernew:				"Courier New",
							franklingothicmedium:	"Franklin Gothic Medium",
							garamond:				"Garamond",
							georgia:				"Georgia",
							impact:					"Impact",
							lucidaconsole:			"Lucida Console",
							lucidasansunicode:		"Lucida Sans Unicode",
							microsoftsansserif:		"Microsoft Sans Serif",
							palatinolinotype:		"Palatino Linotype",
							tahoma:					"Tahoma",
							timesnewroman:			"Times New Roman",
							trebuchetms:			"Trebuchet MS",
							verdana:				"Verdana"
					}),
	'font_sizes': $A([ 1, 2, 3, 4, 5, 6, 7 ])
				
});

/* ===================================================================================================== */
/* Extended objects */

// Extend RegExp with escape
Object.extend( RegExp, { 
	escape: function(text)
	{
		if (!arguments.callee.sRE)
		{
		   	var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$' ];
		   	//arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')' ); // IMPORTANT: dont use g flag
		   	arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
		}
		return text.replace(arguments.callee.sRE, '\\$1');
	}
});

// Extend String with URL UTF-8 escape
String.prototype.encodeUrl = function()
{
		text = this;
		var regcheck = text.match(/[\x90-\xFF]/g);
		
		if ( regcheck )
		{
			for (var i = 0; i < regcheck.length; i++)
			{
				text = text.replace(regcheck[i], '%u00' + (regcheck[i].charCodeAt(0) & 0xFF).toString(16).toUpperCase());
			}
		}
	
		return escape(text).replace(/\+/g, "%2B").replace(/%20/g, '+').replace(/\*/g, '%2A').replace(/\//g, '%2F').replace(/@/g, '%40');
};

// Extend String with URL UTF-8 escape - duplicated so it can be changed from above
String.prototype.encodeParam = function()
{
		text = this;
		var regcheck = text.match(/[\x90-\xFF]/g);

		if ( regcheck )
		{
			for (var i = 0; i < regcheck.length; i++)
			{
				text = text.replace(regcheck[i], '%u00' + (regcheck[i].charCodeAt(0) & 0xFF).toString(16).toUpperCase());
			}
		}
		
		/* Return just text as it is then encoded by prototype lib */
		return escape(text).replace(/\+/g, "%2B");
};


// Extend Date object with a function to check for DST
Date.prototype.getDST = function()
{
	var beginning	= new Date( "January 1, 2008" );
	var middle		= new Date( "July 1, 2008" );
	var difference	= middle.getTimezoneOffset() - beginning.getTimezoneOffset();
	var offset		= this.getTimezoneOffset() - beginning.getTimezoneOffset();
	
	if( difference != 0 )
	{
		return (difference == offset) ? 1 : 0;
	}
	else
	{
		return 0;
	}
};

/* ==================================================================================================== */
/* IPB3 JS Loader */

var Loader = {
	require: function( name )
	{
		document.write("<script type='text/javascript' src='" + name + ".js'></script>");
	},
	boot: function()
	{
		$A( document.getElementsByTagName("script") ).findAll(
			function(s)
			{
  				return (s.src && s.src.match(/ipb\.js(\?.*)?$/))
			}
		).each( 
			function(s) {
  				var path = s.src.replace(/ipb\.js(\?.*)?$/,'');
  				var includes = s.src.match(/\?.*load=([a-z0-9_,]*)/);
				if( ! Object.isUndefined(includes) && includes != null && includes[1] )
				{
					includes[1].split(',').each(
						function(include)
						{
							if( include )
							{
								Loader.require( path + "ips." + include );
							}
						}
					)
				}
			}
		);
	}
}

/************************************************/
/* IPB3 Javascript								*/
/* -------------------------------------------- */
/* ips.global.js - Global functionality			*/
/* (c) IPS, Inc 2008							*/
/* -------------------------------------------- */
/* Author: Rikki Tissier						*/
/************************************************/

var _global = window.IPBoard;

_global.prototype.global = {
	searchTimer: [],
	searchLastQuery: '',
	rssItems: [],
	reputation: {},
	ac_cache: $H(),
	pageJumps: $H(),
	pageJumpMenus: $H(),
	boardMarkers: $H(),
	
	/*------------------------------*/
	/* Constructor 					*/
	init: function()
	{
		Debug.write("Initializing ips.global.js");
		
		document.observe("dom:loaded", function(){
			ipb.global.initEvents();
		});
	},
	initEvents: function()
	{
		// Delegate our user popup links/warn logs
		ipb.delegate.register(".__user", ipb.global.userPopup);
		ipb.delegate.register(".warn_link", ipb.global.displayWarnLogs);
		ipb.delegate.register(".mini_friend_toggle", ipb.global.toggleFriend);
		
		/*if( ipb.vars['use_live_search'] )
		{
			$('main_search').observe("focus", ipb.global.timer_liveSearch );
			$('main_search').observe("blur", ipb.global.timer_hideLiveSearch );
			$('main_search').writeAttribute({autocomplete: "off"});
		}*/
		
		if( $('rss_feed') ){
			ipb.global.buildRSSmenu();
		}
		
		if( $('newSkin') || $('newLang') ){
			ipb.global.setUpSkinLang();
		}
		
		if( $('pm_notification') ){
			new Effect.Parallel([
				new Effect.Appear( $('pm_notification') ),
				new Effect.BlindDown( $('pm_notification') )
			], { duration: 0.5 } );
		}
		
		if( $('close_pm_notification') ){
			$('close_pm_notification').observe('click', ipb.global.closePMpopup );
		}
		
		ipb.global.buildPageJumps();
		
		ipb.delegate.register('.bbc_spoiler_show', ipb.global.toggleSpoiler);
		ipb.delegate.register('a[rel~="external"]', ipb.global.openNewWindow );
	},
	
	userPopup: function( e, elem )
	{
		Event.stop(e);
		
		var sourceid = elem.identify();
		var user = $( elem ).className.match('__id([0-9]+)');
		var fid  = $( elem ).className.match('__fid([0-9]+)');

		if( user == null || Object.isUndefined( user[1] ) ){ Debug.error("Error showing popup"); return; }
		var popid = 'popup_' + user[1] + '_user';
		var _url  = ipb.vars['base_url'] + '&app=members&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=card&mid=' + user[1];
		
		Debug.write( fid );
		
		if ( fid != null && !Object.isUndefined( fid[1] ) && fid[1] )
		{
			_url += '&f=' + fid[1];
		}

		Debug.write( _url );
		ipb.namePops[ user ]	 = new ipb.Popup( popid, {
			 												type: 'balloon',
			 												ajaxURL: _url,
			 												stem: true,
															hideAtStart: false,
			 												attach: { target: elem, position: 'auto' },
			 												w: '400px'
														});
	},
	
	/* SKINNOTE: Needs cleaning up */
	displayWarnLogs: function( e, elem )
	{		
		mid = elem.id.match('warn_link_([0-9a-z]+)_([0-9]+)')[2];
		if( Object.isUndefined(mid) ){ return; }
		
		if( parseInt(mid) == 0 ){
			return false;
		}
		
		Event.stop(e);
		
		var _url 		= ipb.vars['base_url'] + '&app=members&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=warn&do=view&mid=' + mid;
		warnLogs = new ipb.Popup( 'warnLogs', {type: 'pane', modal: false, w: '500px', h: '500px', ajaxURL: _url, hideAtStart: false, close: '.cancel' } );
		
	},
	
	/* ------------------------------ */
	/**
	 * Toggle mini friend button
	 * 
	 * @param	{event}		e		The event
	 * @param	{int}		id		Member id
	*/
	toggleFriend: function(e, elem)
	{
		Event.stop(e);
		
		// Get ID of friend
		var id = $( elem ).id.match('friend_(.*)_([0-9]+)');
		if( Object.isUndefined( id[2] ) ){ return; }
		
		var isFriend = ( $(elem).hasClassName('is_friend') ) ? 1 : 0;
		var urlBit = ( isFriend ) ? 'remove' : 'add';
		
		var url = ipb.vars['base_url'] + "app=members&section=friends&module=ajax&do=" + urlBit + "&member_id=" + id[2] + "&md5check=" + ipb.vars['secure_hash'];
		
		// Send
		new Ajax.Request( 	url,
			 				{
								method: 'get',
								onSuccess: function(t)
								{
									switch( t.responseText )
									{
										case 'pp_friend_timeflood':
											alert( ipb.lang['cannot_readd_friend'] );
											Event.stop(e);
											break;
										case "pp_friend_already":
											alert( ipb.lang['friend_already'] );
											Event.stop(e);
											break;
										case "error":
											return true;
											break;
										default:
											
											var newIcon = ( isFriend ) ? ipb.templates['m_add_friend'].evaluate({ id: id[2]}) : ipb.templates['m_rem_friend'].evaluate({ id: id[2] });
											 
											// Find all friend links for this user
											var friends = $$('.mini_friend_toggle').each( function( fr ){
												if( $(fr).id.endsWith('_' + id[2] ) )
												{
													if ( isFriend ) {
														$(fr).removeClassName('is_friend').addClassName('is_not_friend').update( newIcon );
													} else {
														$(fr).removeClassName('is_not_friend').addClassName('is_friend').update( newIcon );
													}
												}											
											});
											
											new Effect.Highlight( $( elem ), { startcolor: ipb.vars['highlight_color'] } );
											
											// Fire an event so we can update if necessary
											document.fire('ipb:friendRemoved', { friendID: id[2] } );
											Event.stop(e);
										break;
									}
								}
							}
						);
	},
	
	/**
	* MATT
	* Toggle spammer
	*/
	toggleFlagSpammer: function( memberId, flagStatus )
	{
		if ( flagStatus == true )
		{
			if( confirm( ipb.lang['set_as_spammer'] ) )
			{
				var tid	= 0;
				var fid	= 0;
				var sid	= 0;
				
				if( typeof(ipb.topic) != 'undefined' )
				{
					tid = ipb.topic.topic_id;
					fid = ipb.topic.forum_id;
					sid = ipb.topic.start_id;
				}

				window.location = ipb.vars['base_url'] + 'app=forums&module=moderate&section=moderate&do=setAsSpammer&member_id=' + memberId + '&t=' + tid + '&f=' + fid + '&st=' + sid + '&auth_key=' + ipb.vars['secure_hash'];
				return false;
			}
			else
			{
				return false;
			}
		}
		else
		{
			alert( ipb.lang['is_spammer'] );
			return false;
		}
	},
	
	/* ------------------------------ */
	/**
	 * Toggle spoiler
	 * 
	 * @param	{event}		e		The event
	*/
	toggleSpoiler: function(e, button)
	{
		Event.stop(e);
		
		var returnvalue = $(button).up().down('.bbc_spoiler_wrapper').down('.bbc_spoiler_content').toggle();
		
		if( returnvalue.visible() )
		{
			$(button).value = 'Hide';
		}
		else
		{
			$(button).value = 'Show';
		}
	},
	
	/* ------------------------------ */
	/**
	 * Adds some events for skin/language changer
	*/
	setUpSkinLang: function()
	{
		if( $('newSkin') )
		{
			var form = $('newSkin').up('form');
			if( form )
			{
				if( $('newSkinSubmit') ){ $('newSkinSubmit').hide(); }
				$('newSkin').observe('change', function(e)
				{
					form.submit();
					return true;
				});
			}
		}
		if( $('newLang') )
		{
			var form1 = $('newLang').up('form');
			if( form1 )
			{
				if( $('newLangSubmit') ){ $('newLangSubmit').hide(); }
				$('newLang').observe('change', function(e)
				{
					form1.submit();
					return true;
				});
			}
		}
	},
					
	/* ------------------------------ */
	/**
	 * Builds the popup menu for RSS feeds
	*/
	buildRSSmenu: function()
	{
		// Get all link tags
		$$('link').each( function(link)
		{
			if( link.readAttribute('type') == "application/rss+xml" )
			{
				ipb.global.rssItems.push( ipb.templates['rss_item'].evaluate( { url: link.readAttribute('href'), title: link.readAttribute('title') } ) );
			}
		});
		
		if( ipb.global.rssItems.length > 0 )
		{
			rssmenu = ipb.templates['rss_shell'].evaluate( { items: ipb.global.rssItems.join("\n") } );
			$( 'rss_feed' ).insert( { after: rssmenu } );
			new ipb.Menu( $( 'rss_feed' ), $( 'rss_menu' ) );
		}
		else
		{
			$('rss_feed').hide();
		}
	},
	
	/* ------------------------------ */
	/**
	 * Hides the PM notification box
	 * 
	 * @param	{event}		e		The event
	*/
	closePMpopup: function(e)
	{
		if( $('pm_notification') )
		{
			new Effect.Parallel([
				new Effect.Fade( $('pm_notification') ),
				new Effect.BlindUp( $('pm_notification') )
			], { duration: 0.5 } );
		}
		
		Event.stop(e);
	},
	
	/* ------------------------------ */
	/**
	 * Initializes GD image
	 *
	 * @param	{element}	elem	The GD image element
	*/
	initGD: function( elem )
	{
		if( !$(elem) ){ return; }
		$(elem).observe('click', ipb.global.generateNewImage);
		
		if( $('gd-image-link') )
		{
			$('gd-image-link').observe('click', ipb.global.generateNewImage);
		}
	},

	/* ------------------------------ */
	/**
	 * Simulate clicking the image
	 *
	 * @param	{element}	elem	The GD image element
	*/
	generateImageExternally: function( elem )
	{
		if( !$(elem) ){ return; }
		
		$(elem).observe('click', ipb.global.generateNewImage);
	},	
	
	/* ------------------------------ */
	/**
	 * Click event for generating new GD image
	 * 
	 * @param	{event}		e	The event
	*/
	generateNewImage: function(e)
	{
		img = Event.findElement( e, 'img' );
		Event.stop(e);
		if( img == document ){ return; }
		
		// Coming from the link?		
		if( !img )
		{
			anchor	= Event.findElement( e, 'a' );
			
			if( anchor )
			{
				img		= anchor.up().down('img');
			}
		}
		
		oldSrc = img.src.toQueryParams();
		oldSrc = $H( oldSrc ).toObject();
		
		if( !oldSrc['captcha_unique_id'] ){	Debug.error("No captcha ID found"); }
		
		// Get new image
		new Ajax.Request( 
			ipb.vars['base_url'] + "app=core&module=global&section=captcha&do=refresh&captcha_unique_id=" + oldSrc['captcha_unique_id'] + '&secure_key=' + ipb.vars['secure_hash'],
			{
				method: 'get',
				onSuccess: function(t)
				{
					//Change src
					oldSrc['captcha_unique_id'] = t.responseText;
					img.writeAttribute( { src: ipb.vars['base_url'] + $H( oldSrc ).toQueryString() } );
					$F('regid').value = t.responseText;
				}
			}
		);
	},
	
	/* ------------------------------ */
	/**
	 * Registers a reputation toggle on the page
	 * 
	 * @param	{int}		id		The element that wraps rep
	 * @param	{string}	url		The URL to ping
	 * @param	{int}		rating	The current rep rating
	*/
	registerReputation: function( id, url, rating )
	{
		if( !$( id ) ){ return; }
				
		// Find rep up
		var rep_up = $( id ).down('.rep_up');
		var rep_down = $( id ).down('.rep_down');
		var sendUrl = ipb.vars['base_url'] + '&app=core&module=ajax&section=reputation&do=add_rating&app_rate=' + url.app + '&type=' + url.type + '&type_id=' + url.typeid + '&secu