MediaWiki:Gadget-twinkle.js

window.twinkleConfigExists = false;

if ( userIsInGroup( 'sysop' ) || twUserIsWhitelisted ) { twinkleConfigExists = true; }

function twUserIsWhitelisted { return userIsInGroup( 'autoconfirmed' ) || userIsInGroup( 'confirmed' ); }

if ( window.TwinkleConfig == undefined ) window.TwinkleConfig = {};

if ( TwinkleConfig.portletId == undefined ) TwinkleConfig.portletId = 'p-cactions';

/** * Add a portlet menu to one of the navigation areas on the page. * Available navigation areas depend on the script used. * * NOTE: If anyone is brave enough to reuse this directly, please shoot * me a note. Otherwise I might change the signature down the line and * your script breaks. Amalthea. * * @param String portlet -- id of the target portlet (skin dependent) * @param String id -- id of the portlet menu to create, preferably start with "p-". * @param String text -- name of the portlet menu to create. Visibility depends on the class used. * @param String type -- type of portlet. pass "menu" to make this portlet a drop down menu (vector only) * @param String next -- place before this menu, or otherwise at the end. * * @return jQuery -- the new portlet */ function twAddPortlet( portlet, id, text, type, next ) {	var $portlet, $item, $where, $h5, outerDivClass, innerDivClass, skin;

skin = mw.config.get( 'skin' );

// verify/normalize input type = ( skin === "vector" && type === "menu" && ( portlet === "left-navigation" || portlet === "right-navigation" )) ? "menu" : "";

switch ( skin ) {		case "vector": if ( $.inArray( portlet, [ "portal", "left-navigation", "right-navigation" ] ) === -1 ) { portlet = "mw-panel"; }			if ( portlet === "mw-panel" ) { outerDivClass = "portal"; innerDivClass = "body"; } else if ( type === "menu" ) { outerDivClass = "vectorMenu extraMenu emptyPortlet"; innerDivClass = "menu"; } else { outerDivClass = "vectorTabs extraMenu emptyPortlet"; innerDivClass = ""; }			break; case "modern": if ( portlet !== "mw_contentwrapper" ) { portlet = "mw_portlets"; }			outerDivClass = "portlet"; innerDivClass = "pBody"; break; default: portlet = "column-one"; outerDivClass = "portlet"; innerDivClass = "pBody"; break; }

$portlet = $( '#' + portlet ); $item = $( '#' + id ); if ( $item.length ) { return $item.parent.is( $portlet ) ? $item : $; }

// Build the DOM elements. $item = $( ' ', { 'id': id, 'class': outerDivClass } ); $h5 = $( ' ' );

if ( type === "menu" ) { $h5 .append( $( ' ', { 'text': text } ) ) .append( $( '', { 'href': '#' } ).append( $( ' ', { 'text': text } ) ) ); } else { $h5.text( text ); }

$item.append( $h5 ).append( $( ' ', { 'class': innerDivClass } ).append( '' ) );

if ( next ) { $where = $( '#' + next ); if ( $where.parent.is( $portlet ) ) { $where.before( $item ); } else { $portlet.append( $item ); }	} else { $portlet.append( $item ); }

return $item; }

//Build a portlet menu if it doesn't exist yet, and add the portlet link. function twAddPortletLink( task, text, id, tooltip, accesskey ) {	var link;

if (TwinkleConfig.portletArea) { twAddPortlet(			TwinkleConfig.portletArea,			TwinkleConfig.portletId,			TwinkleConfig.portletName,			TwinkleConfig.portletType,			TwinkleConfig.portletNext		); }	link = mw.util.addPortletLink(		TwinkleConfig.portletId,		typeof task ==="string" ? task : "#",		text, id, tooltip, accesskey, TwinkleConfig.portletNext	);

if ( $.isFunction(task) ) { $(link).click( function (e) { task.call(this, e); e.preventDefault; } ); }

return link; }

/** * QuickForm is for the creation of simple and standard forms without much specific coding. */ function QuickForm( event, eventType ) { this.root = new QuickForm.element({ type: 'form', event: event, eventType: eventType }); }

QuickForm.prototype.render = function QuickFormRender { var ret = this.root.render; ret.names = {}; return ret; };

QuickForm.prototype.append = function QuickFormRender( data ) { return this.root.append( data ); };

QuickForm.element = function QuickFormElement( data ) { if ( !( this instanceof QuickForm.element ) ) { return new QuickForm.element( data ); } else if ( data instanceof QuickForm.element ) { this.data = data.data; this.childs = data.childs; this.id = data.id; } else { this.data = data; this.childs = []; this.id = QuickForm.element.id++; } };

QuickForm.element.id = 0;

QuickForm.element.prototype.append = function QuickFormElementAppend( data ) { var child = QuickForm.element( data ); this.childs.push( child ); return child; };

QuickForm.element.prototype.render = function QuickFormElementRender { var current = this.compute; $.each( this.childs, function { current[1].append( this.render ); } ); return current[0]; };

QuickForm.element.prototype.compute = function QuickFormElementCompute( in_id ) { var	$node, $child, $label, data = this.data, id = ( in_id ? in_id + '_' : '' ) + 'node_' + this.id, cur_id, current, $cur_div, $input;

if ( data.adminonly && !userIsInGroup( 'sysop' ) ) { // hell hack alpha data.type = 'hidden'; }

switch( data.type ) { case 'form': $node = $( '' ); if ( data.event ) { $.on( data.eventType || 'submit', data.event ); }			break; case 'select': $node = $( ' ', { 'id': 'div_' + id } ); $child = $( ' ', { 'name': data.name } ); $node.append( $child );

if ( data.label ) { $label = $( ' ', { 'for': id, 'text': data.label } ); $node.append( $label ); }			if ( data.multiple ) { $child.prop( 'multiple', true ); }			if ( data.size ) { $child.attr( 'size', data.size ); }			if ( data.event ) { $child.on( 'change', data.event ); }			if ( data.list ) { for ( var i = 0; i < data.list.length; ++i ) { current = data.list[i]; current.type = current.list ? 'optgroup' : 'option'; $child.append( QuickForm.element( current ).compute[0] ); }			}			break; case 'option': $node = $( ' ', {				'value': data.value,				'label': data.label,				'text': data.label,				'data': {					'values': data.value				}			}); if ( data.selected ) { $node.prop( 'selected', true ); }			if ( data.disabled ) { $node.prop( 'disabled', true ); }			break; case 'optgroup': $node = $( ' ', { 'label': data.label } ); if ( data.list ) { for ( var i = 0; i < data.list.length; ++i ) { var current = data.list[i]; current.type = 'option'; // must be options here $node.append( QuickForm.element( current ).compute[0] ); }			}			break; case 'field': $label = $( ' ', { 'text': data.label } ); $node = $( ' ' ).append( $label ); if ( data.name ) { $node.attr( 'name', data.name ); }			break; case 'checkbox': case 'radio': $node = $( ' ' ); if ( data.list ) { for ( var i = 0; i < data.list.length; ++i ) { cur_id = id + '_' + i;					current = data.list[i]; if ( current.type === 'header' ) { // inline hack $cur_div = $( ' ', { 'text': current.label } ); $node.append( $cur_div ); if ( current.tooltip ) { QuickForm.element.generateTooltip( $cur_div, current ); }						continue; }

$cur_div = $( ' ' ); $input = $( ' ', {						'value':	current.value,						'name':		current.name || data.name,						'type':		data.type,						'id':		cur_id,						'data':		{							'values':	current.value						}					});

$cur_div.append( $input ); $node.append( $cur_div );

if ( current.checked ) { $input.prop( 'checked', true ); }					if ( current.disabled ) { $input.prop( 'disabled', true ); }					if ( data.event ) { $input.on( 'change', data.event ); } else if ( current.event ) { $input.on( 'change', current.event ); }

$label = $( ' ', { 'for': cur_id, 'text': current.label } ); $cur_div.append( $label );

if ( current.tooltip ) { QuickForm.element.generateTooltip( $label, current ); }					if ( current.subgroup ) { var tmpgroup, $subgroup;

tmpgroup = $.extend( {}, current.subgroup ); tmpgroup.name = (current.name || data.name) + '.' + tmpgroup.name;

if ( !tmpgroup.type ) { tmpgroup.type = data.type; }

$subgroup = QuickForm.element( tmpgroup ).compute( cur_id )[0];

$subgroup.style( 'marginLeft', '3em' ); $input.data({ 'subgroup': $subgroup, 'shown': false });

if ( current.checked ) { $cur_div.append( $subgroup ); }

$input.on( 'change', function(e) {							var $target = $( e.target ), name;

if ( !e.target.checked ) { $target.data( 'subgroup' ).remove; return; }

$target.parent.append( $target.data( 'subgroup' ) );

if ( $target.attr( 'type' ) !== 'radio' ) { return; }

name = $target.attr( 'name' );

if ( typeof e.target.form.names[name] !== 'undefined' ) { e.target.form.names[name].data( 'subgroup' ).remove; }

e.target.form.names[name] = $target; });					} else if ( data.type === 'radio' ) {						$input.on( 'change', function(e) { var name = $( e.target ).attr( 'name' ); if ( e.target.checked ) { if ( typeof e.target.form.names[name] !== 'undefined' ) { e.target.form.names[name].data( 'subgroup' ).remove; }								delete e.target.form.names[name]; } 						});					}				}			}			break;		case 'input':			$node = $( ' ' );

if ( data.label ) { $node.append( $label = $( ' ', { 'for': id, 'text': data.label } ) ); }

$input = $( ' ', { 'name': data.name, 'type': 'text' } ); $node.append( $input );

if ( data.value ) { $input.val( data.value ); }			if ( data.size ) { $input.attr( 'size', data.size ); }			if ( data.disabled ) { $input.prop( 'disabled', true ); }			if ( data.readonly ) { $input.prop( 'readonly', true ); }			if ( data.maxlength ) { $input.attr( 'maxlength', data.maxlength ); }			if ( data.event ) { $input.on( 'keyup', data.event ); }			break; case 'dyninput': var min = data.min || 1; var max = data.max || Infinity;

$node = $( ' ' ).append( $label = $( ' ', { 'text': data.label } ) ); $cur_div = $( ' ' );

$node.append( $cur_div );

current	= QuickForm.elements({				type: 'button',				label: 'more',				disabled: min >= max,				event: function(e) {					var	data = $( e.target ).data,						area = data.area;						elem = new QuickForm.element( data.sublist );

e.stopPropagation; data.area.append( elem.render );

if ( ++data.counter >= data.max ) { $( e.target ).prop( 'disabled', true ); }				}			}).compute;

var sublist = { type:		'_dyninput_element', label:		data.sublabel || data.label, name:		data.name, value:		data.value, size:		data.size, remove:		false, maxlength:	data.maxlength, event:		data.event };

$node.append( current[0] );

for ( var i = 0; i < min; ++i ) { $cur_div.append( QuickForm.element( sublist ).render ); }

$.extend( sublist, {				'remove':	true,				'button':	current[1],				'elements':	$cur_div			}); current[1].data({				'sublist':	sublist,				'area':		$cur_node,				'max':		max - min,				'counter':	0			});

break; case '_dyninput_element': // Private, similar to normal input $node = $( ' ' );

if ( data.label ) { $label = $( ' ', { 'for': id, 'text': data.label } ) $node.append( $label ); }

$input = $( ' ', { 'name': data.name, 'type': 'text' } ); $node.append( $input );

if ( data.value ) { $input.attr( 'value', data.value ); }			if ( data.size ) { $input.attr( 'size', data.size ); }			if ( data.maxlength ) { $input.attr( 'maxlength', data.maxlength ); }			if ( data.event ) { $input.on( 'keyup', data.event ); }			if ( data.remove ) { current = QuickForm.element({					type: 'button',					label: 'remove',					event: function(e) {						var	data = $( e.target ).data,							$node = data.input,							$more = data.button;

$node.remove; --more.counter; $more.removeAttribute( 'disabled' ); e.stopPropagation; }				}).compute;

$node.append( current[0] ); current[1].data({ 'input': $node, 'button': data.button }); }			break; case 'hidden': $node = $( ' ', {				'name': data.name,				'type': 'hidden',				'value': data.value,				'data': {					'values':	data.value				}			}); break; case 'header': $node = $( ' ', { 'text': data.label } ); break; case 'div': $node = $( ' ' ); break; case 'submit': $node = $( ' ' ); $child = $( ' ', { 'type': 'submit', 'name': data.name || 'submit' } ); $node.append( $child ); if ( data.label ) { $child.attr( 'value', data.label ); }			if ( data.disabled ) { $child.prop( 'disabled', true ); }			break; case 'button': $node = $( ' ' ); $child = $( ' ', { 'type': 'button', 'name': data.name } ); $node.append( $child ); if ( data.label ) { $child.attr( 'value', data.label ); }			if ( data.disabled ) { $child.prop( 'disabled', true ); }			if ( data.event ) { $child.on( 'click', data.event ); }			break; case 'textarea': $node = $( ' ' ); if ( data.label ) { $label = $( ' ', { 'for': id, 'text': data.label } ); $node.append( $label ); }			$cur_div = $( ' ', { 'name': data.name } ); $node.append( $cur_div ); if ( data.cols ) { $cur_div.attr( 'cols', data.cols ); }			if ( data.rows ) { $cur_div.attr( 'rows', data.rows ); }			if ( data.disabled ) { $cur_div.prop( 'disabled', true ); }			if ( data.readonly ) { $cur_div.prop( 'readonly', true ); }			if ( data.value ) { $cur_div.val( data.value ); }			break; default: throw new Error("QuickForm: Unknown element type " + data.type.toString); }

if ( !$child ) { $child = $node; }	if ( data.tooltip ) { QuickForm.element.generateTooltip( $label || $node, data ); }	if ( data.extra ) { $child.data( 'extra', extra ); }

$child.attr( 'id', data.id || id ); return [ $node, $child ]; };

QuickForm.element.generateTooltip = function QuickFormElementGenerateTooltip( $node, data ) { $( ' ', {		'class':	'ui-icon ui-icon-help ui-icon-inline twinkle-tooltip'	}).appendTo( $node ); };

/* * returns an array containing the values of elements with the given name, that has it's * checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select options * that have selected set to true. (don't try to mix selects with radio/checkboxes, please) * Type is optional and can specify if either radio or checkbox (for the event * that both checkboxes and radiobuttons have the same name. */ HTMLFormElement.prototype.getChecked = function( name, type ) {	var elements = this.elements[name];	if( !elements ) { 		// if the element doesn't exists, return null.		return null;	}	var return_array = [];	if( elements instanceof HTMLSelectElement ) {		var options = elements.options;		for( var i = 0; i < options.length; ++i ) {			if( options[i].selected ) {				if( options[i].values ) {					return_array.push( options[i].values );				} else {					return_array.push( options[i].value );				}

}		}	} else if( elements instanceof HTMLInputElement ) { if( type != null && elements.type != type ) { return []; } else if( elements.checked ) { return [ elements.value ]; }	} else { for( var i = 0; i < elements.length; ++i ) { if( elements[i].checked ) { if( type != null && elements[i].type != type ) { continue; }				if( elements[i].values ) { return_array.push( elements[i].values ); } else { return_array.push( elements[i].value ); }			}		}	}	return return_array; }

/* * returns an array containing the values of elements with the given name, that has non-empty strings * type is "text" or given. */ HTMLFormElement.prototype.getTexts = function( name, type ) { type == type || 'text'; var elements = this.elements[name]; if( !elements ) { // if the element doesn't exists, return null. return null; }	var return_array = []; for( var i = 0; i < elements.length; ++i ) { if( elements[i].value != '' ) { return_array.push( elements[i].value ); }	}	return return_array; } /** RegExp.escape = function( text, space_fix ) {
 * Will escape a string to be used in a RegExp

if ( !arguments.callee.sRE ) { arguments.callee.sRE = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^)/g; }

text = text.replace( arguments.callee.sRE, '\\$1' );

// Special Mediawiki escape, underscore/space is the same, often at lest:

if( space_fix ) { text = text.replace( / |_/g, '[_ ]' ); }

return text;

}

// Sprintf implementation based on perl similar function sprintf { if( arguments.length == 0 ) { throw "Not enough arguments for sprintf"; }	var result = ""; var format = arguments[0];

var index = 1; var current_index = 1; var flags = {}; var in_operator = false; var relative = false; var precision = false; var fixed = false; var vector = false; var vector_delimiter = '.';

for( var i = 0; i < format.length; ++i ) { var current_char = format.charAt(i); if( in_operator ) { switch( current_char ) { case 'i': current_char = 'd'; break; case 'F': current_char = 'f'; break; case '%': case 'c': case 's': case 'd': case 'u': case 'o': case 'x': case 'e': case 'f': case 'g': case 'X': case 'E': case 'G': case 'b': var value = arguments[current_index]; if( vector ) { r = value.toString.split( '' ); result += value.toString.split('').map( function( value ) {							return sprintf.format( current_char, value.charCodeAt, flags );						}).join( vector_delimiter ); } else { result += sprintf.format( current_char, value, flags ); }				if( !fixed ) { ++index; }				current_index = index; flags = {}; relative = false; in_operator = false; precision = false; fixed = false; vector = false; vector_delimiter = '.'; break; case 'v': vector = true; break; case ' ': case '0': case '-': case '+': case '#': flags[current_char] = true; break; case '*': relative = true; break; case '.': precision = true; break; }			if( /\d/.test( current_char ) ) { var num = parseInt( format.substr( i ) ); var len = num.toString.length; i += len - 1; var next = format.charAt( i + 1 ); if( next == '$' ) { if( num <= 0 || num >= arguments.length ) { throw "out of bound"; }					if( relative ) { if( precision ) { flags['precision'] = arguments[num]; precision = false; } else if( format.charAt( i + 2 ) == 'v' ) { vector_delimiter = arguments[num]; }else { flags['width'] = arguments[num]; }						relative = false; } else { fixed = true; current_index = num; }					++i; } else if( precision ) { flags['precision'] = num; precision = false; } else { flags['width'] = num; }			} else if ( relative && !/\d/.test( format.charAt( i + 1 ) ) ) { if( precision ) { flags['precision'] = arguments[current_index]; precision = false; } else if( format.charAt( i + 1 ) == 'v' ) { vector_delimiter = arguments[current_index]; } else { flags['width'] = arguments[current_index]; }				++index; if( !fixed ) { current_index++; }				relative = false; }		} else { if( current_char == '%' ) { in_operator = true; continue; } else { result += current_char; continue; }		}	}	return result; }

sprintf.format = function sprintfFormat( type, value, flags ) {

// Similar to how perl printf works if( value == undefined ) { if( type == 's' ) { return ''; } else { return '0'; }	}

var result; var prefix = ''; var fill = ''; var fillchar = ' '; switch( type ) { case '%': result = '%'; break; case 'c': result = String.fromCharCode( parseInt( value ) ); break; case 's': result = value.toString; break; case 'd': result = parseInt( value ).toString; break; case 'u': result = Math.abs( parseInt( value ) ).toString; // it's not correct, but JS lacks unsigned ints break; case 'o': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(8); break; case 'x': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16); break; case 'b': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(2); break; case 'e': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toExponential( digits ).toString; break; case 'f': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toFixed( digits ).toString; case 'g': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toPrecision( digits ).toString; break; case 'X': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16).toUpperCase; break; case 'E': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toExponential( digits ).toString.toUpperCase; break; case 'G': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toPrecision( digits ).toString.toUpperCase; break; }

if(flags['+'] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) { prefix = '+'; }

if(flags[' '] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) { prefix = ' '; }

if( flags['#'] && parseInt( value ) != 0 ) { switch(type) { case 'o': prefix = '0'; break; case 'x': case 'X': prefix = '0x'; break; case 'b': prefix = '0b'; break; }	}

if( flags['0'] && !flags['-'] ) { fillchar = '0'; }

if( flags['width'] && flags['width'] > ( result.length + prefix.length ) ) { var tofill = flags['width'] - result.length - prefix.length; for( var i = 0; i < tofill; ++i ) { fill += fillchar; }	}

if( flags['-'] && !flags['0'] ) { result += fill; } else { result = fill + result; }	return prefix + result; }

var Bytes = function ( value ) { if( typeof(value) == 'string' ) { var res = /(\d+) ?(\w?)(i?)B?/.exec( value ); var number = res[1]; var mag = res[2]; var si = res[3];

if( ! number ) { this.number = 0; return; }

if( !si ) { this.value = number * Math.pow( 10, Bytes.magnitudes[mag] * 3 ); } else { this.value = number * Math.pow( 2, Bytes.magnitudes[mag] * 10 ); }	} else { this.value = value; } }

Bytes.magnitudes = { '': 0,	'K': 1, 'M': 2, 'G': 3, 'T': 4, 'P': 5, 'E': 6, 'Z': 7, 'Y': 8 } Bytes.rmagnitudes = { 0: '',	1: 'K', 2: 'M', 3: 'G', 4: 'T', 5: 'P', 6: 'E', 7: 'Z', 8: 'Y' }

Bytes.prototype.valueOf = function { return this.value; }

Bytes.prototype.toString = function( magnitude ) { var tmp = this.value; if( magnitude ) { var si = /i/.test(magnitude); var mag = magnitude.replace( /.*?(\w)i?B?.*/g, '$1' ); if( si ) { tmp /= Math.pow( 2, Bytes.magnitudes[mag] * 10 ); } else { tmp /= Math.pow( 10, Bytes.magnitudes[mag] * 3 ); }		if( parseInt( tmp ) != tmp ) { tmp = (new Number( tmp ) ).toPrecision( 4 ); }		return tmp + ' ' + mag + (si?'i':'') + 'B'; } else { // si per default var current = 0; while( tmp >= 1024 ) { tmp /= 1024; ++current; }		tmp = this.value / Math.pow( 2, current * 10 ); if( parseInt( tmp ) != tmp ) { tmp = (new Number( tmp ) ).toPrecision( 4 ); }		return tmp + ' ' + Bytes.rmagnitudes[current] + ( current > 0 ? 'iB' : 'B' ); }

} String.prototype.ltrim = function stringPrototypeLtrim( chars ) { chars = chars || "\\s*"; return this.replace( new RegExp("^[" + chars + "]+", "g"), "" ); }

String.prototype.rtrim = function stringPrototypeRtrim( chars ) { chars = chars || "\\s*"; return this.replace( new RegExp("[" + chars + "]+$", "g"), "" ); } String.prototype.trim = function stringPrototypeTrim( chars ) { return this.rtrim(chars).ltrim(chars); }

String.prototype.splitWeightedByKeys = function stringPrototypeSplitWeightedByKeys( start, end, skip ) { if( start.length != end.length ) { throw 'start marker and end marker must be of the same length'; }	var level = 0; var initial = null; var result = []; if( !( skip instanceof Array ) ) { if( typeof( skip ) == 'undefined' ) { skip = []; } else if( typeof( skip ) == 'string' ) { skip = [ skip ]; } else { throw "non-applicable skip parameter"; }	}	for( var i = 0; i < this.length; ++i ) { for( var j = 0; j < skip.length; ++j ) { if( this.substr( i, skip[j].length ) == skip[j] ) { i += skip[j].length - 1; continue; }		}		if( this.substr( i, start.length ) == start ) { if( initial == null ) { initial = i;			} ++level; i += start.length - 1; } else if( this.substr( i, end.length ) == end ) { --level; i += end.length - 1; }		if( level == 0 && initial != null ) { result.push( this.substring( initial, i + 1 ) ); initial = null; }	}

return result; }

Array.prototype.uniq = function arrayPrototypeUniq { var result = []; for( var i = 0; i < this.length; ++i ) { var current = this[i]; if( result.indexOf( current ) == -1 ) { result.push( current ); }	}	return result; }

Array.prototype.dups = function arrayPrototypeUniq { var uniques = []; var result = []; for( var i = 0; i < this.length; ++i ) { var current = this[i]; if( uniques.indexOf( current ) == -1 ) { uniques.push( current ); } else { result.push( current ); }	}	return result; }

Array.prototype.chunk = function arrayChunk( size ) { if( typeof( size ) != 'number' || size <= 0 ) { // pretty impossible to do anything :)		return [ this ]; // we return an array consisting of this array.	}	var result = [];	var current;	for(var i = 0; i < this.length; ++i ) {		if( i % size == 0 ) { // when 'i' is 0, this is always true, so we start by creating one.			current = [];			result.push( current );		}		current.push( this[i] );	}   return result; }

var Unbinder = function unbinder( string ) { if( typeof( string ) != 'string' ) { throw "not a string"; }	this.content = string; this.counter = 0; this.history = {}; this.prefix = '%UNIQ::' + Math.random + '::'; this.postfix = '::UNIQ%'; }

Unbinder.prototype = { unbind: function UnbinderUnbind( prefix, postfix ) { var re = new RegExp( prefix + '(.*?)' + postfix, 'g' ); this.content = this.content.replace( re, Unbinder.getCallback( this ) ); }, rebind: function UnbinderRebind { var content = this.content; content.self = this; for( var current in this.history ) if( this.history.hasOwnProperty( current ) ) content = content.replace( current, this.history[current] ); return content; }, prefix: null, // %UNIQ::0.5955981644938324:: postfix: null, // ::UNIQ% content: null, // string counter: null, // 0++ history: null // {} };

Unbinder.getCallback = function UnbinderGetCallback(self) { return function UnbinderCallback( match, a , b ) { var current = self.prefix + self.counter + self.postfix; self.history[current] = match; ++self.counter; return current; }; };

function clone( obj, deep ) { var objectClone = new obj.constructor; for ( var property in obj ) if ( !deep ) { objectClone[property] = obj[property]; }   else if ( typeof obj[property] == 'object' ) { objectClone[property] = clone( obj[property], deep ); }   else { objectClone[property] = obj[property]; } return objectClone; }

function ln( ns, title )	{ var ns2ln = { '0'	:	'la', '1'	:	'lat', '2'	:	'lu', '3'	:	'lut', '4'	:	'lw', '5'	:	'lwt', '6'	:	'li', '7'	:	'lit', '8'	:	'lm', '9'	:	'lmt', '10':	'lt', '11':	'ltt', '12':	'lh', '13':	'lht', '14':	'lc', '15':	'lct', '100':	'lp', '101':	'lpt' };	return "\{\{" + ns2ln[ns] + "|" + title + "\}\}"; }

// Helper functions to change case of a string String.prototype.toUpperCaseFirstChar = function { return this.substr( 0, 1 ).toUpperCase + this.substr( 1 ); }

String.prototype.toLowerCaseFirstChar = function { return this.substr( 0, 1 ).toLowerCase + this.substr( 1 ); }

String.prototype.toUpperCaseEachWord = function( delim ) { delim = delim ? delim : ' '; return this.split( delim ).map( function(v) { return v.toUpperCaseFirstChar } ).join( delim ); }

String.prototype.toLowerCaseEachWord = function( delim ) { delim = delim ? delim : ' '; return this.split( delim ).map( function(v) { return v.toLowerCaseFirstChar } ).join( delim ); }

/**
 * Helper functions to get the month as a string instead of a number

Date.monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; Date.monthNamesAbbrev = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];

Date.prototype.getMonthName = function { return Date.monthNames[ this.getMonth ]; }

Date.prototype.getMonthNameAbbrev = function { return Date.monthNamesAbbrev[ this.getMonth ]; } Date.prototype.getUTCMonthName = function { return Date.monthNames[ this.getUTCMonth ]; }

Date.prototype.getUTCMonthNameAbbrev = function { return Date.monthNamesAbbrev[ this.getUTCMonth ]; }

// Accessor functions for wikiediting and api-access var Wikipedia = {};

// we dump all XHR here so they won't loose props Wikipedia.dump = [];

Wikipedia.numberOfActionsLeft = 0; Wikipedia.nbrOfCheckpointsLeft = 0;

Wikipedia.actionCompleted = function( self ) { if( --Wikipedia.numberOfActionsLeft <= 0 && Wikipedia.nbrOfCheckpointsLeft <= 0 ) { Wikipedia.actionCompleted.event( self ); } }

// Change per action wanted Wikipedia.actionCompleted.event = function { new Status( Wikipedia.actionCompleted.notice, Wikipedia.actionCompleted.postfix, 'info' ); if( Wikipedia.actionCompleted.redirect != null ) { // if it isn't an url, make it an relative to self (probably this is the case) if( !/^\w+\:\/\//.test( Wikipedia.actionCompleted.redirect ) ) { Wikipedia.actionCompleted.redirect = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace( '$1', encodeURIComponent( Wikipedia.actionCompleted.redirect ).replace( /\%2F/g, '/' ) ); }		window.setTimeout( function { window.location = Wikipedia.actionCompleted.redirect }, Wikipedia.actionCompleted.timeOut ); } }

if ( typeof(wpActionCompletedTimeOut) == 'undefined' ) var wpActionCompletedTimeOut = 5000; if ( typeof(wpMaxLag) == 'undefined' ) var wpMaxLag = 10; // Maximum lag allowed, 5-10 is a good value, the higher value, the more agressive.

Wikipedia.editCount = 10; Wikipedia.actionCompleted.timeOut = wpActionCompletedTimeOut; Wikipedia.actionCompleted.redirect = null; Wikipedia.actionCompleted.notice = 'Action'; Wikipedia.actionCompleted.postfix = 'completed';

Wikipedia.addCheckpoint = function { ++Wikipedia.nbrOfCheckpointsLeft; }

Wikipedia.removeCheckpoint = function { if( --Wikipedia.nbrOfCheckpointsLeft <= 0 && Wikipedia.numberOfActionsLeft <= 0 ) { Wikipedia.actionCompleted.event; } }

/* currentAction: text, the current action (required) query: Object, the query (required) oninit: function, the function to call when page gotten */ Wikipedia.api = function( currentAction, query, oninit, statelem ) { this.currentAction = currentAction; this.query = query; this.query['format'] = 'xml'; //LET THE FORCE BE WITH YOU!!! this.oninit = oninit; if( statelem ) { statelem.status( currentAction ) } else { this.statelem = new Status( currentAction ); }	++Wikipedia.numberOfActionsLeft; } Wikipedia.api.prototype = { currentAction: '', oninit: null, query: null, responseXML: null, statelem: null, counter: 0, post: function { var xmlhttp = sajax_init_object; Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'POST', mw.config.get('wgServer') + wgScriptPath + '/api.php', true); xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xmlhttp.onerror = function { this.obj.statelem.error( "Error " + this.status + " occurred while quering the api." ); }		xmlhttp.onload = function { this.obj.responseXML = this.responseXML; if( this.obj.oninit ) { this.obj.oninit( this.obj ); }			Wikipedia.actionCompleted; };		xmlhttp.send( QueryString.create( this.query ) ); } }

/* currentAction: text, the current action (required) query: Object, the query (required) oninit: function, the function to call when page gotten (required) onsuccess: function, a function to call when post succeeded onerror: function, a function to call when we abort failed posts onretry: function, a function to call when we try to retry a post */ Wikipedia.wiki = function( currentAction, query, oninit, onsuccess, onerror, onretry ) { this.currentAction = currentAction; this.query = query; this.oninit = oninit; this.onsuccess = onsuccess; this.onerror = onerror; this.onretry = onretry; this.statelem = new Status( currentAction ); ++Wikipedia.numberOfActionsLeft; }

Wikipedia.wiki.prototype = { currentAction: '', onsuccess: null, onerror: null, onretry: null, oninit: null, query: null, postData: null, responseXML: null, statelem: null, counter: 0, post: function( data ) { this.postData = data; if( Wikipedia.editCount <= 0 ) { this.query['maxlag'] = wpMaxLag; // are we a bot? } else { --Wikipedia.editCount; }

var xmlhttp = sajax_init_object; Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'POST', mw.config.get('wgServer') + wgScriptPath + '/index.php?useskin=monobook&' + QueryString.create( this.query ), true); xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xmlhttp.onerror = function(e) { var self = this.obj; self.statelem.error( "Error " + this.status + " occurred while posting the document." ); }		xmlhttp.onload = function(e) { var self = this.obj; var status = this.status; if( status != 200 ) { if( status == 503 ) { var retry = this.getResponseHeader( 'Retry-After' ); var lag = this.getResponseHeader( 'X-Database-Lag' ); if( lag ) { self.statelem.warn( "current lag of " + lag + " seconds is more than our defined maximum lag of " + wpMaxLag + " seconds, will retry in " + retry + " seconds" ); window.setTimeout( function( self ) { self.post( self.postData ); }, retry * 1000, self ); return; } else { self.statelem.error( "Error " + status + " occurred while posting the document." ); }				}				return; }			var xmlDoc; xmlDoc = self.responseXML = this.responseXML; var xpathExpr = 'boolean(//div[@class=\'previewnote\']/p/strong[contains(.,\'Sorry! We could not process your edit due to a loss of session data\')])'; var nosession = xmlDoc.evaluate( xpathExpr, xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue; if( nosession ) { // Grabbing the shipping token, and repost var new_token = xmlDoc.evaluate( '//input[@name="wfEditToken"]/@value', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue; self.postData['wfEditToken'] = new_token; self.post( self.postData ); } else { if( self.onsuccess ) { self.onsuccess( self ); } else { var link = document.createElement( 'a' ); link.setAttribute( 'href', mw.config.get('wgArticlePath').replace( '$1', self.query['title'] ) ); link.setAttribute( 'title', self.query['title'] ); link.appendChild( document.createTextNode( self.query['title'] ) );

self.statelem.info( [ 'completed (', link , ')' ] ); }				Wikipedia.actionCompleted; }		};		xmlhttp.send( QueryString.create( this.postData ) ); },	get: function { this.onloading( this ); var redirect_query = { 'action': 'query', 'titles': this.query['title'], 'redirects': '' }

var wikipedia_api = new Wikipedia.api( "resolving eventual redirect", redirect_query, this.postget, this.statelem ); wikipedia_api.parent = this; wikipedia_api.post; },	postget: function { var xmlDoc = self.responseXML = this.responseXML; var to = xmlDoc.evaluate( '//redirects/r/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue; if( !this.parent.followRedirect ) { this.parent.statelem.info('ignoring eventual redirect'); } else if( to ) { this.parent.query['title'] = to; }		this.parent.onloading( this ); var xmlhttp = sajax_init_object; Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this.parent; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'GET', mw.config.get('wgServer') + wgScriptPath + '/index.php?useskin=monobook&' + QueryString.create( this.parent.query ), true); xmlhttp.onerror = function { var self = this.obj; self.statelem.error( "Error " + this.status + " occurred while receiving the document." ); }		xmlhttp.onload = function { this.obj.onloaded( this.obj ); this.obj.responseXML = this.responseXML; this.obj.responseText = this.responseText; this.obj.oninit( this.obj ); };		xmlhttp.send( null ); },	onloading: function { this.statelem.status( 'loading data...' ); },	onloaded: function { this.statelem.status( 'data loaded...' ); } }

Number.prototype.zeroFill = function( length ) { var str = this.toFixed; if( !length ) { return str; } while( str.length < length ) { str = '0' + str; } return str; }

var Mediawiki = {};

Mediawiki.Template = { parse: function( text, start ) { var count = -1; var level = -1; var equals = -1; var current = ''; var result = { name: '', parameters: {} };

for( var i = start; i < text.length; ++i ) { var test3 = text.substr( i, 3 ); if( test3 == '\{\{\{' ) { current += '\{\{\{'; i += 2; ++level; continue; }			if( test3 == '\}\}\}' ) { current += '\}\}\}'; i += 2; --level; continue; }			var test2 = text.substr( i, 2 ); if( test2 == '\{\{' || test2 == '\[\[' ) { current += test2; ++i; ++level; continue; }			if( test2 == '\]\]' ) { current += test2; ++i; --level; continue; }			if( test2 == '\}\}' ) { current += test2; ++i; --level;

if( level <= 0 ) { if( count == -1 ) { result.name = current.substring(2).trim; ++count; } else { if( equals != -1 ) { var key = current.substring( 0, equals ).trim; var value = current.substring( equals ).trim; result.parameters[key] = value; equals = -1; } else { result.parameters[count] = current; ++count; }					}					break; }				continue; }

if( text.charAt(i) == '|' && level <= 0 ) { if( count == -1 ) { result.name = current.substring(2).trim; ++count; } else { if( equals != -1 ) { var key = current.substring( 0, equals ).trim; var value = current.substring( equals + 1 ).trim; result.parameters[key] = value; equals = -1; } else { result.parameters[count] = current; ++count; }				}				current = ''; } else if( equals == -1 && text.charAt(i) == '=' && level <= 0 ) { equals = current.length; current += text.charAt(i); } else { current += text.charAt(i); }		}

return result; } }

Mediawiki.Page = function mediawikiPage( text ) { this.text = text; }

Mediawiki.Page.prototype = { text: '', removeLink: function( link_target ) { var first_char = link_target.substr( 0, 1 ); var link_re_string = "[" + first_char.toUpperCase + first_char.toLowerCase + ']' + RegExp.escape( link_target.substr( 1 ), true ); var link_simple_re = new RegExp( "\\[\\[(" + link_re_string + ")\\|?\\]\\]", 'g' ); var link_named_re = new RegExp( "\\[\\[" + link_re_string + "\\|(.+?)\\]\\]", 'g' ); if( link_simple_re.test(this.text) ) { this.text = this.text.replace( link_simple_re, "$1" ); } else { this.text = this.text.replace( link_named_re, "$1" ); }	},	commentOutImage: function( image, reason ) { var unbinder = new Unbinder( this.text ); unbinder.unbind( '' );

reason = reason ? ' ' + reason + ': ' : ''; var first_char = image.substr( 0, 1 ); var image_re_string = "[" + first_char.toUpperCase + first_char.toLowerCase + ']' + RegExp.escape( image.substr( 1 ), true );

/*		 * Check for normal image links, i.e. 		 * Will eat the whole link */		var links_re = new RegExp( "\\[\\[(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string ); var allLinks = unbinder.content.splitWeightedByKeys( ,  ).uniq; for( var i = 0; i < allLinks.length; ++i ) { if( links_re.test( allLinks[i] ) ) { var replacement = ''; unbinder.content = unbinder.content.replace( allLinks[i], replacement, 'g' ); }		}		// unbind the newly created comments unbinder.unbind( '' ); /*		 * Check for gallery images, i.e. instances that must start on a new line, eventually preceded with some space, and must include Image: prefix * Will eat the whole line. */		var gallery_image_re = new RegExp( "(^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string + ".*?$)", 'mg' ); unbinder.content.replace( gallery_image_re, "" );

// unbind the newly created comments unbinder.unbind( '' ); /*		 * Check free image usages, for example as template arguments, might have the Image: prefix excluded, but must be preceeded by an | * Will only eat the image name and the preceeding bar and an eventual named parameter */		var free_image_re = new RegExp( "(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" + image_re_string + ")", 'mg' ); unbinder.content.replace( free_image_re, "" );

// Rebind the content now, we are done! this.text = unbinder.rebind; },	addToImageComment: function( image, data ) { var first_char = image.substr( 0, 1 ); var image_re_string = "(?:[Ii]mage|[Ff]ile):\\s*[" + first_char.toUpperCase + first_char.toLowerCase + ']' + RegExp.escape( image.substr( 1 ), true ); var links_re = new RegExp( "\\[\\[" + image_re_string ); var allLinks = this.text.splitWeightedByKeys( ,  ).uniq; for( var i = 0; i < allLinks.length; ++i ) { if( links_re.test( allLinks[i] ) ) { var replacement = allLinks[i]; // just put it at the end? replacement = replacement.replace( /\]\]$/, '|' + data + ']]' ); this.text = this.text.replace( allLinks[i], replacement, 'g' ); }		}		var gallery_re = new RegExp( "^(\\s*" + image_re_string + '.*?)\\|?(.*?)$', 'mg' ); var replacement = "$1|$2 " + data; this.text = this.text.replace( gallery_re, replacement ); },	removeTemplate: function( template ) { var first_char = template.substr( 0, 1 ); var template_re_string = "(?:[Tt]emplate:)?\\s*[" + first_char.toUpperCase + first_char.toLowerCase + ']' + RegExp.escape( template.substr( 1 ), true ); var links_re = new RegExp( "\\\{\\\{" + template_re_string ); var allTemplates = this.text.splitWeightedByKeys( '{\{', '}}', [ '' ] ).uniq; for( var i = 0; i < allTemplates.length; ++i ) { if( links_re.test( allTemplates[i] ) ) { this.text = this.text.replace( allTemplates[i], '', 'g' ); }		}

},	getText: function { return this.text; } }

// Simple helper functions to see what groups a user might belong

function userIsInGroup( group ) {

return ( mw.config.get('wgUserGroups') != null && mw.config.get('wgUserGroups').indexOf( group ) != -1 ) || ( mw.config.get('wgUserGroups') == null && group == 'anon' ); }

function userIsAnon { return mw.config.get('wgUserGroups') == null; }

// AOL Proxy IP Addresses (2007-02-03) var AOLNetworks = [ '64.12.96.0/19',	'149.174.160.0/20',	'152.163.240.0/21',	'152.163.248.0/22',	'152.163.252.0/23',	'152.163.96.0/22',	'152.163.100.0/23',	'195.93.32.0/22',	'195.93.48.0/22',	'195.93.64.0/19',	'195.93.96.0/19',	'195.93.16.0/20',	'198.81.0.0/22',	'198.81.16.0/20',	'198.81.8.0/23',	'202.67.64.128/25',	'205.188.192.0/20',	'205.188.208.0/23',	'205.188.112.0/20',	'205.188.146.144/30',	'207.200.112.0/21', ];

// AOL Client IP Addresses (2007-02-03) var AOLClients = [ '172.128.0.0/10',	'172.192.0.0/12',	'172.208.0.0/14',	'202.67.66.0/23',	'172.200.0.0/15',	'172.202.0.0/15',	'172.212.0.0/14',	'172.216.0.0/16',	'202.67.68.0/22',	'202.67.72.0/21',	'202.67.80.0/20',	'202.67.96.0/19', ];

/**
 * ipadress is in the format 1.2.3.4 and network is in the format 1.2.3.4/5

function isInNetwork( ipaddress, network ) { var iparr = ipaddress.split('.'); var ip = (parseInt(iparr[0]) << 24) + (parseInt(iparr[1]) << 16) + (parseInt(iparr[2]) << 8) + (parseInt(iparr[3]));

var netmask = 0xffffffff << network.split('/')[1];

var netarr = network.split('/')[0].split('.'); var net = (parseInt(netarr[0]) << 24) + (parseInt(netarr[1]) << 16) + (parseInt(netarr[2]) << 8) + (parseInt(netarr[3]));

return (ip & netmask) == net; }

/* Returns true if given string contains a valid IP-address, that is, from 0.0.0.0 to 255.255.255.255*/ function isIPAddress( string ){ var res = /(\d{1,4})\.(\d{1,3})\.(\d{1,3})\.(\d{1,4})/.exec( string ); return res != null && res.slice( 1, 5 ).every( function( e ) { return e < 256; } ); }

/** function QueryString(qString) { this.string = qString; this.params = {};
 * Maps the querystring to an object
 * Functions:
 * QueryString.exists(key)
 * returns true if the particular key is set
 * QueryString.get(key)
 * returns the value associated to the key
 * QueryString.equals(key, value)
 * returns true if the value associated with given key equals given value
 * QueryString.toString
 * returns the query string as a string
 * QueryString.create( hash )
 * creates an querystring and encodes strings via encodeURIComponent and joins arrays with |
 * In static context, the value of location.search.substring(1), else the value given to the constructor is going to be used. The mapped hash is saved in the object.
 * Example:
 * var value = QueryString.get('key');
 * var obj = new QueryString('foo=bar&baz=quux');
 * value = obj.get('foo');
 * Example:
 * var value = QueryString.get('key');
 * var obj = new QueryString('foo=bar&baz=quux');
 * value = obj.get('foo');
 * value = obj.get('foo');

if( qString.length == 0 ) { return; }

qString.replace(/\+/, ' '); var args = qString.split('&');

for( var i = 0; i < args.length; ++i ) { var pair = args[i].split( '=' ); var key = decodeURIComponent( pair[0] ), value = key;

if( pair.length == 2 ) { value = decodeURIComponent( pair[1] ); }

this.params[key] = value; } }

QueryString.static = null;

QueryString.staticInit = function { if( QueryString.static == null ) { QueryString.static = new QueryString(location.search.substring(1)); } }

QueryString.get = function(key) { QueryString.staticInit; return QueryString.static.get(key); };

QueryString.prototype.get = function(key) { return this.params[key] ? this.params[key] : null; };

QueryString.exists = function(key) { QueryString.staticInit; return QueryString.static.exists(key); }

QueryString.prototype.exists = function(key) { return this.params[key] ? true : false; }

QueryString.equals = function(key, value) { QueryString.staticInit; return QueryString.static.equals(key, value); }

QueryString.prototype.equals = function(key, value) { return this.params[key] == value ? true : false; }

QueryString.toString = function { QueryString.staticInit; return QueryString.static.toString; }

QueryString.prototype.toString = function { return this.string ? this.string : null; }

QueryString.create = function( arr ) { var resarr = Array; var editToken; // KLUGE: this should always be the last item in the query string (bug TW-B-0013) for( var i in arr ) { if( typeof arr[i] == 'undefined' ) { continue; }		var res; if( arr[i] instanceof Array ){ var v = Array; for(var j = 0; j < arr[i].length; ++j ) { v[j] = encodeURIComponent( arr[i][j] ); }			res = v.join('|'); } else { res = encodeURIComponent( arr[i] ); }               if( i == 'wpEditToken' ) { editToken = res; } else { resarr.push( encodeURIComponent( i ) + '=' + res ); }	}	if( typeof editToken != 'undefined' ) { resarr.push( 'wpEditToken=' + editToken ); }	return resarr.join('&'); } QueryString.prototype.create = QueryString.create;

/**
 * Simple exception handling

var Exception = function( message ) { this.message = message || ''; this.name = "Exception"; }

Exception.prototype.what = function { return this.message; }

function Status( text, stat, type ) { this.text = this.codify(text); this.stat = this.codify(stat); this.type = type || 'status'; this.generate; if( stat ) { this.render; } } Status.init = function( root ) { if( !( root instanceof Element ) ) { throw new Exception( 'object not an instance of Element' ); }	while( root.hasChildNodes ) { root.removeChild( root.firstChild ); }	Status.root = root;

var cssNode = document.createElement('style'); cssNode.type = 'text/css'; cssNode.rel = 'stylesheet'; cssNode.appendChild( document.createTextNode("")); // Safari bugfix document.getElementsByTagName("head")[0].appendChild(cssNode); var styles = cssNode.sheet ? cssNode.sheet : cssNode.stylesSheet; styles.insertRule(".tw_status_status { color: SteelBlue; }", 0); styles.insertRule(".tw_status_info { color: ForestGreen; }", 0); styles.insertRule(".tw_status_warn { color: OrangeRed; }", 0); styles.insertRule(".tw_status_error { color: OrangeRed; font-weight: 900; }", 0); } Status.root = null;

Status.prototype = { stat: null, text: null, type: 'status', target: null, node: null, linked: false, link: function { if( ! this.linked && Status.root ) { Status.root.appendChild( this.node ); this.linked = true; }	},	unlink: function { if( this.linked ) { Status.root.removeChild( this.node ); this.linked = false; }	},	codify: function( obj ) { if ( ! ( obj instanceof Array ) ) { obj = [ obj ]; }		var result; result = document.createDocumentFragment; for( var i = 0; i < obj.length; ++i ) { if( typeof obj[i] == 'string' ) { result.appendChild( document.createTextNode( obj[i] ) ); } else if( obj[i] instanceof Element ) { result.appendChild( obj[i] ); } // Else cosmic radiation made something shit }		return result;

},	update: function( status, type ) { this.stat = this.codify( status ); if( type ) { this.type = type; }		this.render; },	generate: function { this.node = document.createElement( 'div' ); this.node.appendChild( document.createElement('span') ).appendChild( this.text ); this.node.appendChild( document.createElement('span') ).appendChild( document.createTextNode( ': ' ) ); this.target = this.node.appendChild( document.createElement( 'span' ) ); this.target.appendChild( document.createTextNode( '' ) ); // dummy node },	render: function { this.node.className = 'tw_status_' + this.type; while( this.target.hasChildNodes ) { this.target.removeChild( this.target.firstChild ); }		this.target.appendChild( this.stat ); this.link; },	status: function( status ) { this.update( status, 'status'); },	info: function( status ) { this.update( status, 'info'); },	warn: function( status ) { this.update( status, 'warn'); },	error: function( status ) { this.update( status, 'error'); } }

Status.status = function( text, status ) { return new Status( text, status, 'status' ); } Status.info = function( text, status ) { return new Status( text, status, 'info' ); } Status.warn = function( text, status ) { return new Status( text, status, 'error' ); } Status.error = function( text, status ) { return new Status( text, status, 'error' ); }

// Simple helper function to create a simple node function htmlNode( type, content, color ) { var node = document.createElement( type ); if( color ) { node.style.color = color; }	node.appendChild( document.createTextNode( content ) ); return node; }

// A simple dragable window

function SimpleWindow( width, height ) { var $window = $( ' ', { 'class': 'twinkle-dialog-content' } ), $widget;

this.$window = $window; this.$window.dialog({		autoOpen:	true,		buttons:	{ "Placeholder button": function {} },		dialogClass:	'twinkle-dialog',		width:		Math.min( parseInt( window.innerWidth, 10 ), parseInt( width || 800, 10 ) ),		height:		Math.min( parseInt( window.innerHeight, 10 ), parseInt( height || 600, 10 ) + 20 ),

close:		function(e, ui) { $(e.target).dialog('destroy').remove; }, resize:		function(e, ui) { $(this).css( 'maxHeight', '' ); } });

$widget = this.$window.dialog( "widget" );

// delete the placeholder button (it's only there so the buttonpane gets created) $widget.find( "button" ).remove;

// add container for the buttons we add $widget.find( ".ui-dialog-buttonpane" ).append( $( ' ', { 'class': 'twinkle-dialog-buttons' } ) ); }

SimpleWindow.prototype = { buttons:		[], hasFooterLinks:		false, scriptName:		null,

focus: function(e) { this.$window.dialog( "moveToTop" ); return this; },

close: function(e) { e.preventDefault; this.$window.dialog( "close" ); return this; },

display: function(e) { var $dialog;

if ( this.scriptName) { $dialog = this.$window.dialog("widget"); $dialog.find(".twinkle-dialog-scriptname").remove; $dialog.find(".ui-dialog-title").prepend(				// U+00B7 MIDDLE DOT = &middot;				$( ' ', { 'class': 'twinkle-dialog-scriptname', 'text': this.scriptName + " \u00B7" } )			); }

$dialog = $this.$window.dialog("open");

// tie in with NAVPOP if (window.setupTooltips) { dialog.parent[0].ranSetupTooltipsAlready = false; setupTooltips(dialog.parent[0]); }	},

title: function( str ) { this.$window.dialog( "option", "title", str ); return this; },

width: function( w ) { w = Math.min( parseInt( window.innerWidth, 10 ), parseInt( w, 10 ) ); this.$window.dialog( "option", "width", w ); return this; },

height: function( h ) { h = Math.min( parseInt( window.innerHeight, 10 ), parseInt( h, 10 ) + 20 ); this.$window.dialog( "option", "height", h ); return this; },

content: function( what ) { return this.empty.append( what ); },

empty: function { this.buttons = [];

this.$window.dialog( "widget" ).find( ".twinkle-dialog-buttons" ).empty; this.$window.empty;

return this; },

append: function( what ) { var buttons = this.buttons;

this.$window.append( what );

// look for submit buttons in the content, hide them, and add a proxy button to the button pane this.$window.find('input[type="submit"], button[type="submit"]').each( function {			var $what = $(this);

// look for submit buttons in the content, hide them, and add a proxy button to the button pane $what.css( 'display', 'none' ); buttons.push(				$( ' ' ).text( $what.val || 'Submit Query' ).click( function { $what.click; } )			); });

if ( buttons.length > 0) { this.$window .dialog( "widget" ) .find( ".twinkle-dialog-buttons" ) .empty .append( buttons ) .removeAttr( "data-empty" ); } else { this.$window .dialog( "widget" ) .find( ".twinkle-dialog-buttons" ) .attr( "data-empty", "data-empty" ); }

return this; },

modality: function( modal ) { this.$window.dialog( "option", "modal", modal ); return this; } };

// When Twinkle modules are imported, we can't be sure that this base module // has been loaded yet. For that reason, modules using them need // to initialize themselves using //  window.TwinkleInit = (window.TwinkleInit || []).concat( someInitializationFunction ); // for maximal robustness. Looks weird, works well. $(function {	var funcs = window.TwinkleInit;	window.TwinkleInit = {		concat : function(func) {			func;			return window.TwinkleInit;		}	}; // redefine the concat method used to enqueue initializers: From now on, they just execute immediately.	if (funcs) for (var i=0; i<funcs.length; i++) funcs[i]; });