PageSourceSearch

https://www.mtg-forum.de/public/js/ipb.js?ipbv=f41ec840ce1bcc2e20d…&load=quickpm,hovercard,memberlist

js mtg-forum.de collected 2026-09-24 09:21:52 UTC 127,863 bytes, 4,750 lines download raw bytes

1//************************************************/
2/* IPB3 Javascript								*/
3/* -------------------------------------------- */
4/* ipb.js - Global code							*/
5/* (c) IPS, Inc 2008							*/
6/* -------------------------------------------- */
7/* Author: Rikki Tissier						*/
8/************************************************/
9
10/* ===================================================================================================== */
11/* IPB3 JS Debugging */
12var USE_RTE = 0;
13var Debug = {
14	write: function( text ){
15		if( jsDebug && !Object.isUndefined(window.console) ){
16			console.log( text );
17		}
18		/*else if( jsDebug )
19		{
20			if( !$('_inline_debugging') ){
21				var _inline_debug =  new Element('div', { id: '_inline_debugging' }).setStyle('background: rgba(0,0,0,0.7); color: #fff; padding: 10px; width: 97%; height: 150px; position: absolute; bottom: 0; overflow: auto; z-index: 50000').show();
22				
23				if( !Object.isUndefined( $$('body')[0] ) ){
24					$$('body')[0].insert( _inline_debug );
25				}
26			}
27			
28			try {
29				$('_inline_debugging').innerHTML += "<br />" + text;
30			} catch(err){}
31		}*/
32	},
33	dir: function( values ){
34		if( jsDebug && !Object.isUndefined(window.console) && ! Prototype.Browser.IE && ! Prototype.Browser.Opera ){
35			console.dir( values );
36		}
37	},
38	error: function( text ){
39		if( jsDebug && !Object.isUndefined(window.console) ){
40			console.error( text );
41		}
42	},
43	warn: function( text ){
44		if( jsDebug && !Object.isUndefined(window.console) ){
45			console.warn( text );
46		}
47	},
48	info: function( text ){
49		if( jsDebug && !Object.isUndefined(window.console) ){
50			console.info( text );
51		}
52	}
53};
54
55/* Set up version specifics */
56Prototype.Browser.IE6 = Prototype.Browser.IE && parseInt(navigator.userAgent.
56substring(navigator.userAgent.indexOf("MSIE")+5)) == 6;
57Prototype.Browser.IE7 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 7;
58Prototype.Browser.IE8 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 8;
59Prototype.Browser.IE9 = Prototype.Browser.IE && parseInt(navigator.userAgent.substring(navigator.userAgent.indexOf("MSIE")+5)) == 9;
60
61/* Add in stuff prototype does not include */
62Prototype.Browser.Chrome = Prototype.Browser.WebKit && ( navigator.userAgent.indexOf('Chrome/') > -1 );
63
64/* Older browsers do not have String.trim() */
65if ( !String.prototype.trim )
66{
67	String.prototype.trim = function(){ return this.replace(/^\s+|\s+$/g, ''); };
68}
69
70/* ===================================================================================================== */
71/* OVERWRITE getOffsetParent TO FIX TD ISSUE */
72
73function isBody(element) {
74  return element.nodeName.toUpperCase() === 'BODY';
75}
76
77function isHtml(element) {
78  return element.nodeName.toUpperCase() === 'HTML';
79}
80
81function isDocument(element) {
82  return element.nodeType === Node.DOCUMENT_NODE;
83}
84
85function isDetached(element) {
86  return element !== document.body &&
87   !Element.descendantOf(element, document.body);
88}
89
90Element.Methods.getOffsetParent = function(element) {
91	element = $(element);
92	
93	if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element))
94	return $(document.body);
95	
96	if( Prototype.Browser.IE ){
97		if (element.offsetParent && element.offsetParent != document.body && Element.getStyle( element.offsetParent, 'position' ) != 'static') return $(element.offsetParent);
98		if (element == document.body) return $(element);
99	} else {	
100		var isInline = (Element.getStyle(element, 'display') === 'inline');
101		if (!isInline && element.offsetParent && Element.getStyle(element.offsetParent,'position') != 'static') return $(element.offsetParent);
102	}
103
104	while ((element = element.parentNode) && element !== document.body) {
105		if (Element.getStyle(element, 'position') !== 'static') {
106			return isHtml(element) ? $(document.body) : $(element);
107		}
108	}
109
110	return $(document.body);
111}
112
113/*Event.observe( window, 'load', function(e){
114	Element.Methods.getOffsetParent = function( element ){
115		//alert( "Using overloaded getOffsetParent" );
116		if (element.offsetParent && element.offsetParent != document.body) return $(element.offsetParent);
117		if (element == document.body) return $(element);
118
119		while ((element = element.parentNode) && element != document.body)
120		  if (Element.getStyle(element, 'position') != 'static')
121		    return $(element);
122
123		return $(document.body);
124	};
125});
126
127function _getOffsetParent( element )
128{
129	//alert( "Using overloaded getOffsetParent" );
130	if (element.offsetParent && element.offsetParent != document.body) return $(element.offsetParent);
131	if (element == document.body) return $(element);
132
133	while ((element = element.parentNode) && element != document.body )
134	  if (Element.getStyle(element, 'position') != 'static')
135	    return $(element);
136
137	return $(document.body);
138}*/
139
140/* ===================================================================================================== */
141/* MAIN ROUTINE */
142
143window.IPBoard = Class.create({
144	namePops: [],
145	topicPops: [],
146	vars: [],
147	lang: [],
148	templates: [],
149	editors: $A(),
150	initDone: false,
151	
152	initialize: function()
153	{
154		Debug.write("IPB js is loading...");
155		
156		document.observe("dom:loaded", function(){
157			
158			this.Cookie.init();
159			// Show a little loading graphic
160			Ajax.Responders.register({
161			  onLoading: function( handler ) {
162				if( !Object.isUndefined( handler['options']['hideLoader'] ) && handler['options']['hideLoader'] != false ){
163					return;
164				}
165				
166			    if( !$('ajax_loading') ){
167					if( !ipb.templates['ajax_loading'] ){ return; }
168					$('ipboard_body').insert( ipb.templates['ajax_loading'] );
169				}
170				
171				var effect = new Effect.Appear( $('ajax_loading'), { duration: 0.2 } );
172			  },
173			  onComplete: function() {
174			
175				if( !$('ajax_loading') || !$('ajax_loading').visible() ){ return; }
176			    var effect = new Effect.Fade( $('ajax_loading'), { duration: 0.2 } );
177			    
178			    if ( ! Object.isUndefined( ipb.hoverCard ) ){
179			    	ipb.hoverCardRegister.postAjaxInit();
180				}
181			    
182			    /* General links to functions */
183				$$("[data-clicklaunch]").invoke('clickLaunch');
184				
185				/* Quote functions */
186				ipb.global.parseQuoteBoxes();
187				ipb.global.removeLinkedLightbox();
188			  },
189			  onSuccess: function() {
190			    if ( ! Object.isUndefined( ipb.hoverCard ) ){
191			    	ipb.hoverCardRegister.postAjaxInit();
192				}
193			  },
194			  onFailure: function( t )
195			  {
196				 if( !$('ajax_loading') || !$('ajax_loading').visible() ){ return; }
197				 var effect = new Effect.Fade( $('ajax_loading'), { duration: 0.2 } );
198				  
199				 if ( ! Object.isUndefined( ipb.global ) )
200				 {
201					 ipb.global.showInlineNotification( ipb.lang['ajax_failure'] );
202				 }
203				  
204			  },
205			  onException: function( t, exception )
206			  {
207				  if( !$('ajax_loading') || !$('ajax_loading').visible() ){ return; }
208				  var effect = new Effect.Fade( $('ajax_loading'), { duration: 0.2 } );
209				  
210				  Debug.error( exception );
211				  
212				  if ( ! Object.isUndefined( ipb.global ) )
213				  {
214					  //ipb.global.showInlineNotification( ipb.lang['ajax_failure'] );
215				  }
216			  }
217			});
218			
219			// Initialize our delegation manager
220			ipb.delegate.initialize();			
221			ipb.initDone = true;
222			
223		}.bind(this));
224	},
225	
226	positionCenter: function( elem, dir )
227	{
228		if( !$(elem) ){ return; }
229		elem_s = $(elem).getDimensions();
230		window_s = document.viewport.getDimensions();
231		window_offsets = document.viewport.getScrollOffsets();
232
233		center = { 	left: ((window_s['width'] - elem_s['width']) / 2),
234					 top: ((window_s['height'] - elem_s['height']) / 2)
235				};
236		
237		if( typeof(dir) == 'undefined' || ( dir != 'h' && dir != 'v' ) )
238		{
239			$(elem).setStyle('top: ' + center['top'] + 'px; left: ' + center['left'] + 'px');
240		}
241		else if( dir == 'h' )
242		{
243			$(elem).setStyle('left: ' + center['left'] + 'px');
244		}
245		else if( dir == 'v' )
246		{
247			$(elem).setStyle('top: ' + center['top'] + 'px');
248		}
249		
250		$(elem).setStyle('position: fixed');
251	},
252	showModal: function()
253	{
254		if( !$('ipb_modal') )
255		{
256			this.createModal();
257		}
258		this.modal.show();
259	},
260	hideModal: function()
261	{
262		if( !$('ipb_modal') ){ return; }
263		this.modal.hide();		
264	},
265	createModal: function()
266	{
267		this.modal = new Element('div', { id: 'ipb_modal' } ).hide().addClassName('modal');
268		this.modal.setStyle("width: 100%; height: 100%; position: fixed; top: 0px; left: 0px; overflow: hidden; z-index: 1000; opacity: 0.2");
269		$('ipboard_body').insert({bottom: this.modal});
270	},
271	editorInsert: function( content, editorid )
272	{
273		// If no editor id supplied, lets use the first one
274		if( !editorid )	{
275			var editor = ipb.textEditor.getEditor();
276		} else {
277			var editor = ipb.textEditor.getEditor(editorid);
278		}
279		
280		if( Object.isUndefined( editor ) )
281		{
282			/* Get current */
283			var editor = ipb.textEditor.getEditor();
284		}
285		
286		editor.insert( content );
287	}
288});
289
290/* ===================================================================================================== */
291/* IPB3 Delegation manager */
292/* Simple class that allows us to specify css selectors and an associated function to run */
293/* when an appropriate element is clicked */
294
295IPBoard.prototype.delegate = {
296	store: $A(),
297	
298	initialize: function()
299	{
300		document.observe('click', function(e){
301
302			if( Event.isLeftClick(e) || Prototype.Browser.IE || ipb.vars['is_touch'] ) // IE doesnt provide isLeftClick info for click event, touch devices either
303			{
304				var elem = null;
305				var handler = null;
306			
307				var target = ipb.delegate.store.find( function(item){
308					elem = e.findElement( item['selector'] );
309					if( elem ){
310						handler = item;
311						return true;
312					} else {
313						return false;
314					}
315				});
316			
317				if( !Object.isUndefined( target ) )
318				{				
319					if( handler )
320					{
321						Debug.write("Firing callback for selector " + handler['selector'] );
322						handler['callback']( e, elem, handler['params'] );
323					}
324				}
325			}
326        });
327	},
328	
329	register: function( selector, callback, params )
330	{
331		ipb.delegate.store.push( { selector: selector, callback: callback, params: params } );
332	}
333};
334
335/* ===================================================================================================== */
336/* IPB3 Cookies */
337
338/* Meow */
339IPBoard.prototype.Cookie = {
340	store: [],
341	initDone: false,
342	
343	set: function( name, value, sticky )
344	{
345		var expires = '';
346		var path = '/';
347		var domain = '';
348		
349		if( !name )
350		{
351			return;
352		}
353		
354		if( sticky )
355		{	
356			if( sticky == 1 )
357			{
358				expires = "; expires=Wed, 1 Jan 2020 00:00:00 GMT";
359			}
360			else if( sticky == -1 ) // Delete
361			{
362				expires = "; expires=Thu, 01-Jan-1970 00:00:01 GMT";
363			}
364			else if( sticky.length > 10 )
365			{
366				expires = "; expires=" + sticky;
367			}
368		}
369		if( ipb.vars['cookie_domain'] )
370		{
371			domain = "; domain=" + ipb.vars['cookie_domain'];
372		}
373		if( ipb.vars['cookie_path'] )
374		{
375			path = ipb.vars['cookie_path'];
376		}
377				
378		document.cookie = ipb.vars['cookie_id'] + name + "=" + escape( value ) + "; path=" + path + expires + domain + ';';
379		
380		ipb.Cookie.store[ name ] = value;
381		
382		Debug.write( "Set cookie: " + ipb.vars['cookie_id'] + name + "=" + value + "; path=" + path + expires + domain + ';' );
383	},
384	get: function( name )
385	{
386		/* Init done yet? */
387		if ( ipb.Cookie.initDone !== true )
388		{
389			ipb.Cookie.init();
390		}
391		
392		if( ipb.Cookie.store[ name ] )
393		{
394			return ipb.Cookie.store[ name ];
395		}
396		
397		return '';
398	},
399	doDelete: function( name )
400	{
401		Debug.write("Deleting cookie " + name);
402		ipb.Cookie.set( name, '', -1 );
403	},
404	init: function()
405	{
406		// Already init?
407		if ( ipb.Cookie.initDone )
408		{
409			return true;
410		}
411		
412		// Init cookies by pulling in document.cookie
413		skip = ['session_id', 'ipb_admin_session_id', 'member_id', 'pass_hash'];
414		cookies = $H( document.cookie.replace(" ", '').toQueryParams(";") );
415	
416		if( cookies )
417		{
418			cookies.each( function(cookie){
419				cookie[0] = cookie[0].strip();
420				
421				if( ipb.vars['cookie_id'] != '' )
422				{
423					if( !cookie[0].startsWith( ipb.vars['cookie_id'] ) )
424					{
425						return;
426					}
427					else
428					{
429						cookie[0] = cookie[0].replace( ipb.vars['cookie_id'], '' );
430					}
431				}
432				
433				if( skip[ cookie[0] ] )
434				{
435					return;
436				}
437				else
438				{
439					ipb.Cookie.store[ cookie[0] ] = unescape( cookie[1] || '' );
440					Debug.write( "Loaded cookie: " + cookie[0] + " = " + cookie[1] );
441				}				
442			});
443		}
444		
445		ipb.Cookie.initDone = true;	
446	}
447};
448
449/* ===================================================================================================== */
450/* Form validation */
451
452IPBoard.prototype.validate = {
453	// Checks theres actually a value
454	isFilled: function( elem )
455	{
456		if( !$( elem ) ){ return null; }
457		return !$F(elem).blank();
458	},
459	isNumeric: function( elem )
460	{
461		if( !$( elem ) ){ return null; }
462		return $F(elem).match( /^[\d]+?$/ );
463	},
464	isMatching: function( elem1, elem2 )
465	{
466		if( !$( elem1 ) || !$( elem2 ) ){ return null; }
467		return $F(elem1) == $F(elem2);
468	},
469	email: function( elem )
470	{
471		if( !$( elem ) ){ return null; }
472		if( $F( elem ).match( /^.+@.+\..{2,4}$/ ) ){
473			return true;
474		} else {
475			return false;
476		}
477	}
478};
479
480/* ===================================================================================================== */
481/* AUTOCOMPLETE */
482
483IPBoard.prototype.Autocomplete = Class.create( {
484	
485	initialize: function(id, options)
486	{
487		this.id = $( id ).id;
488		this.timer = null;
489		this.last_string = '';
490		this.internal_cache = $H();
491		this.pointer = 0;
492		this.items = $A();
493		this.observing = true;
494		this.objHasFocus = null;
495		this.options = Object.extend({
496			min_chars: 3,
497			multibox: false,
498			global_cache: false,
499			goToUrl: false,
500			classname: 'ipb_autocomplete',
501			templates: 	{ 
502							wrap: new Template("<ul id='#{id}'></ul>"),
503							item: new Template("<li id='#{id}' data-url='#{url}'>#{itemvalue}</li>")
504						}
505		}, arguments[1] || {});
506		
507		//-----------------------------------------
508		
509		if( !$( this.id ) ){
510			Debug.error("Invalid textbox ID");
511			return false;
512		}
513		
514		this.obj = $( this.id );
515		
516		if( !this.options.url )
517		{
518			Debug.error("No URL specified for autocomplete");
519			return false;
520		}
521		
522		$( this.obj ).writeAttribute('autocomplete', 'off');
523		
524		this.buildList();
525		
526		// Observe keypress
527		$( this.obj ).observe('focus', this.timerEventFocus.bindAsEventListener( this ) );
528		$( this.obj ).observe('blur', this.timerEventBlur.bindAsEventListener( this ) );
529		$( this.obj ).observe('keydown', this.eventKeypress.bindAsEventListener( this ) );
530	},
531	
532	eventKeypress: function(e)
533	{	
534		if( ![ Event.KEY_TAB, Event.KEY_UP, Event.KEY_DOWN, Event.KEY_LEFT, Event.KEY_RIGHT, Event.KEY_RETURN ].include( e.keyCode ) ){
535			return; // Not interested in anything else
536		}
537		
538		// & and up key are both keycode 38. So if we're holding shift, ignore this
539		console.log( e.shiftKey );
540		if ( e.shiftKey === true ) {
541			return;
542		}
543		
544		if( $( this.list ).visible() )
545		{
546			switch( e.keyCode )
547			{
548				case Event.KEY_TAB:
549				case Event.KEY_RETURN:
550					this.selectCurrentItem(e);
551				break;
552				case Event.KEY_UP:
553				case Event.KEY_LEFT:
554					this.selectPreviousItem(e);
555				break;
556				case Event.KEY_DOWN:
557				case Event.KEY_RIGHT:
558					this.selectNextItem(e);
559				break;
560			}
561			
562			Event.stop(e);
563		}
564	},
565	
566	// MOUSE & KEYBOARD EVENT
567	selectCurrentItem: function(e)
568	{
569		var current = $( this.list ).down('.active');
570		this.unselectAll();
571		
572		if( !Object.isUndefined( current ) )
573		{
574			var itemid = $( current ).id.replace( this.id + '_ac_item_', '');
575			if( !itemid ){ return; }
576			
577			// Go to URL?
578			if( this.options.goToUrl && $( current ).readAttribute('data-url') )
579			{
580				window.location = $( current ).readAttribute('data-url');
581				return false;
582			}
583
584			// Get value
585			var value = this.items[ itemid ]
586				.replace('&amp;', '&')
587				.replace( /&#39;/g, "'" )
588				.replace( /&gt;/g, '>' )
589				.replace( /&lt;/g, '<' )
590				.replace( /&#33;/g, '!' );
591
592			if( this.options.multibox )
593			{
594				// some logic to get current name
595				if( $F( this.obj ).indexOf(',') !== -1 )
596				{
597					var pieces = $F( this.obj ).split(',');
598					pieces[ pieces.length - 1 ] = '';
599
600					$( this.obj ).value = pieces.join(',') + ' ';
601				}
602				else
603				{
604					$( this.obj ).value = '';
605					$( this.obj ).focus();
606				}
607				
608				$( this.obj ).value = $F( this.obj ) + value + ', ';
609			}
610			else
611			{
612				$( this.obj ).value = value;
613				
614				var effect = new Effect.Fade( $(this.list), { duration: 0.3 } );
615				//this.observing = false;
616			}	
617		}
618		
619		$( this.obj ).focus();
620		
621		/* Stop cursor jumping back when adding input */
622		if ( Prototype.Browser.IE )
623		{
624			if ( $( this.obj ).createTextRange )
625			{
626				var r = $( this.obj ).createTextRange();
627				r.moveStart("character", $( this.obj ).value.length);
628				r.select();
629			}
630        }
631	},
632	
633	// MOUSE EVENT
634	selectThisItem: function(e)
635	{
636		this.unselectAll();
637		
638		var items = $( this.list ).immediateDescendants();
639		var elem = Event.element(e);
640		
641		// Find the element
642		while( !items.include( elem ) )
643		{
644			elem = elem.up();
645		}
646		
647		$( elem ).addClassName('active');
648	},
649	
650	// KEYBOARD EVENT
651	selectPreviousItem: function(e)
652	{
653		var current = $( this.list ).down('.active');
654		this.unselectAll();
655		
656		if( Object.isUndefined( current ) )
657		{
658			this.selectFirstItem();
659		}
660		else
661		{
662			var prev = $( current ).previous();
663			
664			if( prev ){
665				$( prev ).addClassName('active');
666			}
667			else
668			{
669				this.selectLastItem();
670			}
671		}
672	},
673	
674	// KEYBOARD EVENT
675	selectNextItem: function(e)
676	{
677		// Get the current item
678		var current = $( this.list ).down('.active');
679		this.unselectAll();
680		
681		if( Object.isUndefined( current ) ){
682			this.selectFirstItem();
683		}
684		else
685		{
686			var next = $( current ).next();
687			
688			if( next ){
689				$( next ).addClassName('active');
690			}
691			else
692			{
693				this.selectFirstItem();
694			}
695		}
696	},
697	
698	// INTERNAL CALL
699	selectFirstItem: function()
700	{
701		if( !$( this.list ).visible() ){ return; }
702		this.unselectAll();
703		
704		$( this.list ).firstDescendant().addClassName('active');
705	},
706	
707	// INTERNAL CALL
708	selectLastItem: function()
709	{
710		if( !$( this.list ).visible() ){ return; }
711		this.unselectAll();
712		
713		var d = $( this.list ).immediateDescendants();
714		var l = d[ d.length -1 ];
715		
716		if( l )
717		{
718			$( l ).addClassName('active');
719		}
720	},
721	
722	unselectAll: function()
723	{
724		$( this.list ).childElements().invoke('removeClassName', 'active');
725	},
726	
727	// Ze goggles are blurry!
728	timerEventBlur: function(e)
729	{ 
730		window.clearTimeout( this.timer );
731		this.eventBlur.bind(this).delay( 0.6, e );
732	},
733	
734	// Phew, ze goggles are focussed again
735	timerEventFocus: function(e)
736	{
737		this.timer = this.eventFocus.bind(this).delay(0.4, e);
738	},
739	
740	eventBlur: function(e)
741	{
742		this.objHasFocus = false;
743		
744		if( $( this.list ).visible() )
745		{
746			var effect = new Effect.Fade( $(this.list), { duration: 0.3 } );
747		}
748	},
749	
750	eventFocus: function(e)
751	{
752		if( !this.observing ){ Debug.write("Not observing keypress"); return; }
753		this.objHasFocus = true;
754		
755		// Keep loop going
756		this.timer = this.eventFocus.bind(this).delay(0.6, e);
757		
758		var curValue = this.getCurrentName();
759		if( curValue == this.last_string ){ return; }
760		
761		if( curValue.length < this.options.min_chars ){
762			// Hide list if necessary
763			if( $( this.list ).visible() )
764			{
765				var effect = new Effect.Fade( $( this.list ), { duration: 0.3, afterFinish: function(){ $( this.list ).update(); }.bind(this) } );
766			}
767			
768			return;
769		}
770		
771		this.last_string = curValue;
772		
773		// Cached?
774		json = this.cacheRead( curValue );
775		
776		if( json == false ){
777			// No results yet, get them
778			var request = new Ajax.Request( this.options.url + encodeURIComponent( curValue ),
779								{
780									method: 'get',
781									evalJSON: 'force',
782									onSuccess: function(t)
783									{
784										if( Object.isUndefined( t.responseJSON ) )
785										{
786											// Well, this is bad.
787											Debug.error("Invalid response returned from the server");
788											return;
789										}
790										
791										if( t.responseJSON['error'] )
792										{
793											switch( t.responseJSON['error'] )
794											{
795												case 'requestTooShort':
796													Debug.warn("Server said request was too short, skipping...");
797												break;
798												default:
799													Debug.error("Server returned an error: " + t.responseJSON['error']);
800												break;
801											}
802											
803											return false;
804										}
805										
806										if( t.responseText != "[]" )
807										{
808										
809											// Seems to be OK!
810											this.cacheWrite( curValue, t.responseJSON );
811											this.updateAndShow( t.responseJSON );
812										}
813									}.bind( this )
814								}
815							);
816		}
817		else
818		{
819			this.updateAndShow( json );
820		}
821		
822		//Debug.write( curValue );
823	},
824	
825	updateAndShow: function( json )
826	{
827		if( !json ){ return; }
828		
829		this.updateList( json );
830
831		if( !$( this.list ).visible() && this.objHasFocus )
832		{
833			Debug.write("Showing");
834			var effect = new Effect.Appear( $( this.list ), { duration: 0.3, afterFinish: function(){ this.selectFirstItem(); }.bind(this) } );
835		}
836	},
837	
838	cacheRead: function( value )
839	{
840		if( this.options.global_cache != false )
841		{
842			if( !Object.isUndefined( this.options.global_cache.get( value ) ) ){
843				Debug.write("Read from global cache");
844				return this.options.global_cache.get( value );
845			}
846		}
847		else
848		{
849			if( !Object.isUndefined( this.internal_cache.get( value ) ) ){
850				Debug.write("Read from internal cache");
851				return this.internal_cache.get( value );
852			}
853		}
854		
855		return false;
856	},
857	
858	cacheWrite: function( key, value )
859	{
860		if( this.options.global_cache !== false ){
861			this.options.global_cache.set( key, value );
862		} else {
863			this.internal_cache.set( key, value );
864		}
865		
866		return true;
867	},
868	
869	getCurrentName: function()
870	{
871		if( this.options.multibox )
872		{
873			// some logic to get current name
874			if( $F( this.obj ).indexOf(',') === -1 ){
875				return $F( this.obj ).strip();
876			}
877			else
878			{
879				var pieces = $F( this.obj ).split(',');
880				var lastPiece = pieces[ pieces.length - 1 ];
881				
882				return lastPiece.strip();
883			}
884		}
885		else
886		{
887			return $F( this.obj ).strip();
888		}
889	},
890	
891	buildList: function()
892	{
893		if( $( this.id + '_ac' ) )
894		{
895			return;
896		}
897		
898		var finalPos = {};
899		
900		// Position menu to keep it on screen
901		var sourcePos = $( this.id ).viewportOffset();
902		var sourceDim = $( this.id ).getDimensions();
903		var delta = [0,0];
904		var parent = null;
905		var screenScroll = document.viewport.getScrollOffsets();
906
907		var ul = this.options.templates.wrap.evaluate({ id: this.id + '_ac' });
908		
909		/* In a modal pop up? */
910		var test = $( this.id ).up('.popupWrapper');
911		
912		if ( ! Object.isUndefined( test ) && test.getStyle('position') == 'fixed' )
913		{
914			$(this.id).up().insert( {bottom: ul} );
915			
916			parent = $( this.id ).getOffsetParent();
917			delta = [ parseInt( parent.getStyle('left') / 2 ), parseInt( parent.getStyle('top') / 2 ) ];
918			
919			finalPos['left'] = delta[0];
920			finalPos['top'] = delta[1] + screenScroll.top;
921					
922			/* Make it appear over */
923			$( this.id + '_ac' ).setStyle( { 'zIndex': 10002 } );
924		}
925		else
926		{
927			$$('body')[0].insert( {bottom: ul} );
928			
929			if ( Element.getStyle( $( this.id ), 'position') == 'absolute')
930			{
931				parent = $( this.id ).getOffsetParent();
932				delta = [ parseInt( parent.getStyle('left') ), parseInt( parent.getStyle('top') ) ];
933		    }
934	
935			finalPos['left'] = sourcePos[0] - delta[0];
936			finalPos['top'] = sourcePos[1] - delta[1] + screenScroll.top;
937		}
938		
939		// Now try and keep it on screen
940		finalPos['top'] = finalPos['top'] + sourceDim.height;
941		
942		$( this.id + '_ac' ).setStyle('position: absolute; top: ' + finalPos['top'] + 'px; left: ' + finalPos['left'] + 'px;').hide();
943		
944		
945		this.list = $( this.id + '_ac' );
946	},
947	
948	updateList: function( json )
949	{	
950		if( !json || !$( this.list ) ){ return; }
951	
952		var newitems ='';
953		this.items = $A();
954		
955		json = $H( json );
956		
957		json.each( function( item )
958			{		
959				var li = this.options.templates.item.evaluate({ id: this.id + '_ac_item_' + item.key,
960				 												itemid: item.key,
961				 												itemvalue: item.value['showas'] || item.value['name'],
962				 												img: item.value['img'] || '',
963																img_w: item.value['img_w'] || '',
964																img_h: item.value['img_h'] || '',
965																url: item.value['url'] || ''
966															});
967				this.items[ item.key ] = item.value['name'];
968	
969				newitems = newitems + li;
970			}.bind(this)
971		);
972		
973		$( this.list ).update( newitems );
974		$( this.list ).immediateDescendants().each( function(elem){
975			$( elem ).observe('mouseover', this.selectThisItem.bindAsEventListener(this));
976			$( elem ).observe('click', this.selectCurrentItem.bindAsEventListener(this));
977			$( elem ).setStyle('cursor: pointer');
978		}.bind(this));
979		
980		if( $( this.list ).visible() )
981		{
982			this.selectFirstItem();
983		}
984	}
985});
986
987/**
988 * Get query string param
989 */
990getQueryStringParamByName = function(name)
991{
992	name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
993	var regexS  = "[\\?&]" + name + "=([^&#]*)";
994	var regex   = new RegExp(regexS);
995	var results = regex.exec(window.location.search);
996	
997	if ( results == null )
998	{
999		return "";
1000	}
1001	else
1002	{
1003		return decodeURIComponent( results[1].replace(/\+/g, " ") );
1004	}
1005};
1006
1007/* ===================================================================================================== */
1008/* Extended objects */
1009
1010// Extend RegExp with escape
1011Object.extend( RegExp, { 
1012	escape: function(text)
1013	{
1014		if (!arguments.callee.sRE)
1015		{
1016		   	var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$' ];
1017		   	//arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')' ); // IMPORTANT: dont use g flag
1018		   	arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
1019		}
1020		return text.replace(arguments.callee.sRE, '\\$1');
1021	}
1022});
1023
1024
1025// Escape regex
1026String.prototype.regExpEscape = function()
1027{
1028	var text = this;
1029	return text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
1030};
1031
1032// Extend String with HTMLspecial chars
1033String.prototype.escapeHtml = function()
1034{
1035	return this
1036    .replace(/&/g, "&amp;")
1037    .replace(/</g, "&lt;")
1038    .replace(/>/g, "&gt;")
1039    .replace(/"/g, "&quot;")
1040    .replace(/'/g, "&#039;");
1041};
1042//Extend String with HTMLspecial chars
1043String.prototype.unEscapeHtml = function()
1044{
1045	var _t = this.replace( /&amp;/g, "&" );
1046	return _t
1047    .replace(/&lt;/g, "<")
1048    .replace(/&gt;/g, ">")
1049    .replace(/&quot;/g, '"')
1050    .replace(/&#039;/g, "'")
1051    .replace(/&#39;/g, "'");
1052};
1053
1054// Extend String with URL UTF-8 escape
1055String.prototype.encodeUrl = function()
1056{
1057		var text = this;
1058		var regcheck = text.match(/[\x90-\xFF]/g);
1059		
1060		if ( regcheck )
1061		{
1062			for (var i = 0; i < regcheck.length; i++)
1063			{
1064				text = text.replace(regcheck[i], '%u00' + (regcheck[i].charCodeAt(0) & 0xFF).toString(16).toUpperCase());
1065			}
1066		}
1067	
1068		return escape(text).replace(/\+/g, "%2B").replace(/%20/g, '+').replace(/\*/g, '%2A').replace(/\//g, '%2F').replace(/@/g, '%40');
1069};
1070
1071// Extend String with URL UTF-8 escape - duplicated so it can be changed from above
1072String.prototype.encodeParam = function()
1073{
1074		var text = this;
1075		var regcheck = text.match(/[\x90-\xFF]/g);
1076
1077		if ( regcheck )
1078		{
1079			for (var i = 0; i < regcheck.length; i++)
1080			{
1081				text = text.replace(regcheck[i], '%u00' + (regcheck[i].charCodeAt(0) & 0xFF).toString(16).toUpperCase());
1082			}
1083		}
1084		
1085		/* Return just text as it is then encoded by prototype lib */
1086		return escape(text).replace(/\+/g, "%2B");
1087};
1088
1089// Extend date object to return date and time as object
1090Date.prototype.getDateAndTime = function( unix )
1091{
1092	 var a      = new Date( parseInt( unix ) * 1000 );
1093	 var months = ipb.lang['gbl_months'].split(',');
1094	 var year   = a.getFullYear();
1095	 var month  = months[a.getMonth()];
1096	 var date   = a.getDate();
1097	 var hour   = a.getHours();
1098	 var min    = a.getMinutes();
1099	 var sec    = a.getSeconds();
1100
1101	 return { year    : year,
1102		 	 monthName: month,
1103		 	 month    : ( '0' + a.getMonth() + 1 ).slice( -2 ),
1104		 	 date     : ( '0' + date ).slice( -2 ),
1105		 	 hour     : ( '0' + hour ).slice( -2 ),
1106		 	 min      : ( '0' + min ).slice( -2 ),
1107		 	 sec      : ( '0' + sec ).slice( -2 ),
1108		 	 dst	  : a.getTimezoneOffset() };
1109};
1110
1111// Extend Date object with a function to check for DST
1112Date.prototype.getDST = function()
1113{
1114	var beginning	= new Date( "January 1, 2008" );
1115	var middle		= new Date( "July 1, 2008" );
1116	var difference	= middle.getTimezoneOffset() - beginning.getTimezoneOffset();
1117	var offset		= this.getTimezoneOffset() - beginning.getTimezoneOffset();
1118	
1119	if( difference != 0 )
1120	{
1121		/* Hemisphere check */
1122		if ( difference < 0 )
1123		{
1124			/* Northern */
1125			return (difference == offset) ? 1 : 0;
1126		}
1127		else
1128		{
1129			/* Southern */
1130			return (difference != offset) ? 1 : 0;
1131		}
1132	}
1133	else
1134	{
1135		return 0;
1136	}
1137};
1138
1139/* ==================================================================================================== */
1140/* IPB3 JS Loader */
1141
1142var Loader = {
1143	require: function( name )
1144	{
1145		document.write("<script type='text/javascript' src='" + name + ".js'></script>");
1146	},
1147	boot: function()
1148	{
1149		$A( document.getElementsByTagName("script") ).findAll(
1150			function(s)
1151			{
1152  				return (s.src && s.src.match(/ipb\.js(\?.*)?$/));
1153			}
1154		).each( 
1155			function(s) {
1156  				var path = s.src.replace(/ipb\.js(\?.*)?$/,'');
1157  				var includes = s.src.match(/\?.*load=([a-zA-Z0-9_,\.]*)/);
1158				if( ! Object.isUndefined(includes) && includes != null && includes[1] )
1159				{
1160					includes[1].split(',').each(
1161						function(include)
1162						{
1163							if( include )
1164							{
1165								Loader.require( path + "ips." + include );
1166							}
1167						}
1168					);
1169				}
1170			}
1171		);
1172	}
1173};
1174var callback = { 
1175	afterOpen: function( popup ){
1176		try {
1177			$( 'pj_' + $(elem).identify() + '_input').activate();
1178		}
1179		catch(err){ }
1180	}
1181};
1182
1183/* ==================================================================================================== */
1184/* ELEMENT EXTENSIONS (tooltips, authorpane etc.) */
1185
1186Element.addMethods( {
1187	
1188 	 getInnerText: function(element)
1189 	 {
1190 	 	element = $(element);
1191 	 	return element.innerText && ! window.opera ? element.innerText : element.innerHTML.stripScripts().unescapeHTML().replace(/[\n\r\s]+/g, ' ');
1192     },
1193
1194	defaultize: function( element, lang )
1195	{
1196		if( ipb.global._supportsPlaceholder == null ){
1197			ipb.global._supportsPlaceholder = (function(){
1198				var i = document.createElement('input');
1199				return 'placeholder' in i;
1200			})();
1201		}
1202	
1203		if( ipb.global._supportsPlaceholder ){
1204			if( $F( element ) == lang || $F( element ).empty() ){
1205				$(element).removeClassName('inactive').writeAttribute('placeholder', lang).value = '';
1206			}
1207		} else {
1208			if( $F( element ) == lang || $F( element ).empty() ){
1209				$(element).addClassName('inactive').value = lang;
1210			}
1211		
1212			$(element).observe('focus', function(e){
1213				if( $(element).hasClassName('inactive') && ( $F(element) == '' || $F(element) == lang ) ){
1214					$(element).removeClassName('inactive').value = '';
1215				} else {
1216					$(element).removeClassName('inactive');
1217				}
1218			}).
1219			observe('blur', function(e){
1220				if( $F(element).empty() ){
1221					$(element).addClassName('inactive').value = lang;
1222				}
1223			});
1224			
1225			// Try and find a form around the element
1226			var form = $( element ).up('form');
1227			if( !Object.isUndefined( form ) ){
1228				$( form ).observe('submit', function(e){
1229					if( $(element).hasClassName('inactive') ){
1230						$(element).value = '';
1231					}
1232				});
1233			}
1234		}	
1235	},
1236	
1237	clickLaunch: function( element )
1238	{
1239		var _callback = $( element ).readAttribute("data-clicklaunch");
1240		var _scope    = 'global';
1241		
1242		try {
1243			var _try = $( element ).readAttribute("data-scope");
1244			_scope = ( _try ) ? _try.replace("ipb.", '') : _scope;
1245		} catch(e) { };
1246		
1247		if( $(element).retrieve('clickevent') ){
1248			try {
1249		 		$(element).retrieve('clickevent').stop();
1250			} catch(err){ };
1251		}
1252		
1253		var click = $(element).on( 'click', function(e) {
1254												Event.stop(e);
1255												ipb[_scope][_callback]( element, e);
1256											} );
1257		$(element).store('clickevent', click);
1258	},
1259	
1260	confirmAction: function( element, callback )
1261	{
1262		var _text     = $( element ).readAttribute("data-confirmaction");
1263		var _ok       = '';
1264		
1265		if( callback )
1266		{
1267			_ok = callback;
1268		}
1269		else if ( element.tagName == 'FORM' )
1270		{
1271			_ok = "$('" + element.id +"').submit()";
1272		}
1273		else
1274		{
1275			_ok =  'window.location=\'' + element.readAttribute( 'href' ) + '\'';
1276		}
1277
1278		if ( ! _text || _text == 'true' )
1279		{
1280			_text = ipb.lang['gbl_confirm_desc'];
1281		}
1282		
1283		var _options = { type: 'pane',
1284						 modal: true,
1285						 /* Inline JS makes it easier even if it is uglier */
1286					 	 initial: '<div><h3>' + ipb.lang['gbl_confirm_text'] + '</h3><div class="ipsPad ipsForm_center"><p>' + _text + '</p><br /><span onclick="ipb.global.popups[\'conact\'].hide();" class="clickable ipsButton_secondary important">' + ipb.lang['gbl_confirm_cancel'] + '</span> &nbsp; <span onclick="' + _ok + '" class="clickable ipsButton_secondary">' + ipb.lang['gbl_confirm_ok'] + '</span></div>',
1287					 	 hideAtStart: false,
1288						 w: '300px',
1289					 	 h: 150 };
1290		
1291		if ( element.tagName == 'FORM' || callback )
1292		{
1293			/* Fire immediately */
1294			if ( ! Object.isUndefined( ipb.global.popups['conact'] ) )
1295			{
1296				ipb.global.popups['conact'].kill();
1297			}
1298			
1299			ipb.global.popups['conact'] = new ipb.Popup( 'confirm', _options  );
1300		}
1301		else 
1302		{
1303			$(element).on( 'click', function(e)
1304			{
1305				Event.stop(e);
1306				
1307				if ( ! Object.isUndefined( ipb.global.popups['conact'] ) )
1308				{
1309					ipb.global.popups['conact'].kill();
1310				}
1311				
1312				ipb.global.popups['conact'] = new ipb.Popup( 'confirm', _options );
1313			
1314			} );
1315		}
1316	},
1317	
1318	tooltip: function( element, options ){
1319		
1320		options = Object.extend( {
1321			template: new Template("<div class='ipsTooltip' id='#{id}' style='display: none'><div class='ipsTooltip_inner'>#{content}</div></div>"),
1322			position: 'auto',
1323			content: $( element ).readAttribute("data-tooltip").stripTags().escapeHTML(),
1324			animate: true,
1325			overrideBrowser: true,
1326			delay: 0.4
1327		}, options);
1328		
1329		var show = function(e){
1330			if( options.delay && !options._still_going ){
1331				return; // Action has been cancelled
1332			}
1333			
1334			// Don't show if empty. Bug #36247 Tooltips
1335			if( ! options.content ){
1336				return;
1337			}		
1338			
1339			var id = $(element).identify();
1340
1341			if( !$( id + '_tooltip' ) ){
1342				$( document.body ).insert({ 'bottom': options.template.evaluate({ 'id': id + '_tooltip', 'content': options.content }) } );
1343			}
1344
1345			if( options.overrideBrowser && $(element).hasAttribute('title') ){
1346				$(element).writeAttribute("data-title", $(element).readAttribute('title').stripTags().escapeHTML()).writeAttribute("title", false);
1347			}
1348
1349			var tooltip = $(id + '_tooltip').setStyle({position: 'absolute'});
1350			
1351			// Add word wrap for calculations
1352			//$(tooltip).setStyle('white-space: nowrap');
1353			
1354			var layout = $(element).getLayout();
1355			var position = $(element).cumulativeOffset();
1356			var dims = $( id + '_tooltip' ).getDimensions();
1357			var docDim = $( document.body ).getLayout();
1358			
1359			// Detect best position for tooltip
1360			if( options.position == 'auto' ){
1361				if( position.left + (layout.get('padding-box-width')/2) - (dims.width/2) < 0 ){
1362					options.position = 'right';
1363				} else if( position.left + (dims.width/2) > docDim.get('width') ){
1364					options.position = 'left';
1365				} else {
1366					options.position = 'top';
1367				}				
1368			}
1369			
1370			Debug.write( dims );
1371			
1372			// And now position
1373			switch( options.position ){
1374				case 'top':
1375					$(tooltip).setStyle( { top: (position.top - dims.height - 1) + 'px', left: (position.left + (layout.get('padding-box-width')/2) - (dims.width/2)) + 'px' } ).addClassName('top');
1376				break;
1377				case 'bottom':
1378					$(tooltip).setStyle( { top: (position.top + layout.get('padding-box-height') + 1) + 'px', left: (position.left + (layout.get('padding-box-width')/2) - (dims.width/2)) + 'px' } ).addClassName('bottom');
1379				break;
1380				case 'left':
1381					$(tooltip).setStyle( { top: (position.top - (layout.get('padding-box-height') / 2)) + 'px', left: (position.left - dims.width -  3) + 'px' }).addClassName('left');
1382				break;
1383				case 'right':
1384					$(tooltip).setStyle( { top: (position.top - (layout.get('padding-box-height') / 2)) + 'px', left: (position.left + layout.get('padding-box-width') - 3) + 'px' }).addClassName('right');
1385				break;
1386			}
1387			
1388			// Remove word wrap
1389			//$(tooltip).setStyle('white-space: normal');
1390
1391			if( options.animate ){
1392				new Effect.Appear( $(tooltip), { duration: 0.3, queue: 'end' } );
1393			} else {
1394				$(tooltip).show();
1395			}
1396		},
1397		hide = function(e){
1398			var id = $(element).identify();
1399			if( !$(id + '_tooltip') ){ return; }
1400			
1401			if( options.animate ){
1402				new Effect.Fade( $(id + '_tooltip'), { duration: 0.2, queue: 'end' });
1403			} else {
1404				$(id + '_tooltip').hide();
1405			}
1406		};
1407		
1408		$( element ).observe("mouseenter", function(e){
1409			if( options.delay ){
1410				options._still_going = true;
1411				show.delay( options.delay, e );
1412			} else {
1413				show(e);
1414			}
1415		}).
1416		observe("click", function(e){
1417			options._still_going = false;
1418			hide();
1419		}).
1420		observe("mouseleave", function(e)
1421		{
1422			options._still_going = false;
1423			hide();				
1424		});	
1425	}
1426});
1427
1428/************************************************/
1429/* IPB3 Javascript								*/
1430/* -------------------------------------------- */
1431/* ips.global.js - Global functionality			*/
1432/* (c) IPS, Inc 2008							*/
1433/* -------------------------------------------- */
1434/* Author: Rikki Tissier						*/
1435/************************************************/
1436
1437var _global = window.IPBoard;
1438
1439_global.prototype.global = {
1440	searchTimer: [],
1441	searchLastQuery: '',
1442	rssItems: [],
1443	reputation: {},
1444	popups: {},
1445	ac_cache: $H(),
1446	pageJumps: $H(),
1447	pageJumpMenus: $H(),
1448	boardMarkers: $H(),
1449	searchResults: $H(),
1450	tidPopOpen: 0,
1451	activeTab: 'forums',
1452	userCards: null,
1453	inlineNotification: { timers: [] },
1454	_supportsPlaceholder: null,
1455	
1456	/*------------------------------*/
1457	/* Constructor 					*/
1458	init: function()
1459	{
1460		Debug.write("Initializing ips.global.js");
1461		
1462		document.observe("dom:loaded", function(){
1463			ipb.global.initEvents();
1464		});
1465	},
1466	initEvents: function()
1467	{
1468		// Delegate our user popup links/warn logs
1469		ipb.delegate.register(".warn_link", ipb.global.displayWarnLogs);
1470		ipb.delegate.register(".mini_friend_toggle", ipb.global.toggleFriend);
1471		ipb.delegate.register(".__topic_preview", ipb.global.topicPreview);
1472		ipb.delegate.register('.bbc_spoiler_show', ipb.global.toggleSpoiler);
1473		ipb.delegate.register('a[rel~="external"]', ipb.global.openNewWindow );
1474		ipb.delegate.register('._repLikeMore', ipb.global.repLikeMore);
1475		ipb.delegate.register('a[rel~="quickNavigation"]', ipb.global.openQuickNavigation );
1476		
1477		if( $('sign_in') && !$('sign_in').hasClassName('no_ajax') ){
1478			$('sign_in').on('click', ipb.global.inlineSignin);
1479		}
1480		
1481		if( $('rss_feed') ){
1482			ipb.global.buildRSSmenu();
1483		}
1484		
1485		if ( ! Object.isUndefined( ipb.vars['notificationData'] ) )
1486		{
1487			new ipb.Popup( 'navigation_popup', { type: 'modal',
1488											 	 initial: ipb.templates['notificationTemplate'].evaluate( ipb.vars['notificationData'] ),
1489											 	 hideAtStart: false,
1490												 w: '600px',
1491											 	 h: 250} );
1492		}
1493		
1494		/* This is needed for RTL - the generic anchoring isn't working on RTL for some reason */
1495		if( $('backtotop') ){
1496			$('backtotop').observe( "click", function(e){ Event.stop(e); window.scroll( 0, 0 ); } );
1497		}
1498		
1499		ipb.global.buildPageJumps();		
1500		ipb.global.initUserCards();
1501		
1502		/* Got an inline notification? */
1503		if ( ! Object.isUndefined( ipb.templates['inlineMsg'] ) && ipb.templates['inlineMsg'] != '' ){
1504			ipb.global.showInlineNotification( ipb.templates['inlineMsg'] );
1505		}
1506		
1507		// Contextual search
1508		if( $('search-box') ){
1509			ipb.global.contextualSearch();
1510		}
1511		
1512		// Global menus
1513		if( $('user_link') ){
1514			new ipb.Menu( $('user_link'), $('user_link_menucontent') );
1515		}
1516		
1517		if( $('new_skin') ){
1518			new ipb.Menu( $('new_skin'), $('new_skin_menucontent') );
1519		}
1520
1521		if( $('new_language') ){
1522			new ipb.Menu( $('new_language'), $('new_language_menucontent') );
1523		}
1524
1525		if( $('mark_all_read') ){
1526			new ipb.Menu( $('mark_all_read'), $('mark_all_read_menucontent') );
1527		}
1528		
1529		// Tooltips
1530		$$("[data-tooltip]").invoke('tooltip');
1531		
1532		// General click handlers
1533		$$("[data-clicklaunch]").invoke('clickLaunch');
1534		
1535		// Confirm action used in delete, etc
1536		$$("[data-confirmaction]").invoke('confirmAction');
1537		 
1538		// Status updates
1539		if( $('statusUpdateGlobal') ){
1540			$('statusUpdateGlobal').defaultize( ipb.lang['global_status_update'] );
1541			
1542			$('statusSubmitGlobal').observe( 'click', ipb.global.statusUpdated );
1543		}
1544		
1545		ipb.global.parseQuoteBoxes();
1546		ipb.global.removeLinkedLightbox();
1547		
1548		/* Attachments wrapped in URL @link http://community.invisionpower.com/tracker/issue-37113-linking-image-attachments-does-not-work/ */
1549		$$('a.resized_img').each( function( elem )
1550		{
1551			if ( $(elem).previous('a.bbc_url') )
1552			{
1553				var test = $(elem).previous('a.bbc_url');
1554				
1555				if ( ! test.innerHTML.length )
1556				{
1557					$(elem).writeAttribute( 'href', test.href );
1558					$(elem).writeAttribute( 'rel',  test.rel );
1559					
1560					/* Remove empty href */
1561					test.remove();
1562				}
1563			}
1564		} );
1565		
1566		/* Tag show more */
1567		if ( ! Object.isUndefined( ipb.hoverCard ) && ipb.vars['is_touch'] === false )
1568		{
1569			var ajaxUrl = ipb.vars['base_url'] + "app=core&module=ajax&section=tags&do=getTagsAsPopUp&md5check="+ipb.vars['secure_hash'];
1570			
1571			ipb.hoverCardRegister.initialize( 'tagsPopUp', { 'w': '500px', 'delay': 750, 'position': 'auto', 'ajaxUrl': ajaxUrl, 'getId': true, 'setIdParam': 'key' } );
1572		}
1573	},
1574	
1575	/* Make quote boxes all nice */
1576	parseQuoteBoxes: function()
1577	{
1578		$$('blockquote.ipsBlockquote').each( function( el )
1579		{
1580			if ( ! $(el).hasClassName('built') )
1581			{
1582				var author    = '';
1583				var cid       = '';
1584				var time      = 0;
1585				var date      = '';
1586				var collapsed = 0;
1587				var _extra    = '';
1588				var _a        = new Element('span');
1589				
1590				try {
1591					author    = $(el).getAttribute( 'data-author' )    ? $(el).getAttribute( 'data-author' ).escapeHtml() : '';
1592					cid       = $(el).getAttribute( 'data-cid' )       ? $(el).getAttribute( 'data-cid' ).escapeHtml()       : '';
1593					time      = $(el).getAttribute( 'data-time' )      ? $(el).getAttribute( 'data-time' ).escapeHtml()      : 0;
1594					date      = $(el).getAttribute( 'data-date' )      ? $(el).getAttribute( 'data-date' ).escapeHtml()      : '';
1595					collapsed = $(el).getAttribute( 'data-collapsed' ) ? $(el).getAttribute( 'data-collapsed' ).escapeHtml() : 0;
1596				} catch( aCold ) { }
1597				
1598				if ( time )
1599				{
1600					if ( time == parseInt( time ) && time.length == 10 )
1601					{
1602						/* First, determine if we are in DST now */
1603						var _tz   = new Date().getTimezoneOffset() * 60;
1604
1605						/* Find out if timestamp was in DST */
1606						var _date = new Date().getDateAndTime( parseInt( time ) );
1607
1608						/* If there isn't a match, figure out the DST offset and then add it back in */
1609						if( _date['dst'] * 60 != _tz )
1610						{
1611							_tz	= _tz - _date['dst'] * 60;
1612							_date = new Date().getDateAndTime( parseInt( time ) - parseInt( _tz ) );
1613						}
1614
1615						var _ampm = '';
1616
1617						if( ipb.vars['hour_format'] == "12" )
1618						{
1619							if( _date['hour'] > 12 )
1620							{
1621								_date['hour']	-= 12;
1622								_ampm			= ' ' + ipb.lang['date_pm'];
1623							}
1624							else if( _date['hour'] == 12 )
1625							{
1626								_ampm			= ' ' + ipb.lang['date_pm'];
1627							}
1628							else if( _date['hour'] == 0 )
1629							{
1630								_date['hour']	= 12;
1631								_ampm			= ' ' + ipb.lang['date_am'];
1632							}
1633							else
1634							{
1635								_ampm			= ' ' + ipb.lang['date_am'];
1636							}
1637						}
1638
1639						date      = _date['date'] + ' ' + _date['monthName'] + ' ' + _date['year'] + ' - ' + _date['hour'] + ':' + _date['min'] + _ampm;
1640					}
1641				}
1642				
1643				if ( author && date )
1644				{
1645					_extra = ipb.lang['quote__date_author'].replace( /#name#/, author ).replace( /#date#/, date );
1646				}
1647				else if ( author )
1648				{
1649					_extra = ipb.lang['quote__author'].replace( /#name#/, author );
1650				}
1651				
1652				/* finally.. */
1653				if ( _extra.length == 0 )
1654				{
1655					_extra = ipb.lang['quote_title'];
1656				}
1657				
1658				if ( cid && parseInt( cid ) == cid )
1659				{
1660					_a = new Element( 'a', { 'class': 'snapback right', rel: 'citation', href: ipb.vars['board_url'] + '/index.php?app=forums&module=forums&section=findpost&pid=' + cid } );
1661					_a.update( new Element( 'img', { src: ipb.vars['img_url'] + '/snapback.png' } ) );
1662				}
1663				
1664				el.insert( { before: new Element( 'p', { 'class': 'citation' } ).update( _extra ).insert( _a ) } );
1665				
1666				try
1667				{
1668					el.down('cite').hide();
1669				}
1670				catch(err){}
1671				
1672				el.addClassName( 'built' );
1673				
1674				if ( collapsed )
1675				{
1676					// Hide this element
1677					el.hide();
1678
1679					// Create a new blockquote that will be our 'clickable' area
1680					var bq = new Element( 'blockquote', { 'class': 'ipsBlockquote built clickable' } ).update( ipb.lang['quote_expand'] );
1681
1682					bq.on( 'click', function( e, elem )
1683					{
1684						el.show();
1685						bq.remove();
1686					} );
1687
1688					el.insert( { before: bq } );
1689				}
1690			}
1691		} );
1692	},
1693
1694	/* Remove lightbox if wrapped in a link */
1695	removeLinkedLightbox: function()
1696	{
1697		$$('a.bbc_url').each( function( el )
1698		{
1699			if ( _thislightbox = $(el).down('span[rel~=lightbox]') )
1700			{
1701				var contents = $(el).innerHTML.replace('<span rel="lightbox">', '');
1702				contents = contents.replace('</span>', '');
1703
1704				$(el).innerHTML	= contents;
1705			}
1706		} );
1707	},
1708	
1709	/* Lightbox has been disabled */
1710	lightBoxIsOff: function()
1711	{
1712		$$('span[rel*="lightbox"]').each( function( elem )
1713		{
1714			if ( ! $(elem).down('a') )
1715			{
1716				$(elem).down('img').on( 'click', function(e, el) { window.open(el.src); } );
1717			}
1718		} );
1719	},
1720	
1721	/* Stores default values of status share checkboxes */
1722	saveSocialShareDefaults: function( elem, e )
1723	{
1724		var services = {};
1725		
1726		/* Gather elements */
1727		$$('._share_x_').each( function(elem){
1728			services[ elem.id.replace(/share_x_/, '' ) ] = ( elem.checked ) ? 1 : 0;
1729		} );
1730		
1731		new Ajax.Request( ipb.vars['base_url'] + "app=core&section=sharelinks&module=ajax&do=savePostPrefs&md5check=" + ipb.vars['secure_hash'],
1732				{
1733					method: 'post',
1734					evalJSON: 'force',
1735					parameters: services,
1736					onSuccess: function(t)
1737					{
1738						if ( Object.isUndefined( t.responseJSON ) )
1739						{
1740							alert( ipb.lang['action_failed'] );
1741							return;
1742						}
1743						
1744						if ( ! Object.isUndefined( t.responseJSON['error'] ) )
1745						{
1746							alert( t.responseJSON['error'] );
1747						}
1748						else
1749						{
1750							/* Nothing to do */
1751						}
1752					}
1753				});
1754	},
1755	
1756	/*!! statusUpdated */
1757	/* Updates a status where ever you are on the page. Differs from the ipb.status version slightly */
1758	statusUpdated: function(e)
1759	{
1760		Event.stop(e);
1761		
1762		if ( $('statusUpdateGlobal' ).value.length < 2 || $('statusUpdateGlobal').value == ipb.lang['prof_update_default'] )
1763		{
1764			return false;
1765		}
1766		
1767		// Bug #34650: Commented this out so that board index updates
1768		// use the ajax function defined below instead of the main status function
1769		/* Main status library loaded? */
1770		/*if ( ! Object.isUndefined( ipb.status ) )
1771		{
1772			return ipb.status.updateSubmit( e, 'statusUpdateGlobal' );
1773		}*/
1774		
1775		var su_Twitter  = $('su_TwitterGlobal') && $('su_TwitterGlobal').checked ? 1  : 0;
1776		var su_Facebook = $('su_FacebookGlobal') && $('su_FacebookGlobal').checked ? 1 : 0;
1777		
1778		var skin_group = ( $('statusHook') ) ? 'boards' : 'profile';
1779		
1780		new Ajax.Request( ipb.vars['base_url'] + "app=members&section=status&module=ajax&do=new&md5check=" + ipb.vars['secure_hash'] + "&skin_group=" + skin_group + "&return=json&smallSpace=1",
1781						{
1782							method: 'post',
1783							evalJSON: 'force',
1784							parameters: {
1785								content: $('statusUpdateGlobal' ).value.encodeParam(),
1786								su_Twitter: su_Twitter,
1787								su_Facebook: su_Facebook
1788							},
1789							onSuccess: function(t)
1790							{
1791								if( Object.isUndefined( t.responseJSON ) )
1792								{
1793									alert( ipb.lang['action_failed'] );
1794									return;
1795								}
1796								
1797								if ( t.responseJSON['error'] )
1798								{
1799									alert( t.responseJSON['error'] );
1800								}
1801								else
1802								{
1803									try {
1804										if ( $('status_wrapper') )
1805										{
1806											var memberId = 0;
1807											
1808											try
1809											{
1810												memberId = $('status_wrapper').readAttribute('data-member');
1811											}
1812											catch(err){ }
1813											
1814											if ( ! memberId || ( memberId == ipb.vars['member_id'] ) )
1815											{
1816												$('status_wrapper').innerHTML = t.responseJSON['html'] + $('status_wrapper').innerHTML;
1817												
1818												/* Showing latest only? */
1819												if ( ipb.status.myLatest )
1820												{
1821													if ( $('statusWrap-' + ipb.status.myLatest ) )
1822													{
1823														$('statusWrap-' + ipb.status.myLatest ).hide();
1824													}
1825												}
1826											}
1827										}
1828										
1829										ipb.menus.closeAll(e,true);
1830										ipb.global.showInlineNotification( ipb.lang['status_updated'] );
1831										
1832										$('statusUpdateGlobal').value	= '';
1833										$('statusUpdateGlobal').defaultize( ipb.lang['global_status_update'] );
1834									}
1835									catch(err)
1836									{
1837										Debug.error( 'Logging error: ' + err );
1838									}
1839								}
1840							}
1841						});
1842	},
1843	
1844	/**
1845	 * Changes the user's skin
1846	 */
1847	changeSkin: function(element, e)
1848	{
1849		Debug.dir( element );
1850		var skinId = $(element).readAttribute('data-skinid');
1851		
1852		var url = ipb.vars['base_url'] + 'app=core&module=ajax&section=skin&do=change&skinId=' + skinId + '&secure_key=' + ipb.vars['secure_hash'];
1853		Debug.write( url );
1854		new Ajax.Request(	url,
1855							{
1856								method: 'get',
1857								onSuccess: function(t)
1858								{
1859									/*
1860									 * Get an error?
1861									 */
1862									if( t.responseJSON['status'] == 'ok' )
1863									{
1864										window.location = window.location;
1865										window.location.reload(true);
1866									}
1867									else
1868									{
1869										ipb.global.errorDialogue( ipb.lang['ajax_failure'] );
1870									}
1871								}
1872							}
1873						);
1874		
1875		
1876		Event.stop(e);
1877		return false;
1878	},
1879	
1880	/**
1881	 * Displays the inbox drop down in header
1882	 */
1883	getInboxList: function(element, e)
1884	{
1885		/*
1886		 * Only run AJAX call once.  Cache and use the cache for subsequent requests.
1887		 */
1888		if ( Object.isUndefined( ipb.global.popups['inbox'] ) )
1889		{
1890			ipb.global.popups['inbox'] = true;
1891			ipb.menus.closeAll(e);
1892			
1893			$(element).identify();
1894			
1895			/* Create pop-up wrapper */
1896			$(element).addClassName('ipbmenu');
1897			
1898			$('ipboard_body').insert( ipb.templates['header_menu'].evaluate( { id: 'user_inbox_link_menucontent' } ) );
1899			
1900			$('user_inbox_link_menucontent').setStyle('width: 300px').update( "<div class='ipsPad ipsForm_center'><img src='" + ipb.vars['loading_img'] + "' /></div>" );
1901			
1902			var _newMenu = new ipb.Menu( $(element), $( "user_inbox_link_menucontent" ) );
1903			_newMenu.doOpen();			
1904			
1905			var url = ipb.vars['base_url'] + 'app=members&module=ajax&section=messenger&do=getInboxDropDown';
1906			Debug.write( url );
1907			new Ajax.Request(	url,
1908								{
1909									method: 'post',
1910									evalJSON: 'force',
1911									hideLoader: true,
1912									parameters: {
1913										secure_key: 	ipb.vars['secure_hash']
1914									},
1915									onSuccess: function(t)
1916									{
1917										/*
1918										 * Get an error?
1919										 */
1920										if( t.responseJSON['error'] )
1921										{
1922											if ( t.responseJSON['__board_offline__'] )
1923											{
1924												ipb.global.errorDialogue( ipb.lang['board_offline'] );
1925												ipb.menus.closeAll(e);
1926											}
1927										}
1928										else
1929										{										
1930											$('user_inbox_link_menucontent').update( t.responseJSON['html'] );
1931											
1932											/* Clear counter */
1933											try
1934											{
1935												$(element).down('.ipsHasNotifications').fade( { afterFinish: function() { $(element).down('.ipsHasNotifications').show().addClassName('ipsHasNotifications_blank'); } } );
1936											} catch( acold ) { }
1937										}
1938									}
1939								}
1940							);
1941		}
1942		
1943		Event.stop(e);
1944		return false;
1945	},
1946	
1947	/**
1948	 * Displays the notifications drop down in header
1949	 */
1950	getNotificationsList: function(element, e)
1951	{
1952		Event.stop(e);
1953		
1954		/*
1955		 * Only run AJAX call once.  Cache and use the cache for subsequent requests.
1956		 */
1957		if ( Object.isUndefined( ipb.global.popups['notification'] ) )
1958		{
1959			ipb.global.popups['notification'] = true;
1960			
1961			ipb.menus.closeAll(e);
1962			
1963			$(element).identify();
1964			
1965			/* Create pop-up wrapper */
1966			$(element).addClassName('ipbmenu');
1967			
1968			$('ipboard_body').insert( ipb.templates['header_menu'].evaluate( { id: 'user_notifications_link_menucontent' } ) );
1969			
1970			$('user_notifications_link_menucontent').setStyle('width: 300px').update( "<div class='ipsPad ipsForm_center'><img src='" + ipb.vars['loading_img'] + "' /></div>" );
1971			
1972			var _newMenu = new ipb.Menu( $(element), $( "user_notifications_link_menucontent" ) );
1973			_newMenu.doOpen();
1974			
1975			var url = ipb.vars['base_url'] + 'app=core&module=ajax&section=notifications&do=getlatest';
1976			Debug.write( url );
1977			
1978			new Ajax.Request(	url,
1979								{
1980									method: 'post',
1981									evalJSON: 'force',
1982									hideLoader: true,
1983									parameters: {
1984										secure_key: 	ipb.vars['secure_hash']
1985									},
1986									onSuccess: function(t)
1987									{
1988										/*
1989										 * Get an error?
1990										 */
1991										if( t.responseJSON['error'] )
1992										{
1993											if ( t.responseJSON['__board_offline__'] )
1994											{
1995												ipb.global.errorDialogue( ipb.lang['board_offline'] );
1996												ipb.menus.closeAll(e);
1997											}
1998										}
1999										else
2000										{										
2001											$('user_notifications_link_menucontent').update( t.responseJSON['html'] );
2002											
2003											/* Clear counter */
2004											try
2005											{
2006												$(element).down('.ipsHasNotifications').fade( { afterFinish: function() { $(element).down('.ipsHasNotifications').show().addClassName('ipsHasNotifications_blank'); } } );
2007											} catch( acold ) { }
2008										}
2009									}
2010								}
2011							);
2012		}
2013		
2014		
2015		return false;
2016	},
2017
2018	
2019	openQuickNavigation: function( e )
2020	{
2021		Event.stop(e);
2022		
2023		if( ipb.global.popups['quickNav'] ){
2024			ipb.global.popups['quickNav'].show();
2025		} else {
2026			var url = ipb.vars['base_url'] + "app=core&module=ajax&section=navigation&secure_key=" + ipb.vars['secure_hash'] + "&inapp=" + ipb.vars['active_app'];
2027			ipb.global.popups['quickNav'] = new ipb.Popup( 'navigation_popup', { type: 'modal',
2028												 ajaxURL: url,
2029												 hideAtStart: false,
2030												 w: '600px',
2031												 h: 460 } );
2032
2033		
2034			/* delegate */
2035			ipb.delegate.register('a[rel~="ipsQuickNav"]', ipb.global.quickNavTabClick );
2036		}
2037		
2038		return false;
2039	},
2040	
2041	launchPhotoEditor: function( elem, e )
2042	{
2043		Event.stop(e);
2044		
2045		if ( ! Object.isUndefined( ipb.global.popups['photoEditor'] ) )
2046		{
2047			ipb.global.popups['photoEditor'].kill();
2048		}
2049		
2050		var url = ipb.vars['base_url'] + "&app=members&module=ajax&section=photo&do=show&secure_key=" + ipb.vars['secure_hash'];		
2051		ipb.global.popups['photoEditor'] = new ipb.Popup( 'photo_popup', {  type: 'pane',
2052																				 modal: true,
2053																				 ajaxURL: url,
2054																				 hideAtStart: false,
2055																				 evalJs: 'force',
2056																				 w: '750px',
2057																				 h: 500
2058																				  } );
2059		return false;
2060	},
2061	
2062	quickNavTabClick: function( e, elem )
2063	{
2064		Event.stop(e);
2065		app = elem.readAttribute( 'data-app' );
2066		
2067		var url = ipb.vars['base_url'] + "app=core&module=ajax&section=navigation&secure_key=" + ipb.vars['secure_hash'] + "&do=panel&inapp=" + app;
2068		
2069		new Ajax.Request(	url.replace(/&amp;/g, '&'),
2070				{
2071					method: 'get',
2072					evalJSON: 'force',
2073					hideLoader: true,
2074					onSuccess: function(t)
2075					{
2076						$('ipsNav_content').update( t.responseText );
2077						
2078						$$('a[rel~="ipsQuickNav"]').each( function(link)
2079						{
2080							link.up('li').removeClassName('active');
2081							var _app = link.readAttribute( 'data-app' );
2082							
2083							if ( _app == app )
2084							{
2085								link.up('li').addClassName('active');
2086							}
2087						} );
2088					}
2089				});
2090		
2091		return false;
2092	},
2093	
2094	
2095	ajaxPagination: function( element, url )
2096	{
2097		new Ajax.Request(	url.replace(/&amp;/g, '&'),
2098							{
2099								method: 'get',
2100								evalJSON: 'force',
2101								hideLoader: true,
2102								onSuccess: function(t)
2103								{
2104									$(element).update( t.responseText );
2105								}
2106							});
2107
2108		return false;
2109	},
2110	
2111	inlineSignin: function( e )
2112	{
2113		if( ipb.vars['is_touch'] ){ // Just go to normal form for touch devices
2114			return;
2115		}
2116		
2117		/* If we don't have the template bit.. */
2118		if ( ! $('inline_login_form') )
2119		{
2120			return;
2121		}
2122		
2123		Event.stop(e);
2124
2125		if( ipb.global.loginRedirect )
2126		{
2127			window.location = ipb.global.loginRedirect;
2128			return;
2129		}
2130		
2131		new ipb.Popup( 'sign_in_popup', {	type: 'pane',
2132											initial: $('inline_login_form').show(),
2133											hideAtStart: false,
2134											hideClose: false,
2135											defer: false,
2136											modal: true,
2137											w: '600px' },
2138										{
2139											afterShow: function(pop){
2140												try {
2141													$('ips_username').focus();
2142												} catch(err){}
2143											}
2144										});
2145	},
2146	
2147	forumMarkRead: function(elem, e)
2148	{
2149		Event.stop(e);
2150		
2151		var id = $(elem).readAttribute("data-fid");
2152		
2153		if( !id ){ return; }
2154		
2155		var url    = ipb.vars['base_url'] + '&app=forums&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=markasread&forumid=' + id;
2156	
2157		// Send AJAX request
2158		new Ajax.Request( 	url,
2159			 				{
2160								method: 'get',
2161								evalJSON: 'force',
2162								onSuccess: function(t)
2163								{
2164									if( t.responseText == 'no_forum' || t.responseText == 'no_permission' ){
2165										alert( ipb.lang['mark_read_forum'] );
2166										return;
2167									}
2168									
2169									/* Remove elements */
2170									$$('.__topic').each( function( topic )
2171									{
2172										if ( $(topic).hasClassName('unread') )
2173										{
2174											var tid = $(topic).readAttribute("data-tid");
2175											
2176											if ( tid )
2177											{
2178												ipb.global.topicRemoveUnreadElements( tid );
2179											}
2180										}
2181									} );
2182								}
2183							});
2184		
2185	},
2186	
2187	topicMarkRead: function(elem, e)
2188	{
2189		Event.stop(e);
2190		
2191		var id = $(elem).readAttribute("data-tid");
2192		
2193		if( !id ){ return; }
2194		
2195		var row    = $('trow_'+id);
2196		var url    = ipb.vars['base_url'] + '&app=forums&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=topics&do=markRead&tid=' + id;
2197	
2198		// Send AJAX request
2199		new Ajax.Request( 	url,
2200			 				{
2201								method: 'get',
2202								evalJSON: 'force',
2203								onSuccess: function(t)
2204								{
2205									if( t.responseText == 'no_topic' || t.responseText == 'no_permission' ){
2206										alert( ipb.lang['mark_read_topic'] );
2207										return;
2208									}
2209									
2210									/* Remove mark as read link */
2211									$(elem).remove();
2212									
2213									/* Close preview */
2214									ipb.global.topicPreview(e, row.down('.__topic_preview') );
2215									
2216									/* Remove elements */
2217									ipb.global.topicRemoveUnreadElements( id );
2218								}
2219							});
2220		
2221	},
2222	
2223	topicRemoveUnreadElements: function( tid )
2224	{
2225		$('trow_' + tid).removeClassName('unread').down('.col_f_icon').select('a img').invoke('remove');
2226	},
2227	
2228	topicPreview: function(e, elem)
2229	{				
2230		Event.stop(e);
2231		
2232		var toggle = $(elem).down(".expander");
2233		var row = $(elem).up(".__topic");
2234		var id = $(row).readAttribute("data-tid");
2235		
2236		if( !id ){ return; }
2237		
2238		// Stop multiple loads
2239		if( $(row).readAttribute('loadingPreview') == 'yes' ){
2240			return; // Just be patient!
2241		}		
2242		$( row ).writeAttribute('loadingPreview', 'yes');
2243		
2244		if( $("topic_preview_" + id) )
2245		{
2246			if( $("topic_preview_wrap_" + id).visible() )
2247			{ 
2248				new Effect.BlindUp( $("topic_preview_wrap_" + id), { duration: 0.3, afterFinish: function(){ $('topic_preview_' + id).hide(); } } );
2249				row.removeClassName('highlighted');
2250				$( toggle ).addClassName('closed').removeClassName('loading').removeClassName('open').writeAttribute('title', ipb.lang['open_tpreview']);
2251			}
2252			else
2253			{
2254				$('topic_preview_' + id).show();
2255				new Effect.BlindDown( $("topic_preview_wrap_" + id), { duration: 0.3 } );
2256				row.addClassName('highlighted');
2257				$( toggle ).addClassName('open').removeClassName('loading').removeClassName('closed').writeAttribute('title', ipb.lang['close_tpreview']);
2258			}
2259			
2260			$(row).writeAttribute('loadingPreview', 'no');
2261		}
2262		else
2263		{
2264			var url    = ipb.vars['base_url'] + '&app=forums&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=topics&do=preview&tid=' + id;
2265			if ( ipb.global.searchResults[ id ] ){
2266				url += '&pid=' + ipb.global.searchResults[ id ]['pid'] + '&searchTerm=' + ipb.global.searchResults[ id ]['searchterm'];
2267			}
2268			
2269			$( toggle ).addClassName('loading').removeClassName('closed').removeClassName('open');
2270			
2271			// Send AJAX request
2272			new Ajax.Request( 	url,
2273				 				{
2274									method: 'get',
2275									evalJSON: 'force',
2276									onSuccess: function(t)
2277									{
2278										if( t.responseText == 'no_topic' || t.responseText == 'no_permission' ){
2279											alert( ipb.lang['no_permission_preview'] );
2280											$( toggle ).addClassName('open').removeClassName('loading').removeClassName('closed').writeAttribute('title', ipb.lang['close_tpreview']);
2281
2282											$(row).writeAttribute('loadingPreview', 'no');
2283											return;
2284										}
2285										
2286										if( row.tagName == "TR" )
2287										{
2288											var count = row.childElements().size();
2289											var newrow = new Element('tr', { 'class': 'preview', 'id': 'topic_preview_' + id });
2290											var newcell = new Element('td', { 'colspan': count } );
2291											var wrap = new Element('div', { 'id': 'topic_preview_wrap_' + id }).hide().update( new Element('div' ) );
2292											
2293											// Put all the bits inside each other
2294											row.insert( { after: newrow.insert( newcell.insert( wrap ) ) } );
2295										}
2296										else
2297										{
2298											var wrap = new Element('div', { 'id': 'topic_preview_wrap_' + id }).hide().update( new Element('div') );
2299											row.insert( { after: wrap } );
2300										}										
2301										
2302										// Insert content
2303										wrap.update( t.responseText ).relativize();
2304										// display it
2305										new Effect.BlindDown( wrap, { duration: 0.3 } );
2306										// Update row & elem
2307										row.addClassName('highlighted');
2308										$( toggle ).addClassName('open').removeClassName('loading').removeClassName('closed').writeAttribute('title', ipb.lang['close_tpreview']);
2309										
2310										$(row).writeAttribute('loadingPreview', 'no');
2311									}
2312								});
2313		}
2314	},
2315	
2316	// Set up the main menu
2317	activateMainMenu: function()
2318	{
2319		if( $("nav_other_apps") && $("community_app_menu") ){
2320			/* Image is 0 width inside hidden div, so we have to compensate manually...hardcoded for our useropts image 17px + 3px margin */
2321			/* Note that we are grabbing width of more menu tab, instead of 'margin-box-width' which accounts for margin.  Cannot grab this
2322				value while the tab is hidden and we don't want to loop a second time unnecessarily to readjust after showing the menu */
2323			var start = totalW = $("nav_other_apps").getWidth() + 20; 
2324			var menuWidth = $("community_app_menu").getWidth();
2325
2326			/* Add up the widths of any menu items we won't be moving to the more menu */
2327			$("community_app_menu").select("li.skip_moremenu").each( function(elem){
2328				totalW += $(elem).measure('margin-box-width');
2329			});
2330
2331			$("community_app_menu").select("li:not(#nav_other_apps,.submenu_li)").each( function(elem){
2332				/* These tabs should not be moved to the more menu */
2333				if( $(elem).hasClassName('skip_moremenu') )
2334				{
2335					return;
2336				}
2337
2338				totalW += $(elem).measure('margin-box-width');
2339
2340				if( totalW >= menuWidth )
2341				{
2342					if( !$("more_apps_menucontent") ){
2343						$$("body")[0].insert("<div id='more_apps_menucontent' class='submenu_container clearfix boxShadow'><div class='left'><ul class='submenu_links' id='more_apps_menucontentul'></ul></div></div>");
2344					}
2345
2346					$(elem).addClassName('submenu_li').removeClassName('left');
2347					
2348					$("more_apps_menucontentul").insert( elem ); // Move item to menu
2349				}
2350			});
2351			
2352			// Do we have an app menu?
2353			if( $("more_apps_menucontent" ) )
2354			{
2355				$("nav_other_apps").show();
2356				new ipb.Menu( $('more_apps'), $('more_apps_menucontent') );
2357			}
2358			
2359			Debug.write( menuWidth );
2360		}
2361	},
2362	
2363	/**
2364	 * Init user cards
2365	 */
2366	initUserCards: function()
2367	{
2368		/* User cards */
2369		if ( ! Object.isUndefined( ipb.hoverCard ) && ipb.vars['is_touch'] === false && ipb.vars['member_group']['g_mem_info'] 
2369== 1 )
2370		{
2371			var ajaxUrl = ipb.vars['base_url'] + '&app=members&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=card';
2372			if ( ipb.topic !== undefined && ipb.topic.forum_id !== undefined )
2373			{
2374				ajaxUrl += "&f=" + ipb.topic.forum_id;
2375			}
2376			
2377			ipb.hoverCardRegister.initialize( 'member', { 'w': '500px', 'delay': 750, 'position': 'auto', 'ajaxUrl': ajaxUrl, 'getId': true, 'setIdParam': 'mid' } );
2378		}
2379	},
2380	
2381	/**
2382	 * Display an inline notification
2383	 * 
2384	 */
2385	showInlineNotification: function( content, options )
2386	{
2387		/* Fix options */
2388		options					  = ( Object.isUndefined( options ) ) ? {} : options;
2389		options.showClose 		  = ( Object.isUndefined( options.manualClose ) ) ? false : options.showClose;
2390		options.neverClose 		  = ( Object.isUndefined( options.neverClose ) )  ? false : options.neverClose;
2391		options.displayForSeconds = ( Object.isUndefined( options.displayForSeconds ) ) ? 5 : options.displayForSeconds;
2392		
2393		// If there's already one open, close it,
2394		// and add a callback to try again in when its done.
2395		if ( $('ipsGlobalNotification' ) )
2396		{
2397			ipb.global.closeInlineNotification( function(){
2398				ipb.global.showInlineNotification( content, options );
2399			});
2400
2401			return;
2402		}
2403		else
2404		{
2405			if( $('ipbwrapper') )
2406			{
2407				/* Front end */
2408				$('ipbwrapper').insert( new Element('div', { id: 'ipsGlobalNotification' } ).update( ipb.templates['global_notify'].evaluate( { 'message': content } ) ) );
2409			}
2410			else
2411			{
2412				/* ACP */
2413				$('ipboard_body').insert( new Element('div', { id: 'ipsGlobalNotification' } ).update( ipb.templates['global_notify'].evaluate( { 'message': content, 'close': ipb.templates['global_notify_close'] } ) ) );
2414			}
2415			
2416			/* Add in content */			
2417			new Effect.Appear( 'ipsGlobalNotification', { duration: 1.5 } );
2418			
2419			if ( options.showClose )
2420			{
2421				$('ipsGlobalNotification').insert( new Element( 'div', { id: 'ipsGlobalNotification_close' } ) );
2422				$('ipsGlobalNotification_close').observe('click', ipb.global.closeInlineNotification );
2423			}
2424			else if( $('ipsGlobalNotification_close') )
2425			{
2426				$('ipsGlobalNotification_close').observe('click', ipb.global.closeInlineNotification );
2427			}
2428		}
2429		
2430		/* Listen on any a hrefs */
2431		$('ipsGlobalNotification').on('click', 'span a', ipb.global.closeInlineNotification);
2432		
2433		/* Close */
2434		if ( options.neverClose !== true )
2435		{
2436			try	{
2437				clearTimeout( ipb.global.inlineNotification['timers']['close'] );
2438			}
2439			catch(e) {}
2440			
2441			ipb.global.inlineNotification['timers']['close'] = setTimeout( ipb.global.closeInlineNotification, options.displayForSeconds * 1000 );
2442		}
2443	},
2444	
2445	/**
2446	 * Closes a notification
2447	 */
2448	closeInlineNotification: function( callback )
2449	{
2450		callback = callback || Prototype.emptyFunction;
2451
2452		if ( $('ipsGlobalNotification_close') ){
2453			$('ipsGlobalNotification_close').stopObserving('click');
2454		}
2455		
2456		try {
2457			clearTimeout( ipb.global.inlineNotification['timers']['close'] );
2458		}
2459		catch(e) {}
2460		
2461		new Effect.Fade( 'ipsGlobalNotification', { duration: 1.0, afterFinish: function(){
2462			$('ipsGlobalNotification').remove();
2463			callback();
2464		} } );
2465	},
2466	
2467	/**
2468	 * Show an error dialogue
2469	 * @param string
2470	 * @returns Nothing
2471	 */
2472	errorDialogue: function( text )
2473	{
2474		errContent = "<h3>" + ipb.lang['error_occured'] + "</h3><div class='row2 ipsPad ipsForm_center'><p>" + text + "</p></div>";
2475		
2476		new ipb.Popup( 'generic__errorDialogue', {  type: 'pane',
2477													initial: errContent,
2478													stem: true,
2479													hideAtStart: false,
2480													hideClose: false,
2481													defer: false,
2482													warning: false,
2483													w: 400 } );
2484	},
2485	
2486	/**
2487	 * Show an OK dialogue
2488	 * @param string
2489	 * @returns Nothing
2490	 */
2491	okDialogue: function( text )
2492	{
2493		okContent = "<h3>" + ipb.lang['success'] + "</h3><div class='row2 ipsPad ipsForm_center'><p>" + text + "</p></div>";
2494		
2495		new ipb.Popup( 'generic__okDialogue', {  type: 'pane',
2496												 initial: okContent,
2497												 stem: true,
2498												 hideAtStart: false,
2499												 hideClose: false,
2500												 defer: false,
2501												 w: 400 } );
2502	},
2503	
2504	contextualSearch: function()
2505	{
2506		if( !$('search_options') && !$('search_options_menucontent') ){ return; }
2507		
2508		if ( ! $('main_search') )
2509		{
2510			return;
2511		}
2512		
2513		$('main_search').defaultize( ipb.lang['search_default_value'] );
2514		
2515		// This removes the text for IE7
2516		$('search').select('.submit_input').find( function(elem){ $(elem).value = ''; } );
2517		
2518		var update = function( noSelect )
2519		{
2520			var checked = $('search_options_menucontent').select('input').find( function(elem){ return $(elem).checked; } );
2521			if( Object.isUndefined( checked ) ){ 
2522				checked = $('search_options_menucontent').select('input:first')[0];
2523				if( !checked ){ return; }
2524				checked.checked = true;
2525			}
2526			$('search_options').show().update( $( checked ).up('label').readAttribute('title') || '' );
2527			
2528			// Put cursor in search box
2529			if( noSelect != true ){
2530				$('main_search').focus();
2531			}
2532			
2533			return true;
2534		};
2535		update(true);
2536		
2537		$('search_options_menucontent').select('input').invoke('observe', 'click', update);
2538	},
2539
2540	fetchTid: function( e )
2541	{
2542		var elem = Event.element(e);
2543		elem.identify();
2544		
2545		if( !elem.hasClassName('__topic') )
2546		{
2547			elem = elem.up('.__topic');
2548		}
2549
2550		var id   = elem.id;
2551		
2552		if ( !id || ! $(id) )
2553		{
2554			return 0;
2555		}
2556		
2557		var m    = $(id).className.match('__tid([0-9]+)');
2558		var tid  = m[1];
2559		
2560		return tid;
2561	},
2562
2563	displayWarnLogs: function( e, elem )
2564	{		
2565		mid = elem.id.match('warn_link_([0-9a-z]+)_([0-9]+)')[2];
2566		if( Object.isUndefined(mid) ){ return; }
2567		
2568		if( parseInt(mid) == 0 ){
2569			return false;
2570		}
2571		
2572		Event.stop(e);
2573		
2574		var _url 		= ipb.vars['base_url'] + '&app=core&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=warn&do=view&mid=' + mid;
2575		warnLogs = new ipb.Popup( 'warnLogs', {type: 'pane', modal: false, w: '500px', h: 500, ajaxURL: _url, hideAtStart: false, close: '.cancel' } );
2576		
2577	},
2578	
2579	/* ------------------------------ */
2580	/**
2581	 * Toggle mini friend button
2582	 * 
2583	 * @param	{event}		e		The event
2584	 * @param	{int}		id		Member id
2585	*/
2586	toggleFriend: function(e, elem)
2587	{
2588		Event.stop(e);
2589		
2590		// Get ID of friend
2591		var id = $( elem ).id.match('friend_(.*)_([0-9]+)');
2592		if( Object.isUndefined( id[2] ) ){ return; }
2593		
2594		var isFriend = ( $(elem).hasClassName('is_friend') ) ? 1 : 0;
2595		var urlBit = ( isFriend ) ? 'remove' : 'add';
2596		
2597		var url = ipb.vars['base_url'] + "app=members&section=friends&module=ajax&do=" + urlBit + "&member_id=" + id[2] + "&md5check=" + ipb.vars['secure_hash'];
2598		
2599		// Send
2600		new Ajax.Request( 	url,
2601			 				{
2602								method: 'get',
2603								onSuccess: function(t)
2604								{
2605									switch( t.responseText )
2606									{
2607										case 'pp_friend_timeflood':
2608											alert( ipb.lang['cannot_readd_friend'] );
2609											Event.stop(e);
2610											break;
2611										case "pp_friend_already":
2612											alert( ipb.lang['friend_already'] );
2613											Event.stop(e);
2614											break;
2615										case "error":
2616											return true;
2617											break;
2618										default:
2619											
2620											var newIcon = ( isFriend ) ? ipb.templates['m_add_friend'].evaluate({ id: id[2]}) : ipb.templates['m_rem_friend'].evaluate({ id: id[2] });
2621											 
2622											// Find all friend links for this user
2623											var friends = $$('.mini_friend_toggle').each( function( fr ){
2624												if( $(fr).id.endsWith('_' + id[2] ) )
2625												{
2626													if ( isFriend ) {
2627														$(fr).removeClassName('is_friend').addClassName('is_not_friend').update( newIcon );
2628													} else {
2629														$(fr).removeClassName('is_not_friend').addClassName('is_friend').update( newIcon );
2630													}
2631												}											
2632											});
2633											
2634											new Effect.Highlight( $( elem ), { startcolor: ipb.vars['highlight_color'] } );
2635											
2636											// Fire an event so we can update if necessary
2637											document.fire('ipb:friendRemoved', { friendID: id[2] } );
2638											Event.stop(e);
2639										break;
2640									}
2641								}
2642							}
2643						);
2644	},
2645	
2646	/**
2647	* MATT
2648	* Toggle spammer
2649	*/
2650	toggleFlagSpammer: function( memberId, flagStatus )
2651	{
2652		if ( flagStatus == true )
2653		{
2654			if( confirm( ipb.lang['set_as_spammer'] ) )
2655			{
2656				var tid	= 0;
2657				var fid	= 0;
2658				var sid	= 0;
2659				
2660				if( typeof(ipb.topic) != 'undefined' )
2661				{
2662					tid = ipb.topic.topic_id;
2663					fid = ipb.topic.forum_id;
2664					sid = ipb.topic.start_id;
2665				}
2666
2667				window.location = ipb.vars['base_url'] + 'app=core&module=modcp&do=setAsSpammer&member_id=' + memberId + '&t=' + tid + '&f=' + fid + '&st=' + sid + '&auth_key=' + ipb.vars['secure_hash'];
2668				return false;
2669			}
2670			else
2671			{
2672				return false;
2673			}
2674		}
2675		else
2676		{
2677			alert( ipb.lang['is_spammer'] );
2678			return false;
2679		}
2680	},
2681	
2682	/* ------------------------------ */
2683	/**
2684	 * Toggle spoiler
2685	 * 
2686	 * @param	{event}		e		The event
2687	*/
2688	toggleSpoiler: function(e, button)
2689	{
2690		Event.stop(e);
2691		
2692		var returnvalue = $(button).up('.bbc_spoiler').down('.bbc_spoiler_wrapper').down('.bbc_spoiler_content').toggle();
2693		
2694		if( returnvalue.visible() ){
2695			$(button).value = ipb.lang['spoiler_hide'];
2696		} else {
2697			$(button).value = ipb.lang['spoiler_show'];
2698		}
2699	},
2700
2701	/* ------------------------------ */
2702	/**
2703	 * Builds the popup menu for RSS feeds
2704	*/
2705	buildRSSmenu: function()
2706	{
2707		// Get all link tags
2708		$$('link').each( function(link)
2709		{
2710			if( link.readAttribute('type') == "application/rss+xml" )
2711			{
2712				ipb.global.rssItems.push( ipb.templates['rss_item'].evaluate( { url: link.readAttribute('href'), title: link.readAttribute('title').escapeHtml() } ) );
2713			}
2714		});
2715		
2716		if( ipb.global.rssItems.length > 0 )
2717		{
2718			rssmenu = ipb.templates['rss_shell'].evaluate( { items: ipb.global.rssItems.join("\n") } );
2719			$( 'rss_feed' ).insert( { after: rssmenu } );
2720			new ipb.Menu( $( 'rss_feed' ), $( 'rss_menu' ) );
2721		}
2722		else
2723		{
2724			$('rss_feed').hide();
2725		}
2726	},
2727	
2728	/* ------------------------------ */
2729	/**
2730	 * Reputation Popup Balloon
2731	 */
2732	repPopUp: function( e, repId, repApp, repType )
2733	{
2734		if( ipb.global.popups['rep_' + repId] ){
2735			ipb.global.popups['rep_' + repId].kill();
2736		}
2737		
2738		var _url = ipb.vars['base_url'] + '&app=core&module=ajax&secure_key=' + ipb.vars['secure_hash'] + '&section=reputation&do=view&repApp=' + repApp + '&repType=' + repType + '&repId=' + repId;
2739		ipb.global.popups['rep_' + repId] = new ipb.Popup('rep_' + repId, {
2740																type: 'balloon',
2741																stem: true,
2742																attach: { target: e, position: 'auto' },
2743																hideAtStart: false,
2744																ajaxURL: _url,
2745																w: '300px',
2746																h: 400
2747															});
2748	},
2749	
2750	/* ------------------------------ */
2751	/**
2752	 * Hides the PM notification box
2753	 * 
2754	 * @param	{event}		e		The event
2755	*/
2756	closePMpopup: function(e)
2757	{
2758		if( $('pm_notification') )
2759		{
2760			new Effect.Parallel([
2761				new Effect.Fade( $('pm_notification') ),
2762				new Effect.BlindUp( $('pm_notification') )
2763			], { duration: 0.5 } );
2764		}
2765		
2766		Event.stop(e);
2767	},
2768	
2769	/* ------------------------------ */
2770	/**
2771	 * Mark a PM read from popup
2772	 * 
2773	 * @param	{event}		e		The event
2774	*/
2775	markReadPMpopup: function(e)
2776	{
2777		if( $('pm_notification') )
2778		{
2779			var elem 	= Event.findElement(e, 'a');
2780			var href	= elem.href.replace( /&amp;/g, '&' ) + '&ajax=1';
2781			
2782			//Debug.write( 'Mark as read: ' + href );
2783			
2784			new Ajax.Request( href + "&md5check=" + ipb.vars['secure_hash'],
2785							{
2786								method: 'get',
2787								evalJSON: 'force',
2788								onSuccess: function(t){}
2789							});
2790			
2791			new Effect.Parallel([
2792				new Effect.Fade( $('pm_notification') ),
2793				new Effect.BlindUp( $('pm_notification') )
2794			], { duration: 0.5 } );
2795		}
2796		
2797		Event.stop(e);
2798		return false;
2799	},
2800	
2801	/* ------------------------------ */
2802	/**
2803	 * Initializes GD image
2804	 *
2805	*/
2806	initGD: function()
2807	{
2808		$('gd-antispam').observe('click', ipb.global.generateNewImage);
2809		
2810		if( $('gd-image-link') )
2811		{
2812			$('gd-image-link').observe('click', ipb.global.generateNewImage );
2813		}
2814	},
2815
2816	/* ------------------------------ */
2817	/**
2818	 * Simulate clicking the image
2819	 *
2820	 * @param	{element}	elem	The GD image element
2821	*/
2822	generateImageExternally: function( elem )
2823	{
2824		if( !$(elem) ){ return; }
2825		
2826		$(elem).observe('click', ipb.global.generateNewImage);
2827	},	
2828	
2829	/* ------------------------------ */
2830	/**
2831	 * Click event for generating new GD image
2832	 * 
2833	 * @param	{event}		e	The event
2834	*/
2835	generateNewImage: function(e)
2836	{
2837		img	= $('gd-antispam');
2838		
2839		Event.stop(e);
2840
2841		oldSrc = img.src.toQueryParams();
2842		oldSrc = $H( oldSrc ).toObject();
2843		
2844		if( !oldSrc['captcha_unique_id'] ){	Debug.error("No captcha ID found"); }
2845		
2846		// Get new image
2847		new Ajax.Request( 
2848			ipb.vars['base_url'] + "app=core&module=global&section=captcha&do=refresh&captcha_unique_id=" + oldSrc['captcha_unique_id'] + '&secure_key=' 
2848+ ipb.vars['secure_hash'],
2849			{
2850				method: 'get',
2851				onSuccess: function(t)
2852				{
2853					//Change src
2854					oldSrc['captcha_unique_id'] = t.responseText;
2855					img.writeAttribute( { src: ipb.vars['base_url'] + $H( oldSrc ).toQueryString() } );
2856					$('regid').value = t.responseText;
2857				}
2858			}
2859		);
2860	},
2861	
2862	/* ------------------------------ */
2863	/**
2864	 * Registers a reputation toggle on the page
2865	 * 
2866	 * @param	{int}		id		The element that wraps rep
2867	 * @param	{string}	url		The URL to ping
2868	 * @param	{int}		rating	The current rep rating
2869	*/
2870	registerReputation: function( id, url, rating )
2871	{
2872		if( !$( id ) ){ return; }
2873				
2874		// Find rep up
2875		var rep_up         = $( id ).down('.rep_up');
2876		var rep_down       = $( id ).down('.rep_down');
2877		
2878		var domLikeStripId = ( $(url.domLikeStripId) ) ? $(url.domLikeStripId) : false;
2879	
2880		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 + '&secure_key=' + ipb.vars['secure_hash'];		
2881		
2882		if( $( rep_up ) ){
2883			$( rep_up ).observe( 'click', ipb.global.repRate.bindAsEventListener(this, 1, id) );
2884		}
2885		
2886		if( $( rep_down ) ){
2887			$( rep_down ).observe( 'click', ipb.global.repRate.bindAsEventListener(this, -1, id) );
2888		}
2889		
2890		ipb.global.reputation[ id ] = { obj: $( id ), domLikeStripId: domLikeStripId, url: url, sendUrl: sendUrl, currentRating: rating || 0 };
2891		Debug.write( "Registered reputation" );
2892	},
2893	
2894	/* ------------------------------ */
2895	/**
2896	 * Does a reputation rating action
2897	 * 
2898	 * @param	{event}		e		The event
2899	*/
2900	repRate: function( e )
2901	{
2902		Event.stop(e);
2903		var type = $A(arguments)[1];
2904		var id = $A(arguments)[2];
2905		var value = ( type == 1 ) ? 1 : -1;
2906		
2907		if( !ipb.global.reputation[ id ] ){
2908			return;
2909		} else {
2910			var rep = ipb.global.reputation[ id ];
2911		}
2912
2913		Debug.write( rep.sendUrl + '&rating=' + value );
2914		
2915		// Send ping
2916		new Ajax.Request( rep.sendUrl + '&rating=' + value,
2917						{
2918							method: 'get',
2919							onSuccess: function( t )
2920							{
2921								if( t.responseJSON['status'] == 'ok' )
2922								{
2923									try {									
2924										// It worked! Hide the rep buttons
2925										rep.obj.down('.rep_up').up('li').hide();
2926										rep.obj.down('.rep_down').up('li').hide();
2927										
2928										/* Can we see some, though? */
2929										if ( t.responseJSON['canRepUp'] === true )
2930										{
2931											rep.obj.down('.rep_up').up('li').show();
2932										}
2933										
2934										if ( t.responseJSON['canRepDown']  === true )
2935										{
2936											rep.obj.down('.rep_down').up('li').show();
2937										}										
2938									} catch(err) { Debug.error( err ); }
2939									
2940									// Update the figure
2941									var rep_display = rep.obj.down('.rep_show');
2942									if( rep_display )
2943									{										
2944										['positive', 'negative', 'zero'].each(function(c){ rep_display.removeClassName(c); });
2945										
2946										var newValue = parseInt( t.responseJSON['rating'] );
2947										
2948										if( newValue > 0 )
2949										{
2950											rep_display.addClassName('positive');
2951										}
2952										else if( newValue < 0 )
2953										{
2954											rep_display.addClassName('negative');
2955										}
2956										else
2957										{
2958											rep_display.addClassName('zero');
2959										}
2960										
2961										rep_display.update( newValue );
2962									}
2963									
2964									/* Got a like strip */
2965									if ( $(rep.domLikeStripId.id) )
2966									{
2967										if ( t.responseJSON['likeData'].formatted !== false )
2968										{
2969											$(rep.domLikeStripId.id).update( t.responseJSON['likeData'].formatted ).show();
2970										}
2971										else
2972										{
2973											$(rep.domLikeStripId.id).update( '' ).hide();
2974										}
2975									}
2976								}
2977								else
2978								{
2979									if( t.responseJSON['error'] == 'nopermission' || t.responseJSON['error'] == 'no_permission' )
2980									{
2981										ipb.global.errorDialogue( ipb.lang['no_permission'] );
2982									}
2983									else
2984									{
2985										ipb.global.errorDialogue( t.responseJSON['error'] );
2986									}
2987								}
2988							}
2989						});
2990	
2991	 },
2992	
2993	 /**
2994	 * Fetch 'more'pop-up
2995	 */
2996	repLikeMore: function(e, elem)
2997	{
2998		Event.stop(e);
2999		
3000		try
3001		{
3002			var id   = elem.readAttribute('data-id');
3003			var app  = elem.readAttribute('data-app');
3004			var type = elem.readAttribute('data-type');
3005		}
3006		catch( e )
3007		{
3008			Debug.error(e);
3009		}
3010		
3011		if ( ! Object.isUndefined( ipb.global.popups['likeMore'] ) )
3012		{
3013			ipb.global.popups['likeMore'].kill();
3014		}
3015		
3016		var popid = 'setfave_' + id;
3017		var _url  = ipb.vars['base_url'] + '&app=core&module=ajax&section=reputation&do=more&secure_key=' + ipb.vars['secure_hash'] + '&f_app=' + app + '&f_type=' + type + '&f_id=' + id;
3018		Debug.write( _url );
3019		
3020		/* easy one this... */
3021		ipb.global.popups['likeMore'] = new ipb.Popup( popid, { type: 'pane',
3022																ajaxURL: _url,
3023																stem: false,
3024																hideAtStart: false,
3025																h: 500,
3026																w: '450px' });		
3027	},
3028
3029	/* ------------------------------ */
3030	/**
3031	 * Utility function for converting bytes
3032	 * 
3033	 * @param	{int}		size	The value in bytes to convert
3034	 * @return	{string}			The converted string, with unit
3035	*/
3036	convertSize: function(size)
3037	{
3038		var kb = 1024;
3039		var mb = 1024 * 1024;
3040		var gb = 1024 * 1024 * 1024;
3041		
3042		if( size < kb ){ return size + " B"; }
3043		if( size < mb ){ return ( size / kb ).toFixed( 2 ) + " KB"; }
3044		if( size < gb ){ return ( size / mb ).toFixed( 2 ) + " MB"; }
3045		
3046		return ( size / gb ).toFixed( 2 ) + " GB";
3047	},
3048
3049	/* ------------------------------ */
3050	/**
3051	 * Registers a page jump toggle
3052	 * 
3053	 * @param	{int}	source		ID of this jump
3054	 * @param	{hash}	options		Options for this jump
3055	*/
3056	registerPageJump: function( source, options )
3057	{
3058		if( !source || !options ){
3059			return;
3060		}
3061		
3062		ipb.global.pageJumps[ source ] = options;	
3063	},
3064	
3065	/* ------------------------------ */
3066	/**
3067	 * Builds a page jump control
3068	*/
3069	buildPageJumps: function()
3070	{
3071		$$('.pagejump').each( function(elem){
3072			// Find the pj ID
3073			var classes = $( elem ).className.match(/pj([0-9]+)/);
3074			
3075			if( Object.isUndefined( classes ) || !classes || !classes[1] ){
3076				return;
3077			}
3078			
3079			$( elem ).identify();
3080			
3081			// Doth a popup exist?
3082			//Debug.write( "This wrapper has been created! " + classes[1]  );
3083			var temp = ipb.templates['page_jump'].evaluate( { id: 'pj_' + $(elem).identify() } );
3084			$$('body')[0].insert( temp );
3085			
3086			$('pj_' + $(elem).identify() + '_submit').observe('click', ipb.global.pageJump.bindAsEventListener( this, $(elem).identify() ) );
3087			
3088			// So it submits on enter
3089			$('pj_' + $(elem).identify() + '_input').observe('keypress', function(e){
3090				if( e.which == Event.KEY_RETURN )
3091				{
3092					ipb.global.pageJump( e, $(elem).identify() );
3093				}
3094			});
3095			
3096			var wrap = $( 'pj_' + $(elem).identify() + '_wrap' ).addClassName('pj' + classes[1]).writeAttribute('jumpid', classes[1] );
3097			
3098			var callback = { 
3099				afterOpen: function( popup ){
3100					try {
3101						$( 'pj_' + $(elem).identify() + '_input').activate();
3102					}
3103					catch(err){ }
3104				}
3105		 	};
3106			
3107			ipb.global.pageJumpMenus[ classes[1] ] = new ipb.Menu( $( elem ), $( wrap ), { stopClose: true }, callback );
3108		});
3109	},
3110	
3111	/* ------------------------------ */
3112	/**
3113	 * Executes a page jump
3114	 * 
3115	 * @param	{event}		e		The event
3116	 * @param	{element}	elem	The page jump element
3117	*/
3118	pageJump: function( e, elem )
3119	{
3120		if( !$( elem ) || !$( 'pj_' + $(elem).id + '_input' ) ){ return; }
3121		
3122		var value = $F( 'pj_' + $(elem).id + '_input' );
3123		var jumpid = $( 'pj_' + $(elem).id + '_wrap' ).readAttribute( 'jumpid' );
3124		
3125		if( value.blank() ){
3126			try {
3127				ipb.global.pageJumpMenus[ source ].doClose();
3128			} catch(err) { }
3129		}
3130		else
3131		{
3132			value = parseInt( value );
3133		}
3134		
3135		// Work out page number 
3136		var options = ipb.global.pageJumps[ jumpid ];
3137		if( !options ){ Debug.dir( ipb.global.pageJumps ); Debug.write( jumpid ); return; }
3138		
3139		var pageNum = ( ( value - 1 ) * options.perPage );
3140		Debug.write( pageNum );
3141		
3142		if( pageNum < 1 ){
3143			pageNum = 0;
3144		}
3145		/*else if( pageNum > options.totalPages ){
3146			pageNum = options.totalPages;
3147		}*/
3148		
3149		/* Probably much easier to let the PHP redirect figure it out
3150		 * now we have the new page-2 options. Not an SEO issue as crawlers won't input
3151		 * data and not a resouces issue as drop down is hardly a hot spot for traffic
3152		 */
3153
3154		var separator = options.url.indexOf( '&' ) !== -1 ? '&' : '?' ;
3155		
3156		var url = options.url + separator + options.stKey + '=' + pageNum;
3157		
3158		if( options.anchor )
3159		{
3160			url = url + options.anchor;
3161		}
3162	
3163		url = url.replace(/&amp;/g, '&');
3164		// Without a negative lookbehind, http:// gets replaced with http:/ when we replace // with /
3165		// @see http://blog.stevenlevithan.com/archives/mimic-lookbehind-javascript
3166		url = url.replace(/(http:|https:)?\/\//g, function($0, $1) { return $1 ? $0 : '/'; } );
3167		
3168		document.location = url;
3169		
3170		return;
3171	},
3172	
3173	/* ------------------------------ */
3174	/**
3175	 * Open the link in a new window
3176	 * 
3177	 * @param	{event}		e		The event
3178	 * @param	{boolean}	force	Force new window regardless of host?
3179	*/
3180	openNewWindow: function(e, link, force)
3181	{		
3182		var ourHost	= document.location.host;
3183		var newHost = link.host;
3184		
3185		if( Prototype.Browser.IE )
3186		{
3187			newHost	= newHost.replace( /^(.+?):(\d+)$/, '$1' );
3188		}
3189
3190		/**
3191		 * Open a new window, if link is to a different host
3192		 */
3193		if( ourHost != newHost || force )
3194		{
3195			window.open(link.href);
3196			Event.stop(e);
3197			return false;
3198		}
3199		else
3200		{
3201			return true;
3202		}
3203	},
3204	
3205	/* ------------------------------ */
3206	/**
3207	 * Registers an ajax marker
3208	 * 
3209	 * @param	{string}	id		ID of the wrapper element
3210	 * @param	{string}	key		Key of the current marker status (e.g. f_unread)
3211	 * @param	{string}	url		URL to ping
3212	*/
3213	registerMarker: function( id, key, url )
3214	{
3215		if( !$(id) || key.blank() || url.blank() ){ return; }
3216		Debug.write( "Marker INIT: " + id );
3217		$( id ).observe('click', ipb.global.sendMarker.bindAsEventListener( this, id, key, url ) );
3218	},
3219	
3220	/* ------------------------------ */
3221	/**
3222	 * Sends a marker read request
3223	 * 
3224	 * @param	{event}		e		The event
3225	 * @param	{string}	id		ID of containing element
3226	 * @param	{string}	key		Key of current marker
3227	 * @param	{string}	url		URL to ping
3228	*/
3229	sendMarker: function( e, id, key, url )
3230	{
3231		Event.stop(e);
3232		
3233		new Ajax.Request( url + "&secure_key=" + ipb.vars['secure_hash'], 
3234							{
3235								method: 'get',
3236								evalJSON: 'force',
3237								onSuccess: function(t)
3238								{
3239									if( Object.isUndefined( t.responseJSON ) )
3240									{
3241										Debug.error("Invalid server response");
3242										return false;
3243									}
3244									
3245									if( t.responseJSON['error'] )
3246									{
3247										Debug.error( t.responseJSON['error'] );
3248										return false;
3249									}
3250									
3251									if ( $(id + '_tooltip') ){
3252										$(id + '_tooltip').hide();
3253									}
3254									
3255									$( id ).up('tr').removeClassName('unread');
3256									
3257									// Update icon
3258									$( id ).replace( unreadIcon );
3259									
3260									// Update subforum icons
3261									var _intId	= id.replace( /forum_img_/, '' );
3262									
3263									if( $("subforums_" + _intId ) )
3264									{
3265										$$("#subforums_" + _intId + " li" ).each( function(elem) {
3266											$(elem).removeClassName('unread');
3267										});
3268									}
3269								}
3270							});
3271	},
3272	
3273	registerCheckAll: function( id, classname )
3274	{
3275		if( !$( id ) ){ return; }
3276		
3277		$( id ).observe('click', ipb.global.checkAll.bindAsEventListener( this, classname ) );
3278		
3279		$$('.' + classname ).each( function(elem){
3280			$( elem ).observe('click', ipb.global.checkOne.bindAsEventListener( this, id ) );
3281		});
3282	},
3283	
3284	checkAll: function( e, classname )
3285	{
3286		Debug.write('checkAll');
3287		var elem = Event.element(e);
3288		
3289		// Get all checkboxes
3290		var checkboxes = $$('.' + classname);
3291		
3292		if( elem.checked ){
3293			checkboxes.each( function(check){
3294				check.checked = true;
3295			});
3296		} else {
3297			checkboxes.each( function(check){
3298				check.checked = false;
3299			});
3300		}			
3301	},
3302	
3303	checkOne: function(e, id)
3304	{
3305		var elem = Event.element(e);
3306		
3307		if( $( id ).checked && elem.checked == false )
3308		{
3309			$( id ).checked = false;
3310		}		
3311	},
3312	
3313	updateReportStatus: function(e, reportID, noauto, noimg )
3314	{
3315		Event.stop(e);
3316		
3317		var url = ipb.vars['base_url'] + "app=core&amp;module=ajax&amp;section=reports&amp;do=change_status&secure_key=" + ipb.vars['secure_hash'] + "&amp;status=3&amp;id=" + parseInt( reportID ) + "&amp;noimg=" + parseInt( noimg ) + "&amp;noauto=" + parseInt( noauto );
3318		
3319		// Do request, see what we get
3320		new Ajax.Request( url.replace(/&amp;/g, '&'),
3321							{
3322								method: 'post',
3323								evalJSON: 'force',
3324								onSuccess: function( t )
3325								{
3326									if( Object.isUndefined( t.responseJSON ) )
3327									{
3328										alert( ipb.lang['action_failed'] );
3329										return;
3330									}
3331									
3332									try {
3333										$('rstat-' + reportID).update( t.responseJSON['img'] );
3334										ipb.menus.closeAll( e );
3335									} catch(err) {
3336										Debug.error( err );
3337									}
3338								}
3339							});
3340	},
3341	
3342	getTotalOffset: function(elem, top, left)
3343	{
3344		if( $( elem ).getOffsetParent() != document.body )
3345		{
3346			Debug.write( "Checking " + $(elem).id );
3347			var extra = $(elem).positionedOffset();
3348			top += extra['top'];
3349			left += extra['left'];
3350			
3351			return ipb.global.getTotalOffset( $( elem ).getOffsetParent(), top, left );
3352		}
3353		else
3354		{
3355			Debug.write("OK Finished!");
3356			return { top: top, left: left };
3357		}
3358	},
3359	
3360	// Checks a server response from an ajax request for 'nopermission'
3361	checkPermission: function( text )
3362	{
3363		if( text == "nopermission" || text == 'no_permission' )
3364		{
3365			alert( ipb.lang['no_permission'] );
3366			return false;
3367		}
3368		
3369		return true;
3370	},
3371	
3372	/**
3373	 * Check for entry keypress
3374	 */
3375	checkForEnter: function(e, callback)
3376	{
3377		if( ![ Event.KEY_RETURN ].include( e.keyCode ) ){
3378			return; // Not interested in anything else
3379		}
3380		
3381		if ( callback )
3382		{
3383			switch( e.keyCode )
3384			{
3385				case Event.KEY_RETURN:
3386					callback(e);
3387				break;
3388			}
3389			
3390			Event.stop(e);
3391		}	
3392	},
3393
3394	/**
3395	 * Check for DST
3396	 */
3397	checkDST: function()
3398	{
3399		var memberHasDst	= ipb.vars['dst_in_use'];
3400		var dstInEffect		= new Date().getDST();
3401
3402		if( memberHasDst - dstInEffect != 0 )
3403		{
3404			var url = ipb.vars['base_url'] + 'app=members&module=ajax&section=dst&md5check='+ipb.vars['secure_hash'];
3405			
3406			new Ajax.Request(	url,
3407								{
3408									method: 'get',
3409									onSuccess: function(t)
3410									{
3411										// We don't need to do anything about this..
3412										return true;
3413									}
3414								}
3415							);
3416		}
3417	}
3418};
3419
3420/************************************************/
3421/* IPB3 Javascript								*/
3422/* -------------------------------------------- */
3423/* ips.menu.js - Me n you class	<3				*/
3424/* (c) IPS, Inc 2008							*/
3425/* -------------------------------------------- */
3426/* Author: Rikki Tissier 						*/
3427/************************************************/
3428
3429/* ipb.menus is a menu manager; ipb.Menu is a menu object */
3430
3431var _menu = window.IPBoard;
3432_menu.prototype.menus = {
3433	registered: $H(),
3434	closeCallBack: false,
3435	
3436	init: function()
3437	{
3438		Debug.write("Initializing ips.menu.js");
3439		document.observe("dom:loaded", function(){
3440			ipb.menus.initEvents();
3441		});
3442	},
3443	
3444	initEvents: function()
3445	{  
3446		// Set document event
3447		Event.observe( document, 'click', ipb.menus.docCloseAll );
3448		
3449		// Auto-find menus
3450		$$('.ipbmenu').each( function(menu){
3451			id = menu.identify();
3452			if( $( id + "_menucontent" ) )
3453			{ 
3454				new ipb.Menu( menu, $( id + "_menucontent" ) );
3455			}
3456		});
3457	},
3458	
3459	register: function( source, obj )
3460	{
3461		ipb.menus.registered.set( source, obj );
3462	},
3463	
3464	registerCloseCallBack: function( callBack )
3465	{
3466		ipb.menus.closeCallBack = callBack;
3467	},
3468	
3469	docCloseAll: function( e )
3470	{
3471		ipb.menus.closeAll( e );
3472	},
3473	
3474	/*!! Close all menus (forceClose ignores clicked in menu area check) */
3475	closeAll: function( except, forceClose )
3476	{
3477		ipb.menus.registered.each( function(menu, force){
3478		
3479			if( typeof( except ) == 'undefined' || ( except && menu.key != except ) )
3480			{
3481				try{
3482					if( forceClose || ( !(except.target && $(except.target).descendantOf( menu.value.target )) && except != menu.key ) ){
3483						menu.value.doClose();
3484					}
3485				} catch(err) {
3486					// Assume this menu gone byebye
3487				}
3488			}
3489		});
3490		
3491		/* Could make this an array and chain events */
3492		if ( Object.isFunction( ipb.menus.closeCallBack ) )
3493		{
3494			ipb.menus.closeCallBack();
3495		}
3496	}	
3497};
3498
3499_menu.prototype.Menu = Class.create({
3500	initialize: function( source, target, options, callbacks ){
3501		if( !$( source ) || !$( target ) ){ return; }
3502		if( !$( source ).id ){
3503			$( source ).identify();
3504		}
3505		this.id = $( source ).id + '_menu';
3506		this.source = $( source );
3507		this.target = $( target );
3508		this.callbacks = callbacks || {};
3509		
3510		this.options = Object.extend( {
3511			eventType: 'click',
3512			closeOnMouseout: false,
3513			stopClose: true,
3514			offsetX: 0,
3515			offsetY: 0
3516		}, arguments[2] || {});
3517		
3518		// Set up events
3519		$( source ).observe( 'click', this.eventClick.bindAsEventListener( this ) );
3520		$( source ).observe( 'mouseover', this.eventOver.bindAsEventListener( this ) );
3521		$( target ).observe( 'click', this.targetClick.bindAsEventListener( this ) );
3522
3523		/* Close on mouse out too? */
3524		if( this.options['closeOnMouseout'] !== false )
3525		{
3526			$( this.options['closeOnMouseout'] ).observe( 'mouseleave', this.mouseOutClose.bindAsEventListener( this ) );
3527		}
3528		
3529		/* Have an alt? */
3530		if ( $( $( source ).id + '_alt' ) )
3531		{
3532			$( $( source ).id + '_alt' ).observe( 'click', this.eventClick.bindAsEventListener( this, $( $( source ).id + '_alt' ) ) );
3533			$( $( source ).id + '_alt' ).observe( 'mouseover', this.eventOver.bindAsEventListener( this ) );
3534		}
3535		
3536		// Set up target
3537		$( this.target ).setStyle( 'position: absolute;' ).hide().setStyle( { zIndex: 9999 } );
3538		$( this.target ).descendants().each( function( elem ){
3539			$( elem ).setStyle( { zIndex: 10000 } );
3540		});
3541		
3542		ipb.menus.register( $( source ).id, this ); 
3543		
3544		if( Object.isFunction( this.callbacks['afterInit'] ) )
3545		{
3546			this.callbacks['afterInit']( this );
3547		}
3548	},
3549	
3550	doOpen: function(elem)
3551	{
3552		Debug.write("Menu open");
3553		var pos = {};
3554		
3555		var _source = ( this.options.positionSource ) ? this.options.positionSource : this.source;
3556	
3557		if ( ! Object.isUndefined( elem ) )
3558		{
3559			var _source = elem;
3560		}
3561		
3562		// This is the positioned offset of the source element
3563		var sourcePos		= $( _source ).positionedOffset();
3564		
3565		// Cumulative offset (actual position on the page, e.g. if you scrolled down it could be higher than max resolution height)
3566		var _sourcePos		= $( _source ).cumulativeOffset();
3567		
3568		// Cumulative offset of your scrolling (how much you have scrolled)
3569		var _offset			= $( _source ).cumulativeScrollOffset();
3570		
3571		// Real source position: Actual position on page, minus scroll offset (provides position on page within viewport)
3572		var realSourcePos	= { top: _sourcePos.top - _offset.top, left: _sourcePos.left - _offset.left };
3573		
3574		// Dimensions of source object
3575		var sourceDim		= $( _source ).getDimensions();
3576		
3577		// Viewport dimensions (e.g. 1280x1024)
3578		var screenDim		= document.viewport.getDimensions();
3579		
3580		// Target dimensions
3581		var menuDim			= { width: $( this.target ).measure('border-box-width'), height: $(this.target).measure('border-box-height') };
3582		
3583		var isFixed = $( _source ).ancestors().find( function(el){ return el.getStyle('position') == 'fixed'; } );
3584		
3585		/* RTL bug fixes */
3586		if( isRTL )
3587		{
3588			if( sourcePos.top < 0 )
3589			{
3590				sourcePos.top	= realSourcePos.top;
3591			}
3592			
3593			// Really really hacky RTL bug fix... :(
3594			if( $(_source).id == 'user_link' )
3595			{
3596				//alert(sourcePos.left);
3597				sourcePos.left = sourcePos.left - ( parseInt($(_source).getStyle('padding-left').replace( /px/, '' )) + parseInt($(_source).getStyle('margin-left').replace( /px/, '' )) );
3598				//alert(sourcePos.left);
3599			}
3600		}
3601		
3602		// Some logging	
3603		Debug.write( "realSourcePos: " + realSourcePos.top + " x " + realSourcePos.left );
3604		Debug.write( "sourcePos: " + sourcePos.top + " x " + sourcePos.left );
3605		Debug.write( "scrollOffset: " + _offset.top + " x " + _offset.left );
3606		Debug.write( "_sourcePos: " + _sourcePos.top + " x " + _sourcePos.left );
3607		Debug.write( "sourceDim: " + sourceDim.width + " x " + sourceDim.height);
3608		Debug.write( "menuDim: " + menuDim.height );
3609		Debug.write( "screenDim: " + screenDim.height );
3610		Debug.write( "manual ofset: " + this.options.offsetX + " x " + this.options.offsetY );
3611
3612		// Ok, if it's a relative parent, do one thing, else be normal
3613		// Getting fed up of this feature and IE bugs
3614		_a = _source.getOffsetParent();
3615		_b = this.target.getOffsetParent();
3616		
3617		Debug.write("_a is " + _a );
3618		Debug.write("_b is " + _b );
3619		
3620		if( isFixed )
3621		{
3622			$( this.target ).setStyle('position: fixed');
3623			
3624			if( ( _sourcePos.left + menuDim.width ) > screenDim.width ){
3625				diff = menuDim.width - sourceDim.width;
3626				pos.left = _sourcePos.left - diff + this.options.offsetX;
3627			} else {
3628				pos.left = (_sourcePos.left) + this.options.offsetX;
3629			}
3630			
3631			if( ( _sourcePos.top + menuDim.height ) > screenDim.height ){
3632				pos.top = _sourcePos.top - menuDim.height + this.options.offsetY;
3633			} else {
3634				pos.top = _sourcePos.top + sourceDim.height + this.options.offsetY;
3635			}
3636			
3637			
3638			$( this.target ).setStyle( 'top: ' + (pos.top-1) + 'px; left: ' + pos.left + 'px;' );
3639		}
3640		else
3641		{
3642			if( _a != _b )
3643			{
3644				// Left
3645				if( ( realSourcePos.left + menuDim.width ) > screenDim.width ){
3646					diff = menuDim.width - sourceDim.width;
3647					pos.left = _sourcePos.left - diff + this.options.offsetX;
3648				} else {
3649					if( Prototype.Browser.IE7 ){
3650						pos.left = (_sourcePos.left) + this.options.offsetX;
3651					} else {
3652						pos.left = (_sourcePos.left) + this.options.offsetX;
3653					}
3654				}
3655			
3656				// Top
3657				/* If there's no space to open downwards, open upwards *unless*
3658				/* it would go off the top of the screen (i.e. < 0px ) Bug #18270 */
3659				if( 
3660					( ( ( realSourcePos.top + sourceDim.height ) + menuDim.height ) > screenDim.height ) &&
3661					( _sourcePos.top - menuDim.height + this.options.offsetY ) > 0 )
3662				{
3663					pos.top = _sourcePos.top - menuDim.height + this.options.offsetY;
3664				} else {
3665					pos.top = _sourcePos.top + sourceDim.height + this.options.offsetY;
3666				}
3667			}
3668			else
3669			{
3670				Debug.write("MENU: source offset EQUALS target offset");
3671			
3672				// Left
3673				if( ( realSourcePos.left + menuDim.width ) > screenDim.width ){
3674					diff = menuDim.width - sourceDim.width;
3675					pos.left = sourcePos.left - diff + this.options.offsetX;
3676				} else {
3677					pos.left = sourcePos.left + this.options.offsetX;
3678				}
3679			
3680				// Top
3681				/* If there's no space to open downwards, open upwards *unless*
3682				/* it would go off the top of the screen (i.e. < 0px ) Bug #18270 */
3683				if( 
3684					( ( ( realSourcePos.top + sourceDim.height ) + menuDim.height ) > screenDim.height ) &&
3685					( _sourcePos.top - menuDim.height + this.options.offsetY ) > 0 )
3686				{
3687					pos.top = sourcePos.top - menuDim.height + this.options.offsetY;
3688				} else {
3689					pos.top = sourcePos.top + sourceDim.height + this.options.offsetY;
3690				}
3691			}
3692			
3693			$( this.target ).setStyle( 'top: ' + (pos.top-1) + 'px; left: ' + pos.left + 'px;' );
3694		}
3695		
3696		$( this.source ).addClassName('menu_active'); // Set active class on the source
3697		
3698		// Now set pos
3699		Debug.write("Menu position: " + pos.top + " x " + pos.left );
3700		
3701		
3702		// If we have any fixed ancestors, then fix the menu too
3703		/*if( isFixed ){
3704			$( this.target ).setStyle('position: fixed');
3705		}*/
3706		
3707		// And show
3708		new Effect.Appear( $( this.target ), { duration: 0.2, afterFinish: function(e){
3709				if( Object.isFunction( this.callbacks['afterOpen'] ) )
3710				{
3711					this.callbacks['afterOpen']( this );
3712				}
3713		}.bind(this) } );
3714		
3715		// Set key event so we can close on ESC
3716		Event.observe( document, 'keypress', this.checkKeyPress.bindAsEventListener( this ) );		
3717	},
3718	
3719	checkKeyPress: function( e )
3720	{
3721		//Debug.write( e );
3722		
3723		if( e.keyCode == Event.KEY_ESC )
3724		{
3725			this.doClose();
3726		}		
3727	},
3728
3729	mouseOutClose: function()
3730	{
3731		this.doClose();
3732	},
3733
3734	doClose: function()
3735	{
3736		new Effect.Fade( $( this.target ), { duration: 0.3, afterFinish: function(e){
3737				if( Object.isFunction( this.callbacks['afterClose'] ) )
3738				{
3739					this.callbacks['afterClose']( this );
3740				}
3741		 }.bind( this ) } );
3742		
3743		//Debug.write( "Closing " + $( this.source ).id );
3744		this.source.removeClassName('menu_active');
3745	},
3746	
3747	targetClick: function(e)
3748	{
3749		if( !this.options.stopClose ){
3750			this.doClose();
3751		}
3752		
3753		/*try
3754		{
3755			var elem = Event.findElement(e);
3756			
3757			if ( elem.hasClassName('_noCloseMenuUponClick') )
3758			{
3759				Event.stop(e);
3760			}
3761		}
3762		catch(e) { }
3763		
3764		if( ( this.options.stopClose && !$(elem).match("input") || $(elem).match("input") ) ){
3765			//if( $(e.target).match("[type=checkbox]")){
3766			//	Debug.write( $(e.target).checked );
3767			//	$(e.target).checked = true;
3768			//}
3769			Event.stop(e);
3770		}*/
3771	},
3772	
3773	eventClick: function(e, elem)
3774	{
3775		if( this.options['eventType'] == 'click' )
3776		{
3777			Event.stop(e);
3778			
3779			if( $( this.target ).visible() ){
3780				
3781				if( Object.isFunction( this.callbacks['beforeClose'] ) )
3782				{
3783					this.callbacks['beforeClose']( this );
3784				}
3785				
3786				this.doClose();
3787			} else {
3788				ipb.menus.closeAll( $(this.source).id );
3789				
3790				if( Object.isFunction( this.callbacks['beforeOpen'] ) )
3791				{
3792					this.callbacks['beforeOpen']( this );
3793				}
3794				
3795				this.doOpen(elem);
3796			}
3797		}
3798	},
3799	
3800	eventOver: function()
3801	{
3802		if( this.options['eventType'] == 'mouseover' )
3803		{
3804			if( !$( this.target ).visible() ){
3805				
3806				ipb.menus.closeAll( $(this.source).id );
3807				
3808				if( Object.isFunction( this.callbacks['beforeOpen'] ) )
3809				{
3810					this.callbacks['beforeOpen']( this );
3811				}
3812				
3813				this.doOpen();
3814			}
3815		}
3816	}
3817});
3818
3819
3820/************************************************/
3821/* IPB3 Javascript								*/
3822/* -------------------------------------------- */
3823/* ips.popup.js - Popup creator					*/
3824/* (c) IPS, Inc 2008							*/
3825/* -------------------------------------------- */
3826/* Author: Rikki Tissier						*/
3827/************************************************/
3828
3829/**
3830 * Full list of options:
3831 * 
3832 * type: 			balloon, pane
3833 * modal: 			true/false
3834 * w: 				width
3835 * h: 				height
3836 * classname: 		classname to be applied to wrapper
3837 * initial: 		initial content
3838 * ajaxURL: 		If supplied, will ping URL for content and update popup
3839 * close: 			element that will close popup (wont work with balloon)
3840 * attach: 			{ target, event, mouse, offset }
3841 * hideAtStart: 	Hide after creation (allows showing at a later time)
3842 * stem: 			true/false
3843 * delay: 			{ show, hide }
3844 */
3845_popup = window.IPBoard;
3846_popup.prototype.Popup = Class.create({
3847		
3848	initialize: function( id, options, callbacks )
3849	{
3850		/* Set up properties */
3851		this.id				= '';
3852		this.wrapper		= null;
3853		this.inner			= null;
3854		this.stem			= null;
3855		this.options		= {};
3856		this.timer			= [];
3857		this.ready			= false;
3858		this.visible		= false;
3859		this._startup		= null;
3860		this.hideAfterSetup	= false;
3861		this.eventPairs		= {	'mouseover': 	'mouseout',
3862								'mousedown': 	'mouseup'
3863							  };
3864		this._tmpEvent 		= null;
3865		
3866		/* Now run */
3867		this.id = id;
3868		this.options = Object.extend({
3869			type: 				'pane',
3870			w: 					'500px',
3871			modal: 				false,
3872			modalOpacity: 		0.4,
3873			hideAtStart: 		true,
3874			delay: 				{ show: 0, hide: 0 },
3875			defer: 				false,
3876			hideClose: 			false,
3877			black:				false,
3878			warning:			false,
3879			evalJs:				true,
3880			evalScript:			true,  /* Should we render JS in DOM.innerHTML */ 
3881			closeContents: 		ipb.templates['close_popup']		
3882		}, arguments[1] || {});
3883		
3884		this.callbacks = callbacks || {};
3885		
3886		// Are we deferring the load?
3887		if( this.options.defer && $( this.options.attach.target ) )
3888		{
3889			this._defer = this.init.bindAsEventListener( this );
3890			$( this.options.attach.target ).observe( this.options.attach.event, this._defer );
3891			
3892			if( this.eventPairs[ this.options.attach.event ] )
3893			{
3894				this._startup = function(e){ this.hideAfterSetup = true; this.hide(); }.bindAsEventListener( this );
3895				$( this.options.attach.target ).observe( this.eventPairs[ this.options.attach.event ], this._startup  );
3896			}
3897		}
3898		else
3899		{
3900			this.init();
3901		}
3902	},
3903	
3904	init: function()
3905	{
3906		try {
3907			Event.stopObserving( $( this.options.attach.target ), this.options.attach.event, this._defer );
3908			
3909			if ( $(this.options.attach.target) )
3910			{
3911				var toff = $( this.options.attach.target ).positionedOffset();
3912				var menu = $(this.options.attach.target).up('.ipbmenu_content');
3913				
3914				if ( toff.top == 0 && toff.left == 0 || $(menu) )
3915				{
3916					/* make it a centered box */
3917					this.options.type = 'modal';
3918					this.options.attach = {};
3919				}
3920			}
3921			
3922		} catch(err) { }
3923		
3924		
3925		this.wrapper = new Element('div', { 'id': this.id + '_popup' } ).setStyle('z-index: 10001').hide().addClassName('popupWrapper');
3926		
3927		this.inner = new Element('div', { 'id': this.id + '_inner' } ).addClassName('popupInner');
3928		
3929		if ( this.options.black )
3930		{
3931			this.inner.addClassName('black_mode');
3932		}
3933		
3934		if ( this.options.warning )
3935		{
3936			this.inner.addClassName('warning_mode');
3937		}
3938		
3939		if( this.options.w ){ this.inner.setStyle( 'width: ' + this.options.w ); }
3940		
3941		this.wrapper.insert( this.inner );
3942		
3943		if( this.options.hideClose != true )
3944		{
3945			this.closeLink = new Element('div', { 'id': this.id + '_close' } ).addClassName('popupClose').addClassName('clickable');
3946			this.closeLink.update( this.options.closeContents );
3947			this.closeLink.observe('click', this.hide.bindAsEventListener( this ) );
3948			this.wrapper.insert( this.closeLink );
3949			
3950			if ( this.options.black || this.options.warning )
3951			{
3952				this.closeLink.addClassName('light_close_button');
3953			}
3954		}
3955		
3956		$$('body')[0].insert( this.wrapper );
3957	
3958		if( this.options.classname ){ this.wrapper.addClassName( this.options.classname ); }
3959		
3960		if( this.options.initial ){
3961			this.update( this.options.initial, this.options.evalScript );
3962		}
3963		
3964		// Callback
3965		if( Object.isFunction( this.callbacks['beforeAjax'] ) )
3966		{
3967			this.callbacks['beforeAjax']( this );
3968		}
3969		
3970		// If we are updating with ajax, handle the show there
3971		if( this.options.ajaxURL ){
3972			this.updateAjax();
3973			setTimeout( this.continueInit.bind(this), 80 );
3974		} else {
3975			this.ready = true;
3976			this.continueInit();
3977		}
3978	},
3979	
3980	continueInit: function()
3981	{
3982		if( !this.ready )
3983		{
3984			setTimeout( this.continueInit.bind(this), 80 );
3985			return;
3986		}
3987		
3988		/* Ensure pop-up isn't larger than viewport */
3989		if( this.inner.select(".fixed_inner").size() )
3990		{
3991			Debug.write("Found fixed_inner");
3992			this.inner.select(".fixed_inner")[0].setStyle( 'height: ' + this.options.h + 'px; max-height: ' + this.options.h + 'px; overflow: auto' );
3993		}
3994		else
3995		{
3996			var _vph = document.viewport.getDimensions().height - 25;
3997			this.options.h = ( this.options.h && _vph > this.options.h ) ? this.options.h : _vph;
3998			this.inner.setStyle( 'max-height: ' + this.options.h + 'px' );
3999		}
4000		
4001		//Debug.write("Continuing...");
4002		// What are we making?
4003		if( this.options.type == 'balloon' ){
4004			this.setUpBalloon();
4005		} else {
4006			this.setUpPane();
4007		}
4008		
4009		// Set up close event
4010		try {
4011			if( this.options.close ){
4012				closeElem = $( this.wrapper ).select( this.options.close )[0];
4013				
4014				if( Object.isElement( closeElem ) )
4015				{
4016					$( closeElem ).observe( 'click', this.hide.bindAsEventListener( this ) );
4017				}
4018			}
4019		} catch( err ) {
4020			Debug.write( err );
4021		}
4022		
4023		// Callback
4024		if( Object.isFunction( this.callbacks['afterInit'] ) )
4025		{
4026			this.callbacks['afterInit']( this );
4027		}
4028		
4029		if( !this.options.hideAtStart && !this.hideAfterSetup )
4030		{
4031			this.show();
4032		}
4033		if( this.hideAfterSetup && this._startup )
4034		{	
4035			Event.stopObserving( $( this.options.attach.target ), this.eventPairs[ this.options.attach.event ], this._startup );
4036		}
4037	},
4038	
4039	updateAjax: function()
4040	{
4041		Debug.write( this.options.ajaxURL );
4042		new Ajax.Request( this.options.ajaxURL,
4043						{
4044							method: 'get',
4045							evalJS: this.options.evalJs,
4046							onSuccess: function(t)
4047							{
4048								if ( t.responseText != 'error' )
4049								{ 
4050									try
4051									{
4052										if ( ! Object.isUndefined( t.responseJSON ) && ! Object.isUndefined( t.responseJSON['error'] ) )
4053										{
4054											if ( t.responseJSON['__board_offline__'] )
4055											{
4056												ipb.global.errorDialogue( ipb.lang['board_offline'] );
4057												ipb.menus.closeAll(e);
4058											}
4059											else
4060											{
4061												ipb.global.errorDialogue( t.responseJSON['error'] );
4062											}
4063											
4064											return false;
4065										}
4066									} catch(e){}
4067									
4068									if ( t.responseText == 'nopermission' )
4069									{
4070										ipb.global.errorDialogue( ipb.lang['no_permission'] );
4071										return;
4072									}
4073									
4074									/* Check for log out */
4075									if ( t.responseText.match( "__session__expired__log__out__" ) )
4076									{
4077										this.update('');
4078										alert( ipb.lang['session_timed_out'] );
4079										return false;
4080									}
4081									
4082									Debug.write( "AJAX done!" );
4083									this.update( t.responseText );
4084									this.ready = true;
4085									
4086									// Callback
4087									if( Object.isFunction( this.callbacks['afterAjax'] ) )
4088									{
4089										this.callbacks['afterAjax']( this, t.responseText );
4090									}
4091								}
4092								else
4093								{
4094									Debug.write( t.responseText );
4095									return;
4096								}
4097							}.bind(this)
4098						});
4099	},
4100	
4101	show: function(e)
4102	{ 
4103		if( e ){ Event.stop(e); }
4104		
4105		if( this.timer['show'] ){
4106			clearTimeout( this.timer['show'] );
4107		}
4108		
4109		if( this.options.delay.show != 0 ){
4110		
4111			this.timer['show'] = setTimeout( this._show.bind( this ), this.options.delay.show );
4112		} else {
4113			this._show(); // Just show it
4114		}
4115	},
4116	
4117	hide: function(e)
4118	{
4119		if( e ){ Event.stop(e); }
4120		if( this.document_event ){
4121			Event.stopObserving( document, 'click', this.document_event );
4122			Event.stopObserving( document, 'touchstart', this.document_event );
4123		}
4124		
4125		if( this.timer['hide'] ){
4126			clearTimeout( this.timer['hide'] );
4127		}
4128				
4129		if( this.options.delay.hide != 0 ){
4130			this.timer['hide'] = setTimeout( this._hide.bind( this ), this.options.delay.hide );
4131		} else {
4132			this._hide(); // Just hide it
4133		}
4134	},
4135	
4136	/* remove wrapper and kill timeouts */
4137	kill: function()
4138	{
4139		if( this.timer['hide'] ){
4140			clearTimeout( this.timer['hide'] );
4141		}
4142		
4143		if( this.timer['show'] ){
4144			clearTimeout( this.timer['show'] );
4145		}
4146		
4147		if( $( this.wrapper ) )
4148		{
4149			$( this.wrapper ).remove();
4150		}
4151	},
4152	
4153	_show: function()
4154	{
4155		this.visible = true;
4156		
4157		/* Experimental */
4158		try
4159		{
4160			if ( this.options.warning )
4161			{
4162				_wrap = this.inner.down('h3').next('div');
4163				
4164				if ( _wrap )
4165				{
4166					if ( ! _wrap.className.match( /moderated/ ) )
4167					{
4168						_wrap.addClassName('moderated');
4169					}
4170				}
4171			}
4172		} catch(e){}
4173		
4174		if( this.options.modal == false ){
4175			new Effect.Appear( $( this.wrapper ), { duration: 0.3, afterFinish: function(){
4176				if( Object.isFunction( this.callbacks['afterShow'] ) )
4177				{
4178					this.callbacks['afterShow']( this );
4179				}
4180			}.bind(this) } );
4181			this.document_event = this.handleDocumentClick.bindAsEventListener(this);
4182			this.setDocumentEvent();
4183		} else {
4184			new Effect.Appear( $('document_modal'), { duration: 0.3, to: this.options.modalOpacity, afterFinish: function(){
4185				new Effect.Appear( $( this.wrapper ), { duration: 0.4, afterFinish: function(){
4186					if( Object.isFunction( this.callbacks['afterShow'] ) )
4187					{
4188						this.callbacks['afterShow']( this );
4189					}
4190			 	}.bind(this) } );
4191			}.bind(this) });
4192		}
4193	},
4194	
4195	_hide: function()
4196	{
4197		this.visible = false;
4198		
4199		if( this._tmpEvent != null )
4200		{
4201			Event.stopObserving( $( this.wrapper ), 'mouseout', this._tmpEvent );
4202			this._tmpEvent = null;
4203		}
4204						
4205		if( this.options.modal == false ){
4206			new Effect.Fade( $( this.wrapper ), { duration: 0.3, afterFinish: function(){
4207				if( Object.isFunction( this.callbacks['afterHide'] ) )
4208				{
4209					this.callbacks['afterHide']( this );
4210				}
4211			}.bind(this) } );
4212		} else {
4213			new Effect.Fade( $( this.wrapper ), { duration: 0.3, afterFinish: function(){
4214				new Effect.Fade( $('document_modal'), { duration: 0.2, afterFinish: function(){
4215					if( Object.isFunction( this.callbacks['afterHide'] ) )
4216					{
4217						this.callbacks['afterHide']( this );
4218					}
4219				}.bind(this) } );
4220			}.bind(this) });
4221		}
4222	},
4223	
4224	setDocumentEvent: function()
4225	{		
4226		if( !ipb.vars['is_touch'] ){
4227			Event.observe( document, 'click', this.document_event );
4228			return;
4229		}
4230		
4231		// Touch event
4232		Event.observe( document, 'touchstart', this.document_event );
4233	},
4234	
4235	handleDocumentClick: function(e)
4236	{
4237		Debug.write( 'document click: ' + Event.element(e).id);
4238		
4239		if( !Event.element(e).descendantOf( this.wrapper ) && ( this.options.attach && ( Event.element(e).id != this.options.attach.target.id ) ) )
4240		{
4241			this.hide(e);
4242		}
4243	},
4244	
4245	update: function( content, evalScript )
4246	{
4247		var scripts = [];
4248		
4249		if( Object.isElement( content ) ){
4250			this.inner.insert( { bottom: content } );
4251		} else {
4252			// Get the scripts we'll need to execute
4253			scripts = content.extractScripts();
4254			content = content.stripScripts();
4255
4256			this.inner.update( content );
4257		}
4258
4259		// Should this popup eval scripts? Default is YES
4260		if( ( Object.isUndefined( evalScript ) || evalScript != false ) && this.options.evalScript ){
4261			scripts.map( function (script) {
4262				eval( script );
4263			});
4264		}
4265	},
4266	
4267	setUpBalloon: function()
4268	{
4269		// Are we attaching?
4270		if( this.options.attach )
4271		{
4272			var attach = this.options.attach;
4273			
4274			if( attach.target && $( attach.target ) )
4275			{				
4276				if( this.options.stem == true )
4277				{
4278					this.createStem();
4279				}
4280				
4281				// Get position
4282				if( !attach.position ){ attach.position = 'auto'; }
4283				
4284				if( isRTL )
4285				{
4286					if( Object.isUndefined( attach.offset ) ){ attach.offset = { top: 0, right: 0 }; }
4287					if( Object.isUndefined( attach.offset.top ) ){ attach.offset.top = 0; }
4288					if( Object.isUndefined( attach.offset.left ) ){ attach.offset.right = 0; }else{ attach.offset.right = attach.offset.left; }
4289				}
4290				else
4291				{
4292					if( Object.isUndefined( attach.offset ) ){ attach.offset = { top: 0, left: 0 }; }
4293					if( Object.isUndefined( attach.offset.top ) ){ attach.offset.top = 0; }
4294					if( Object.isUndefined( attach.offset.left ) ){ attach.offset.left = 0; }
4295				}
4296				
4297				if( attach.position == 'auto' )
4298				{
4299					Debug.write("Popup: auto-positioning");
4300					var screendims 		= document.viewport.getDimensions();
4301					var screenscroll 	= document.viewport.getScrollOffsets();
4302					var toff			= $( attach.target ).viewportOffset();
4303					var wrapSize 		= $( this.wrapper ).getDimensions();
4304					var delta 			= [0,0];
4305					
4306					if (Element.getStyle( $( attach.target ), 'position') == 'absolute')
4307					{
4308						var parent = attach.target.getOffsetParent();
4309						delta = parent.viewportOffset();
4310				    }
4311				
4312				    if( isRTL )
4313				    {
4314				    	toff['right'] = screendims.width - ( toff[0] - delta[0] );
4315				    }
4316				    else
4317				    {
4318				    	toff['left'] = toff[0] - delta[0];
4319				    }
4320					toff['top'] = toff[1] - delta[1] + screenscroll.top;
4321
4322					//Debug.write( toff['left'] + "    " + toff['top'] );
4323					// Need to figure out if it will be off-screen
4324					var start 	= 'top';
4325					
4326					if( isRTL ){
4327						var end 	= 'right';
4328					} else {
4329						var end 	= 'left';
4330					}
4331
4332					//Debug.write( "Target offset top: " + toff.top + ", wrapSize Height: " + wrapSize.height + ", screenscroll top: " + screenscroll.top);
4333					if( ( toff.top - wrapSize.height - attach.offset.top ) < ( 0 + screenscroll.top ) ){
4334						var start = 'bottom';
4335					}
4336					
4337					if( isRTL )
4338					{
4339						if( ( toff.right + wrapSize.width - attach.offset.right ) < ( screendims.width - screenscroll.left ) ){
4340							var end = 'left';
4341						}
4342					}
4343					else
4344					{
4345						if( ( toff.left + wrapSize.width - attach.offset.left ) > ( screendims.width - screenscroll.left ) ){
4346							var end = 'right';
4347						}
4348					}
4349
4350					finalPos = this.position( start + end, { target: $( attach.target ), content: $( this.wrapper ), offset: attach.offset } );
4351					
4352					if( this.options.stem == true )
4353					{
4354						finalPos = this.positionStem( start + end, finalPos );
4355					}
4356				}
4357				else
4358				{
4359					Debug.write("Popup: manual positioning");
4360					
4361					finalPos = this.position( attach.position, { target: $( attach.target ), content: $( this.wrapper ), offset: attach.offset } );
4362					
4363					if( this.options.stem == true )
4364					{
4365						finalPos = this.positionStem( attach.position, finalPos );
4366					}
4367				}
4368				
4369				// Add mouse events
4370				if( !Object.isUndefined( attach.event ) ){
4371					$( attach.target ).observe( attach.event, this.show.bindAsEventListener( this ) );
4372					
4373					if( attach.event != 'click' && !Object.isUndefined( this.eventPairs[ attach.event ] ) ){
4374						$( attach.target ).observe( this.eventPairs[ attach.event ], this.hide.bindAsEventListener( this ) );
4375					}
4376						
4377					$( this.wrapper ).observe( 'mouseover', this.wrapperEvent.bindAsEventListener( this ) );					
4378				}				
4379			}
4380		}
4381		
4382		if( isRTL )
4383		{
4384			Debug.write("Popup: Right: " + finalPos.right + "; Top: " + finalPos.top);
4385			$( this.wrapper ).setStyle( 'top: ' + finalPos.top + 'px; right: ' + finalPos.right + 'px; position: absolute;' );
4386		}
4387		else
4388		{
4389			Debug.write("Popup: Left: " + finalPos.left + "; Top: " + finalPos.top);
4390			$( this.wrapper ).setStyle( 'top: ' + finalPos.top + 'px; left: ' + finalPos.left + 'px; position: absolute;' );
4391		}
4392	},
4393	
4394	wrapperEvent: function(e)
4395	{
4396		if( this.timer['hide'] )
4397		{
4398			// Cancel event now
4399			clearTimeout( this.timer['hide'] );
4400			this.timer['hide'] = null;
4401			
4402			if( this.options.attach.event && this.options.attach.event == 'mouseover' )
4403			{
4404				// Set new event to account for mouseout of the popup,
4405				// but only if we don't already have one - otherwise we get
4406				// expontentially more event calls. Bad.
4407				if( this._tmpEvent == null ){
4408					this._tmpEvent = this.hide.bindAsEventListener( this );
4409					$( this.wrapper ).observe('mouseout', this._tmpEvent );
4410				}
4411			}
4412		}
4413	},
4414	
4415	positionStem: function( pos, finalPos )
4416	{
4417		var stemSize = { height: 16, width: 31 };
4418		var wrapStyle = {};
4419		var stemStyle = {};
4420		
4421		switch( pos.toLowerCase() )
4422		{
4423			case 'topleft':
4424				wrapStyle = { marginBottom: stemSize.height + 'px' };
4425				
4426				if( isRTL )
4427				{
4428					stemStyle = { bottom: -(stemSize.height) + 'px', right: '5px' };
4429					finalPos.right = finalPos.right - 15;
4430				}
4431				else
4432				{
4433					stemStyle = { bottom: -(stemSize.height) + 'px', left: '5px' };
4434					finalPos.left = finalPos.left - 15;
4435				}
4436				break;
4437			case 'topright':
4438				wrapStyle = { marginBottom: stemSize.height + 'px' };
4439
4440				if( isRTL )
4441				{
4442					stemStyle = { bottom: -(stemSize.height) + 'px', left: '5px' };
4443					finalPos.right = finalPos.right + 15;
4444				}
4445				else
4446				{
4447					stemStyle = { bottom: -(stemSize.height) + 'px', right: '5px' };
4448					finalPos.left = finalPos.left + 15;
4449				}
4450				break;
4451			case 'bottomleft':
4452				wrapStyle = { marginTop: stemSize.height + 'px' };
4453
4454				if( isRTL )
4455				{
4456					stemStyle = { top: -(stemSize.height) + 'px', right: '5px' };
4457					finalPos.right = finalPos.right - 15;
4458				}
4459				else
4460				{
4461					stemStyle = { top: -(stemSize.height) + 'px', left: '5px' };
4462					finalPos.left = finalPos.left - 15;
4463				}
4464				break;
4465			case 'bottomright':
4466				wrapStyle = { marginTop: stemSize.height + 'px' };
4467
4468				if( isRTL )
4469				{
4470					stemStyle = { top: -(stemSize.height) + 'px', left: '5px' };
4471					finalPos.right = finalPos.right + 15;
4472				}
4473				else
4474				{
4475					stemStyle = { top: -(stemSize.height) + 'px', right: '5px' };
4476					finalPos.left = finalPos.left + 15;
4477				}
4478				break;
4479		}
4480
4481		$( this.wrapper ).setStyle( wrapStyle );
4482		$( this.stem ).setStyle( stemStyle ).setStyle('z-index: 6000').addClassName( pos.toLowerCase() );
4483		
4484		return finalPos;
4485	},
4486	
4487	position: function( pos, v )
4488	{
4489		finalPos = {};
4490		
4491		v.target.identify();
4492		
4493		var toff			= $( v.target.id ).viewportOffset();
4494		var tsize	 		= $( v.target.id ).getDimensions();
4495		var wrapSize 		= $( v.content ).getDimensions();
4496		var screenscroll 	= document.viewport.getScrollOffsets();
4497		var offset 			= v.offset;
4498		var delta			= [0,0];
4499		
4500		if (Element.getStyle( $( v.target.id ), 'position') == 'absolute')
4501		{
4502			var parent = $( v.target.id ).getOffsetParent();
4503			delta    = parent.viewportOffset();
4504			
4505			/* That above doesn't help */
4506			delta = [0,0];
4507		
4508	    }
4509		
4510	    if( isRTL )
4511	    {
4512			toff['right'] = document.viewport.getDimensions().width - ( toff[0] - delta[0] );
4513		}
4514		else
4515		{
4516			toff['left'] = toff[0] - delta[0];
4517		}
4518	    
4519		toff['top'] = toff['top'] - delta[1] + screenscroll.top;
4520				
4521		switch( pos.toLowerCase() )
4522		{
4523			case 'topleft':
4524				finalPos.top = ( toff.top - wrapSize.height - ( tsize.height / 2 ) ) - offset.top;
4525				
4526				if( isRTL )
4527				{
4528					finalPos.right = toff.right + offset.right;
4529				}
4530				else
4531				{
4532					finalPos.left = toff.left + offset.left;
4533				}
4534				break;
4535			case 'topright':			
4536			 	finalPos.top = ( toff.top - wrapSize.height - ( tsize.height / 2 ) ) - offset.top;
4537			 	
4538			 	if( isRTL )
4539			 	{
4540			 		finalPos.right = ( toff.right - ( wrapSize.width - tsize.width ) ) - offset.right;
4541			 	}
4542			 	else
4543			 	{
4544					finalPos.left = ( toff.left - ( wrapSize.width - tsize.width ) ) - offset.left;
4545				}
4546				break;
4547			case 'bottomleft':
4548				finalPos.top = ( toff.top + tsize.height ) + offset.top;
4549				
4550				if( isRTL )
4551				{
4552					finalPos.right = toff.right + offset.right;
4553				}
4554				else
4555				{
4556					finalPos.left = toff.left + offset.left;
4557				}
4558				break;
4559			case 'bottomright':
4560				finalPos.top = ( toff.top + tsize.height ) + offset.top;
4561				
4562				if( isRTL )
4563				{
4564					finalPos.right = ( toff.right - ( wrapSize.width - tsize.width ) ) - offset.right;
4565				}
4566				else
4567				{
4568					finalPos.left = ( toff.left - ( wrapSize.width - tsize.width ) ) - offset.left;
4569				}
4570				break;
4571		}
4572	
4573		return finalPos;
4574	},
4575	
4576	createStem: function()
4577	{
4578		this.stem = new Element('div', { id: this.id + '_stem' } ).update('&nbsp;').addClassName('stem');
4579		this.wrapper.insert( { top: this.stem } );
4580	},
4581	
4582	setUpPane: function()
4583	{
4584		// Does the document have a modal blackout?
4585		if( !$('document_modal') ){
4586			this.createDocumentModal();
4587		}
4588		
4589		this.positionPane();	
4590	},
4591	
4592	positionPane: function()
4593	{
4594		// Position it in the middle
4595		var elem_s = $( this.wrapper ).getDimensions();
4596		var window_s = document.viewport.getDimensions();
4597		var window_offsets = document.viewport.getScrollOffsets();
4598
4599		if( ipb.vars['is_touch'] ){
4600			// #35826: On some skins, webkit will handle the document viewport sizes incorrectly.
4601			// To try and protect against this, we'll use the actual screen size for these
4602			// calculations.
4603			window_s = { width: window.innerWidth, height: window.innerHeight };
4604		}
4605		
4606		if( isRTL )
4607		{
4608			var center = { 	right: ((window_s['width'] - elem_s['width']) / 2),
4609						 	top: (((window_s['height'] - elem_s['height']) / 2)/2)
4610						};
4611						
4612			if( center.top < 10 ){ center.top = 10; }
4613						
4614			$( this.wrapper ).setStyle('top: ' + center['top'] + 'px; right: ' + center['right'] + 'px; position: fixed;');
4615
4616		}
4617		else
4618		{
4619			var center = { 	left: ((window_s['width'] - elem_s['width']) / 2),
4620						 	top: (((window_s['height'] - elem_s['height']) / 2)/2)
4621						};
4622
4623			if( center.top < 10 ){ center.top = 10; }
4624						
4625			$( this.wrapper ).setStyle('top: ' + center['top'] + 'px; left: ' + center['left'] + 'px; position: fixed;');
4626		}
4627	},
4628			
4629	createDocumentModal: function()
4630	{
4631		var pageLayout = $( document.body ).getLayout();
4632		var pageSize = { width: pageLayout.get('width'), height: pageLayout.get('margin-box-height') };
4633		var viewSize = document.viewport.getDimensions();
4634		
4635		var dims = [];
4636		
4637		Debug.dir( pageSize );
4638		Debug.dir( viewSize );
4639		
4640		if( viewSize['height'] < pageSize['height'] ){
4641			dims['height'] = pageSize['height'];
4642		} else {
4643			dims['height'] = viewSize['height'];
4644		}
4645		
4646		if( viewSize['width'] < pageSize['width'] ){
4647			dims['width'] = pageSize['width'];
4648		} else {
4649			dims['width'] = viewSize['width'];
4650		}
4651		
4652		var modal = new Element( 'div', { 'id': 'document_modal' } ).addClassName('modal').hide();
4653		
4654		if( isRTL ){
4655			modal.setStyle('width: ' + dims['width'] + 'px; height: ' + dims['height'] + 'px; position: fixed; top: 0px; right: 0px; z-index: 10000;');
4656		} else {
4657			modal.setStyle('width: ' + dims['width'] + 'px; height: ' + dims['height'] + 'px; position: fixed; top: 0px; left: 0px; z-index: 10000;');
4658		}
4659		
4660		$$('body')[0].insert( modal );
4661	},
4662	
4663	getObj: function()
4664	{
4665		return $( this.wrapper );
4666	}
4667});
4668
4669/************************************************/
4670/* IPB3 Javascript								*/
4671/* -------------------------------------------- */
4672/* ips.ticker.js - Popup creator				*/
4673/* (c) IPS, Inc 2008							*/
4674/* -------------------------------------------- */
4675/* Author: Rikki Tissier						*/
4676/************************************************/
4677_ticker = window.IPBoard;
4678_ticker.prototype.Ticker = Class.create({		
4679	initialize: function( root, options, callbacks )
4680	{
4681		if( !$(root) ){
4682			return;
4683		}
4684		this.root = $(root);
4685		this.options = Object.extend({
4686			duration: 4,
4687			select: "li"
4688		}, options || {});
4689		
4690		this.items = $( root ).select( this.options.select );
4691		if( !this.items.length ){ return; }
4692		
4693		// Hide all items except first
4694		this.items.invoke('hide').first().show();
4695		
4696		// Start timer
4697		this.timer = this.nextItem.bind( this ).delay( this.options.duration );
4698		
4699		// Pause event
4700		$( this.root ).observe('mouseenter', this.pauseTicker.bindAsEventListener( this ) );
4701		$( this.root ).observe('mouseleave', this.unpauseTicker.bindAsEventListener( this ) );
4702	},
4703	pauseTicker: function(e)
4704	{
4705		clearTimeout( this.timer );
4706	},
4707	unpauseTicker: function(e)
4708	{
4709		this.timer = this.nextItem.bind(this).delay( this.options.duration );
4710	},
4711	nextItem: function()
4712	{
4713		// Find current item
4714		var cur = this.items.find( function(elem){
4715			return elem.visible();
4716		});
4717
4718		var next = $( cur ).next( this.options.select );
4719
4720		if( Object.isUndefined( next ) ){
4721			next = this.items.first();
4722		}
4723
4724		// Fade current
4725		new Effect.Fade( $( cur ), { duration: 0.4, queue: 'end', afterFinish: function(){
4726			new Effect.Appear( $( next ), { duration: 0.8, queue: 'end' } );
4727		} } );
4728
4729		// Reset timer
4730		this.timer = this.nextItem.bind( this ).delay( this.options.duration );
4731	}
4732});
4733
4734function warningPopup( elem, id )
4735{
4736	var _url = ipb.vars['base_url'] + '&app=members&module=ajax&secure_key=' 
4736+ ipb.vars['secure_hash'] + '&section=warnings&id=' + id;
4737	new ipb.Popup( 'warning' + id, {
4738		type: 'balloon',
4739		stem: true,
4740		attach: { target: elem, position: 'auto' },
4741		hideAtStart: false,
4742		ajaxURL: _url,
4743		w: '600px',
4744		h: 800
4745	});
4746}
4747
4748ipb = new IPBoard;
4749ipb.global.init();
4750ipb.menus.init();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.