text:
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 = ·				$( ' ', { '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]; });

title: MediaWiki:Gadget-twinkle.js


text:
MediaWiki:Gadget-twinklefluff.js

if (twinkleConfigExists) {

/** Twinklefluff revert and antivandalism utillity */ // If TwinkleConfig aint exist. if( typeof( TwinkleConfig ) == 'undefined' ) { TwinkleConfig = {}; }

/** TwinkleConfig.summaryAd (string) If ad should be added or not to summary, default TW */ if( typeof( TwinkleConfig.summaryAd ) == 'undefined' ) { TwinkleConfig.summaryAd = " (TW)"; }

/** TwinkleConfig.revertMaxRevisions (int) defines how many revision to query maximum, maximum possible is 50, default is 50 */ if( typeof( TwinkleConfig.revertMaxRevisions ) == 'undefined' ) { TwinkleConfig.revertMaxRevisions = 50; }

/** TwinkleConfig.userTalkPageMode may take arguments: 'window': open a new window, remember the opened window 'tab': opens in a new tab, if possible. 'blank': force open in a new window, even if a such window exist */ if( typeof( TwinkleConfig.userTalkPageMode ) == 'undefined' ) { TwinkleConfig.userTalkPageMode = 'window'; }

/** TwinkleConfig.openTalkPage (array) What types of actions that should result in opening of talk page */ if( typeof( TwinkleConfig.openTalkPage ) == 'undefined' ) { TwinkleConfig.openTalkPage = [ 'agf', 'norm', 'vand' ]; }

/** TwinkleConfig.openTalkPageOnAutoRevert (bool) Defines if talk page should be opened when canling revert from contrib page, this because from there, actions may be multiple, and opening talk page not suitable. If set to true, openTalkPage defines then if talk page will be opened. */ if( typeof( TwinkleConfig.openTalkPageOnAutoRevert ) == 'undefined' ) { TwinkleConfig.openTalkPageOnAutoRevert = false; }

/** TwinkleConfig.markRevertedPagesAsMinor (array) What types of actions that should result in marking edit as minor */ if( typeof( TwinkleConfig.markRevertedPagesAsMinor ) == 'undefined' ) { TwinkleConfig.markRevertedPagesAsMinor = [ 'vand' ]; }

/** TwinkleConfig.watchRevertedPages (array) What types of actions that should result in forced addition to watchlist */ if( typeof( TwinkleConfig.watchRevertedPages ) == 'undefined' ) { TwinkleConfig.watchRevertedPages = [ 'agf', 'norm', 'vand', 'torev' ]; }

/** TwinkleConfig.offerReasonOnNormalRevert (boolean) If to offer a promt for extra summary reason for normal reverts, default to true */ if( typeof( TwinkleConfig.offerReasonOnNormalRevert ) == 'undefined' ) { TwinkleConfig.offerReasonOnNormalRevert = true; }

/** TwinkleConfig.showRollbackLinks (array) Where Twinkle should show rollback links (diff, others, mine, contribs) */ if( typeof( TwinkleConfig.showRollbackLinks ) == 'undefined' ) { TwinkleConfig.showRollbackLinks = [ 'diff', 'others' ]; }

// a list of usernames, usually only bots, that vandalism revert is jumped over, that is // if vandalism revert was chosen on such username, then its target in on the revision before. // This is for handling quick bots that makes edits seconds after the original edit is made. // This only affect vandalism rollback, for good faith rollback, it will stop, indicating a bot // has no faith, and for normal rollback, it will rollback that edit. var WHITELIST = [ 'HagermanBot', 'SineBot', 'HBC AIV helperbot', 'HBC AIV helperbot2', 'HBC AIV helperbot3', ]

var twinklefluff = { auto: function { if( QueryString.get( 'oldid' ) != mw.config.get('wgCurRevisionId') ) { // not latest revision return; }

var ntitle = getElementsByClassName( document.getElementById('bodyContent'), 'td', 'diff-ntitle' )[0]; if( ntitle.getElementsByTagName('a')[0].firstChild.value.indexOf( 'Current revision' ) != 0 ) { // not latest revision return; }

vandal = ntitle.getElementsByTagName('a')[3].firstChild.value;

if( !TwinkleConfig.openTalkPageOnAutoRevert ) { TwinkleConfig.openTalkPage = []; }

return twinklefluff.revert( QueryString.get( 'twinklerevert' ), vandal ); },	normal: function {

var spanTag = function( color, content ) { var span = document.createElement( 'span' ); span.style.color = color; span.appendChild( document.createTextNode( content ) ); return span; }

if( mw.config.get('wgNamespaceNumber') == -1 && mw.config.get('wgCanonicalSpecialPageName') == "Contributions" ) { //Get the username these contributions are for username = document.evaluate( 'substring-after(//div[@id="contentSub"]//a[@title="Special:Log"][last]/@href, "user=")', document, null, XPathResult.STRING_TYPE, null).stringValue; if( TwinkleConfig.showRollbackLinks.indexOf('contribs') != -1 || ( mw.config.get('wgUserName') != username && TwinkleConfig.showRollbackLinks.indexOf('others') != -1 ) || ( mw.config.get('wgUserName') == username && TwinkleConfig.showRollbackLinks.indexOf('mine') != -1 ) ) { var list = document.evaluate( '//div[@id="bodyContent"]//ul/li[contains(span[@class="mw-uctop"], "(top)")]', document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ); var vandal = document.evaluate( '//div[@id="contentSub"]/a[1]/@title', document, null, XPathResult.STRING_TYPE, null ).stringValue.replace(/^User( talk)?:/, '').replace("'", "\\'");

var revNode = document.createElement('strong'); var revLink = document.createElement('a'); revLink.appendChild( spanTag( 'Black', ' [' ) ); revLink.appendChild( spanTag( 'SteelBlue', 'rollback' ) ); revLink.appendChild( spanTag( 'Black', ']' ) ); revNode.appendChild(revLink);

var revVandNode = document.createElement('strong'); var revVandLink = document.createElement('a'); revVandLink.appendChild( spanTag( 'Black', ' [' ) ); revVandLink.appendChild( spanTag( 'Red', 'vandalism' ) ); revVandLink.appendChild( spanTag( 'Black', ']' ) ); revVandNode.appendChild(revVandLink);

for(var i = 0; i < list.snapshotLength; ++i ) { var current = list.snapshotItem(i);

var href = document.evaluate( 'a[2]/@href', current, null, XPathResult.STRING_TYPE, null ).stringValue; var tmpNode = revNode.cloneNode( true ); tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'norm' } ) ); current.appendChild( tmpNode ); var tmpNode = revVandNode.cloneNode( true ); tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'vand' } ) ); current.appendChild( tmpNode ); }			}		} else { if( mw.config.get('wgCanonicalSpecialPageName') == "Special:Undelete" ) { //You can't rollback deleted pages! return; }

var body = document.getElementById('bodyContent');

var firstRev = document.evaluate( 'boolean(/div[@class="firstrevisionheader"])', body, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue; if( firstRev ) { // we have first revision here, nothing to do. return; }

try { var otitle1 = document.getElementById('mw-diff-otitle1'); var ntitle1 = document.getElementById('mw-diff-ntitle1'); if (!otitle1 || !ntitle1) return; var otitle = otitle1.parentNode; var ntitle = ntitle1.parentNode; } catch( e ) { // no old, nor new title, nothing to do really, return; return; }

var old_rev_url = document.evaluate( '//strong/a/@href', document.getElementById('mw-diff-ntitle2'), null, XPathResult.STRING_TYPE, null ).stringValue;

// Lets first add a [edit this revision] link var query = new QueryString( old_rev_url.split( '?', 2 )[1] );

var oldrev = query.get( 'oldid' );

var revertToRevision = document.createElement('div'); revertToRevision.setAttribute( 'id', 'tw-revert-to-revision' ); revertToRevision.style.fontWeight = 'bold';

var revertToRevisionLink = revertToRevision.appendChild( document.createElement('a') ); revertToRevisionLink.href = "javascript:twinklefluff.revertToRevision('" + oldrev + "')"; revertToRevisionLink.appendChild( spanTag( 'Black', '[' ) ); revertToRevisionLink.appendChild( spanTag( 'SaddleBrown', 'restore this version' ) ); revertToRevisionLink.appendChild( spanTag( 'Black', ']' ) );

otitle.insertBefore( revertToRevision, otitle.firstChild );

if( document.getElementById('differences-nextlink') ) { // Not latest revision curVersion = false; return; }			if( TwinkleConfig.showRollbackLinks.indexOf('diff') != -1 ) { vandal = document.evaluate( 'a', document.getElementById('mw-diff-ntitle2'), null, XPathResult.STRING_TYPE, null ).stringValue.replace("'", "\\'");

var revertNode = document.createElement('div'); revertNode.setAttribute( 'id', 'tw-revert' );

var agfNode = document.createElement('strong'); var vandNode = document.createElement('strong'); var normNode = document.createElement('strong');

var agfLink = document.createElement('a'); var vandLink = document.createElement('a'); var normLink = document.createElement('a');

agfLink.href = "javascript:twinklefluff.revert('agf', '" + vandal + "')"; vandLink.href = "javascript:twinklefluff.revert('vand', '" + vandal + "')"; normLink.href = "javascript:twinklefluff.revert('norm', '" + vandal + "')";

agfLink.appendChild( spanTag( 'Black', '[' ) ); agfLink.appendChild( spanTag( 'DarkOliveGreen', 'rollback (AGF)' ) ); agfLink.appendChild( spanTag( 'Black', ']' ) );

vandLink.appendChild( spanTag( 'Black', '[' ) ); vandLink.appendChild( spanTag( 'Red', 'rollback (VANDAL)' ) ); vandLink.appendChild( spanTag( 'Black', ']' ) );

normLink.appendChild( spanTag( 'Black', '[' ) ); normLink.appendChild( spanTag( 'SteelBlue', 'rollback' ) ); normLink.appendChild( spanTag( 'Black', ']' ) );

agfNode.appendChild(agfLink); vandNode.appendChild(vandLink); normNode.appendChild(normLink);

revertNode.appendChild( agfNode ); revertNode.appendChild( document.createTextNode(' || ') ); revertNode.appendChild( normNode ); revertNode.appendChild( document.createTextNode(' || ') ); revertNode.appendChild( vandNode );

ntitle.insertBefore( revertNode, ntitle.firstChild ); }		}	} }

twinklefluff.revert = function revertPage( type, vandal, rev, page ) {

mw.config.get('wgPageName') = page || mw.config.get('wgPageName'); mw.config.get('wgCurRevisionId') = rev || wgCurRevisionId;

Status.init( document.getElementById('bodyContent') ); var params = { type: type, user: vandal }	var query = { 'action': 'query', 'prop': 'revisions', 'titles': mw.config.get('wgPageName'), 'rvlimit': 50, // max possible 'rvprop': [ 'ids', 'timestamp', 'user', 'comment' ] }	var wikipedia_api = new Wikipedia.api( 'Grabbing data of earlier revisions', query, twinklefluff.callbacks.main ); wikipedia_api.params = params; wikipedia_api.post; }

twinklefluff.revertToRevision = function revertToRevision( oldrev ) {

Status.init( document.getElementById('bodyContent') );

var query = { 'action': 'query', 'prop': 'revisions', 'titles': mw.config.get('wgPageName'), 'rvlimit': 1, 'rvstartid': oldrev, 'rvprop': [ 'ids', 'timestamp', 'user', 'comment', 'content' ], 'format': 'xml' }

var wikipedia_api = new Wikipedia.api( 'Grabbing data of the earlier revision', query, twinklefluff.callbacks.toRevision.main ); wikipedia_api.params = { rev: oldrev }; wikipedia_api.post; }

twinklefluff.callbacks = { toRevision: { main: function( self ) { var xmlDoc = self.responseXML; self.params.revision = xmlDoc.evaluate('//rev', xmlDoc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; var query = { 'title': mw.config.get('wgPageName'), 'action': 'submit' };			var wikipedia_wiki = new Wikipedia.wiki( 'Reverting page', query, twinklefluff.callbacks.toRevision.reverting ); wikipedia_wiki.params = self.params; wikipedia_wiki.get;

},		reverting: function( self ) { var form = self.responseXML.getElementById( 'editform' ); var text = self.params.revision.textContent;

if( !form ) { self.statelem.error( 'couldn\'t grab element "editform", aborting, this could indicate failed response from the server' ); return; }

var optional_summary = prompt( "Please, if possible, specify a reason for the revert" ); if (optional_summary == null) {				self.statelem.error( 'Aborted by user.' ); return; }			var summary = sprintf( "Reverted to revision %d by %2$s%s.%s", 				self.params.revision.getAttribute( 'revid' ),				self.params.revision.getAttribute( 'user' ),				optional_summary ? "; " + optional_summary : '',				TwinkleConfig.summaryAd			); var postData = { 'wpMinoredit': TwinkleConfig.markRevertedPagesAsMinor.indexOf( 'torev' ) != -1 ? '' : undefined, 'wpWatchthis': TwinkleConfig.watchRevertedPages.indexOf( 'torev' ) != -1 ? '' : form.wpWatchthis.checked ? '' : undefined, 'wpStarttime': form.wpStarttime.value, 'wpEdittime': form.wpEdittime.value, 'wpAutoSummary': form.wpAutoSummary.value, 'wpEditToken': form.wpEditToken.value, 'wpSection': '', 'wpSummary': summary, 'wpTextbox1': text };			Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName'); Wikipedia.actionCompleted.notice = "Reversion completed"

self.post( postData ); }	},	main: function( self ) {

var xmlDoc = self.responseXML; var revs = xmlDoc.evaluate( '//rev', xmlDoc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );

if( revs.snapshotLength < 1 ) { self.statitem.error( 'We have less than one additional revision, thus impossible to revert' ); return; }		var top = revs.snapshotItem(0); if( top.getAttribute( 'revid' ) < mw.config.get('wgCurRevisionId') ) { Status.error( 'Error', [ 'The received top revision id ', htmlNode( 'strong', top.getAttribute('revid') ), ' is less than our current revision id, this could indicate that the current revision has been deleted, the server is lagging, or that bad data has been received. Will stop proceeding at this point.' ] ); return; }		var index = 1; if( mw.config.get('wgCurRevisionId') != top.getAttribute('revid') ) { Status.warn( 'Warning', [ 'Latest revision ', htmlNode( 'strong', top.getAttribute('revid') ), ' doesn\'t equal our revision ', htmlNode( 'strong', wgCurRevisionId) ] ); if( top.getAttribute( 'user' ) == self.params.user ) { switch( self.params.type ) { case 'vand': Status.info( 'Info', [ 'Latest revision was made by ', htmlNode( 'strong', self.params.user ), ', as we assume vandalism, we continue to revert' ]); break; case 'agf': Status.warn( 'Warning', [ 'Latest revision was made by ', htmlNode( 'strong', self.params.user ), ', as we assume good faith, we stop reverting, as the problem might have been fixed.' ]); return; default: Status.warn( 'Notice', [ 'Latest revision was made by ', htmlNode( 'strong', self.params.user ), ', but we will stop reverting anyway.' ] ); return; }			}			else if( 				self.params.type == 'vand' && 				WHITELIST.indexOf( top.getAttribute( 'user' ) ) != -1 && revs.snapshotLength > 1 &&				revs.snapshotItem(1).getAttribute( 'pageId' ) == mw.config.get('wgCurRevisionId') 			) { Status.info( 'Info', [ 'Latest revision was made by ', htmlNode( 'strong', top.getAttribute( 'user' ) ), ', a trusted bot, and the revision before was made by our vandal, so we proceed with the revert.' ] ); index = 2; } else { Status.error( 'Error', [ 'Latest revision was made by ', htmlNode( 'strong', top.getAttribute( 'user' ) ), ', so it might have already been reverted, stopping reverting.'] ); return; }

}

if( WHITELIST.indexOf( self.params.user ) != -1 ) { switch( self.params.type ) { case 'vand': Status.info( 'Info', [ 'Vandalism revert was chosen on ', htmlNode( 'strong', self.params.user ), ', as this is a whitelisted bot, we assume you wanted to revert vandalism made by the previous user instead.' ] ); index = 2; vandal = revs.snapshotItem(1).getAttribute( 'user' ); self.params.user = revs.snapshotItem(1).getAttribute( 'user' ); break; case 'agf': Status.warn( 'Notice', [ 'Good faith revert was chosen on ', htmlNode( 'strong', self.params.user ), ', as this is a whitelisted bot, it makes no sense at all to revert it as a good faith edit, will stop reverting.' ] ); return;

break; case 'norm': default: var cont = confirm( 'Normal revert was chosen, but the top user (' + self.params.user + ') is a whitelisted bot, do you want to revert the revision before instead?' ); if( cont ) { Status.info( 'Info', [ 'Normal revert was chosen on ', htmlNode( 'strong', self.params.user ), ', as this is a whitelisted bot, and per confirm, we\'ll revert the previous revision instead.' ] ); index = 2; self.params.user = revs.snapshotItem(1).getAttribute( 'user' ); } else { Status.warn( 'Notice', [ 'Normal revert was chosen on ', htmlNode( 'strong', self.params.user ), ', this is a whitelisted bot, but per confirmation, revert on top revision will proceed.' ] ); }				break; }		}		var found = false; var count = 0;

for( var i = index; i < revs.snapshotLength; ++i ) { ++count; if( revs.snapshotItem(i).getAttribute( 'user' ) != self.params.user ) { found = i;				break; }		}

if( ! found ) { self.statelem.error( [ 'No previous revision found, perhaps ', htmlNode( 'strong', self.params.user ), ' is the only contributor, or that the user has made more than ' + TwinkleConfig.revertMaxRevisions + ' edits in a row.' ] ); return;

}

if( count == 0 ) { Status.error( 'Error', "We were to revert zero revisions. As that makes no sense, we'll stop reverting this time. It could be that the edit already have been reverted, but the revision id was still the same." ); return; }

var good_revision = revs.snapshotItem( found );

if( 			self.params.type != 'vand' && 			count > 1 && 			!confirm( self.params.user + ' has done ' + count + ' edits in a row. Are you sure you want to revert them all?' ) 		) { Status.info( 'Notice', 'Stopping reverting per user input' ); return; }

self.params.count = count;

self.params.goodid = good_revision.getAttribute( 'revid' ); self.params.gooduser = good_revision.getAttribute( 'user' );

self.statelem.status( [ ' revision ', htmlNode( 'strong', good_revision.getAttribute( 'revid' ) ), ' that was made ', htmlNode( 'strong', count ), ' revisions ago by ', htmlNode( 'strong', good_revision.getAttribute( 'user' ) ) ] );

var query = { 'action': 'query', 'prop': 'revisions', 'titles': mw.config.get('wgPageName'), 'rvlimit': 1, 'rvprop': 'content', 'rvstartid': good_revision.getAttribute( 'revid' ) }

var wikipedia_api = new Wikipedia.api( [ 'Getting content for revision ', htmlNode( 'strong', good_revision.getAttribute( 'revid' ) ) ], query, twinklefluff.callbacks.grabbing ); wikipedia_api.params = self.params; wikipedia_api.post; },	grabbing: function( self ) {

xmlDoc = self.responseXML;

self.params.content = xmlDoc.evaluate( '//rev[1]', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue;

var query = { 'title': mw.config.get('wgPageName'), 'action': 'submit' };		var wikipedia_wiki = new Wikipedia.wiki( 'Reverting page', query, twinklefluff.callbacks.reverting ); wikipedia_wiki.params = self.params; wikipedia_wiki.get; },	reverting: function( self ) { var doc = self.responseXML;

var form = doc.getElementById( 'editform' ); if( !form ) { self.statelem.error( 'couldn\'t grab element "editform", aborting, this could indicate failed response from the server' ); return; }

var text = self.params.content; if( !text ) { self.statelem.error( 'we received no revision, something is wrong, bailing out!' ); return; }

var summary;

switch( self.params.type ) { case 'agf': var extra_summary = prompt( "An optional comment for the edit summary:" ); if (extra_summary == null) {				self.statelem.error( 'Aborted by user.' ); return; }			summary = sprintf( "Reverted good faith edits by %1$s%s.%s", 				self.params.user.replace("\\'", "'"), 				extra_summary ? "; " + extra_summary.toUpperCaseFirstChar : '',				TwinkleConfig.summaryAd			); break; case 'vand': summary = sprintf( "Reverted %d %s by %3$s identified as vandalism to last revision by %4$s.%s", 				self.params.count, 				self.params.count > 1 ? 'edits': 'edit',				self.params.user.replace("\\'", "'"),				self.params.gooduser.replace("\\'", "'"),				TwinkleConfig.summaryAd			); break; case 'norm': if( TwinkleConfig.offerReasonOnNormalRevert ) { var extra_summary = prompt( "An optional comment for the edit summary:" ); if (extra_summary == null) {					self.statelem.error( 'Aborted by user.' ); return; }			}			summary = sprintf( "Reverted %d %s by %3$s%s.%s", 				self.params.count, 				self.params.count > 1 ? 'edits': 'edit',				self.params.user.replace("\\'", "'"),				extra_summary ? "; " + extra_summary.toUpperCaseFirstChar : '',				TwinkleConfig.summaryAd 			); }

if( TwinkleConfig.openTalkPage.indexOf( self.params.type ) != -1 ) { Status.info( 'Info', [ 'Opening user talk page edit form for user ', htmlNode( 'strong', self.params.user ) ] ); var query = { 'title': 'User talk:' + self.params.user, 'action': 'edit', 'preview': 'yes', 'vanarticle': mw.config.get('wgPageName').replace(/_/g, ' '), 'vanarticlerevid': wgCurRevisionId, 'vanarticlegoodrevid': self.params.goodid, 'type': self.params.type, 'count': self.params.count }

switch( TwinkleConfig.userTalkPageMode ) { case 'tab': window.open( mw.config.get('wgServer') + wgScriptPath + '/index.php?' + QueryString.create( query ), '_tab' ); break; case 'blank': window.open( mw.config.get('wgServer') + wgScriptPath + '/index.php?' + QueryString.create( query ), '_blank', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' ); break; case 'window': default: window.open( mw.config.get('wgServer') + wgScriptPath + '/index.php?' + QueryString.create( query ), 'twinklewarnwindow', 'location=no,toolbar=no,status=no,directories=no,scrollbars=yes,width=1200,height=800' ); break; }		}

var postData = { 'wpMinoredit': TwinkleConfig.markRevertedPagesAsMinor.indexOf( self.params.type ) != -1 ? '' : undefined, 'wpWatchthis': TwinkleConfig.watchRevertedPages.indexOf( self.params.type ) != -1 ? '' : form.wpWatchthis.checked ? '' : undefined, 'wpStarttime': form.wpStarttime.value, 'wpEdittime': form.wpEdittime.value, 'wpAutoSummary': form.wpAutoSummary.value, 'wpEditToken': form.wpEditToken.value, 'wpSection': '', 'wpSummary': summary, 'wpTextbox1': text };

Wikipedia.actionCompleted.redirect = mw.config.get('wgPageName'); Wikipedia.actionCompleted.notice = "Reversion completed"

self.post( postData ); } }

$( function {		if( QueryString.exists( 'twinklerevert' ) ) {			twinklefluff.auto;		} else {			twinklefluff.normal;		}	} );

} //twinkleConfigExists

license: Creative Commons - Attribution Share-Alike - https://creativecommons.org/licenses/by-sa/4.0/

title: MediaWiki:Gadget-twinklefluff.js


text:
MediaWiki:Gadget-twinkledelimages.js

// If TwinkleConfig aint exist. if( typeof( TwinkleConfig ) == 'undefined' ) { TwinkleConfig = {}; }

/** TwinkleConfig.summaryAd (string) If ad should be added or not to summary, default TW if( typeof( TwinkleConfig.summaryAd ) == 'undefined' ) { TwinkleConfig.summaryAd = " using TW"; }

/** TwinkleConfig.deletionSummaryAd (string) If ad should be added or not to deletion summary, default TW if( typeof( TwinkleConfig.deletionSummaryAd ) == 'undefined' ) { TwinkleConfig.deletionSummaryAd = " using TW"; }

/** TwinkleConfig.deliChunks (integer) How many files should be processed at a time if( typeof( TwinkleConfig.deliChunks ) == 'undefined' ) { TwinkleConfig.deliChunks = 500; }

/** TwinkleConfig.deliMax (integer) How many files should be processed maximum if( typeof( TwinkleConfig.deliMax ) == 'undefined' ) { TwinkleConfig.deliMax = 5000; } function twinkledeli { if( mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgCurRevisionId') == false ) { return; }	if( userIsInGroup( 'sysop' ) ) {

mw.util.addPortletLink( (typeof pTwinkle != "undefined") ? pTwinkle : "p-cactions", "javascript:twinkledeli.callback", "Deli-batch", "tw-deli", "Delete file found on page", ""); } } $(twinkledeli);

twinkledeli.unlinkCache = {}; twinkledeli.callback = function twinklesdeliCallback { var Window = new SimpleWindow( 800, 400 ); Window.setTitle( "Batch file deletion" );

var form = new QuickForm( twinkledeli.callback.evaluate ); form.append( {			type: 'checkbox',			list: [				{ 					label: 'Delete files',					name: 'delete_image',					value: 'delete',					checked: true				},				{					label: 'Unlink uses of this file',					name: 'unlink_image',					value: 'unlink',					checked: true				}			]		} ); form.append( {			type: 'textarea',			name: 'reason',			label: 'Reason: '		} ); if( mw.config.get('wgNamespaceNumber') == Namespace.CATEGORY ) { var query = { 'action': 'query', 'generator': 'categorymembers', 'gcmtitle': mw.config.get('wgPageName'), 'gcmnamespace': Namespace.IMAGE, 'gcmlimit' : TwinkleConfig.deliMax, 'prop': [ 'imageinfo', 'categories', 'revisions' ], 'grvlimit': 1, 'grvprop': [ 'user' ] };	} else { var query = { 'action': 'query', 'generator': 'images', 'titles': mw.config.get('wgPageName'), 'prop': [ 'imageinfo', 'categories', 'revisions' ], 'grvlimit': 1, 'grvprop': [ 'user' ] };	}	var wikipedia_api = new Wikipedia.api( 'Grabbing files', query, function( self ) {			var xmlDoc = self.responseXML;			var snapshot = xmlDoc.evaluate('//page[@imagerepository="local"]', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );			var list = [];			for ( var i = 0; i < snapshot.snapshotLength; ++i ) {				var object = snapshot.snapshotItem(i);				var image = xmlDoc.evaluate( '@title', object, null, XPathResult.STRING_TYPE, null ).stringValue;				var user = xmlDoc.evaluate( 'imageinfo/ii/@user', object, null, XPathResult.STRING_TYPE, null ).stringValue;				var last_edit = xmlDoc.evaluate( 'revisions/rev/@user', object, null, XPathResult.STRING_TYPE, null ).stringValue;

var disputed = xmlDoc.evaluate( 'boolean(categories/cl[@title="Category:Contested candidates for speedy deletion"])', object, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue || user == last_edit; list.push( {label:(i+1) + ": " + image + ' (' + user + ')[' + last_edit + ']' + ( disputed ? ' DISPUTED' : '' ), value:image, checked:!disputed }); }			self.params.form.append( {					type: 'checkbox',					name: 'images',					list: list				}			) self.params.form.append( { type:'submit' } );

var result = self.params.form.render; self.params.Window.setContent( result );

} );

wikipedia_api.params = { form:form, Window:Window }; wikipedia_api.post; var root = document.createElement( 'div' ); Status.init( root ); Window.setContent( root ); Window.display; }

twinkledeli.currentDeleteCounter = 0; twinkledeli.currentUnlinkCounter = 0; twinkledeli.currentdeletor; twinkledeli.callback.evaluate = function twinkledeliCallbackEvaluate(event) { mw.config.get('wgPageName') = mw.config.get('wgPageName').replace( /_/g, ' ' ); // for queen/king/whatever and country! var images = event.target.getChecked( 'images' ); var reason = event.target.reason.value; var delete_image = event.target.delete_image.checked; var unlink_image = event.target.unlink_image.checked; if( ! reason ) { return; }	Status.init( event.target ); function toCall( work ) { if( work.length == 0 && twinkledeli.currentDeleteCounter <= 0 && twinkledeli.currentUnlinkCounter <= 0 ) { Status.info( 'work done' ); window.clearInterval( twinkledeli.currentdeletor ); Wikipedia.removeCheckpoint; return; } else if( twinkledeli.currentDeleteCounter <= 0 && twinkledeli.currentUnlinkCounter <= 0 ) { twinkledeli.unlinkCache = []; // Clear the cache var images = work.shift; twinkledeli.currentDeleteCounter = images.length; // can be less than the number of elements in deliChunks for( var i = 0; i < images.length; ++i ) { var image = images[i]; var query = { 'action': 'query', 'titles': image }				var wikipedia_api = new Wikipedia.api( 'Checking if file ' + image + ' exists', query, twinkledeli.callbacks.main ); wikipedia_api.params = { image:image, reason:reason, unlink_image:unlink_image, delete_image:delete_image }; wikipedia_api.post; }		}	}	var work = images.chunk( TwinkleConfig.deliChunks ); Wikipedia.addCheckpoint; twinkledeli.currentdeletor = window.setInterval( toCall, 1000, work ); } twinkledeli.callbacks = { main: function( self ) { var xmlDoc = self.responseXML; var normal = xmlDoc.evaluate( '//normalized/n/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue; if( normal ) { self.params.image = normal; }		var exists = xmlDoc.evaluate( 'boolean(//pages/page[@title="' + self.params.image.replace( /"/g, '\\"') + '" and not(@missing)])', xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue;

if( ! exists ) { self.statelem.error( "It seems that the page doesn't exists, perhaps it has already been deleted" ); return; }		if( self.params.unlink_image ) { var query = { 'action': 'query', 'list': 'imageusage', 'titles': self.params.image, 'iulimit': userIsInGroup( 'sysop' ) ? 5000 : 500 // 500 is max for normal users, 5000 for bots and sysops };			var wikipedia_api = new Wikipedia.api( 'Grabbing file links', query, twinkledeli.callbacks.unlinkImageInstancesMain ); wikipedia_api.params = self.params; wikipedia_api.post; }		if( self.params.delete_image ) { var query = { 'title': self.params.image, 'action': 'delete' };			var wikipedia_wiki = new Wikipedia.wiki( 'Deleting file ' + self.params.image, query, twinkledeli.callbacks.deleteImage, function( self ) { 					--twinkledeli.currentDeleteCounter;					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_wiki.params = self.params;			wikipedia_wiki.followRedirect = false;			wikipedia_wiki.get;				}	},	deleteImage: function( self ) {		var form = this.responseXML.getElementById( 'deleteconfirm' );		if( ! form ) { // Hell, file deletion is b0rked :( form = this.responseXML.getElementsByTagName( 'form' )[0]; var postData = { 'wpDeleteReasonList': 'other', 'wpReason': "Deleted because \"" + self.params.reason + "\"." + TwinkleConfig.deletionSummaryAd, 'wpEditToken': form.wpEditToken.value }			self.post( postData ); } else {

var postData = { 'wpWatch': form.wpWatch.checked ? '' : undefined, 'wpDeleteReasonList': 'other', 'wpReason': "Deleted because \"" + self.params.reason + "\"." + TwinkleConfig.deletionSummaryAd, 'wpEditToken': form.wpEditToken.value }			self.post( postData ); }	},	unlinkImageInstancesMain: function( self ) { var xmlDoc = self.responseXML; var snapshot = xmlDoc.evaluate('//imageusage/iu/@title', xmlDoc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null );

if( snapshot.snapshotLength == 0 ) { --twinklebatchdelete.currentUnlinkCounter; return; }

var statusIndicator = new Status('Unlinking file instances ', '0%');

var total = snapshot.snapshotLength * 2;

var onsuccess = function( self ) { var obj = self.params.obj; var total = self.params.total; var now = parseInt( 100 * ++(self.params.current)/total ) + '%'; obj.update( now ); self.statelem.unlink; if( self.params.current >= total ) { obj.info( now + ' (completed)' ); --twinklebatchdelete.currentUnlinkCounter; Wikipedia.removeCheckpoint;

}		}		var onloaded = onsuccess;

var onloading = function( self ) {}

Wikipedia.addCheckpoint; if( snapshot.snapshotLength == 0 ) { statusIndicator.info( '100% (completed)' ); --twinklebatchdelete.currentUnlinkCounter; Wikipedia.removeCheckpoint; return; }		self.params.total = total; self.params.obj = statusIndicator; self.params.current =  0;

for ( var i = 0; i < snapshot.snapshotLength; ++i ) { var title = snapshot.snapshotItem(i).value; var query = { 'title': title, 'action': 'submit' }			var wikipedia_wiki = new Wikipedia.wiki( "Unlinking on " + title, query, twinkledeli.callbacks.unlinkImageInstances ); var params = clone( self.params ); params.title = title;

wikipedia_wiki.params = params; wikipedia_wiki.onloading = onloading; wikipedia_wiki.onloaded = onloaded; wikipedia_wiki.onsuccess = onsuccess; wikipedia_wiki.get; }	},	unlinkImageInstances: function( self ) { var image = self.params.image.replace( /^(?:Image|File):/, '' ); var form = self.responseXML.getElementById('editform'); var text;

if( self.params.title in twinkledeli.unlinkCache ) { text = twinkledeli.unlinkCache[ self.params.title ]; } else { text = form.wpTextbox1.value; }		var old_text = text; var wikiPage = new Mediawiki.Page( text ); wikiPage.commentOutImage( image, 'Commented out because image was deleted' );

text = wikiPage.getText; twinkledeli.unlinkCache[ self.params.title ] = text; if( text == old_text ) { // Nothing to do, return self.onsuccess( self ); Wikipedia.actionCompleted( self ); return; }		var postData = { 'wpMinoredit': form.wpMinoredit.checked ? '' : undefined, 'wpWatchthis': undefined, 'wpStarttime': form.wpStarttime.value, 'wpEdittime': form.wpEdittime.value, 'wpAutoSummary': form.wpAutoSummary.value, 'wpEditToken': form.wpEditToken.value, 'wpSection': '', 'wpSummary': 'Removing instance of file ' + image + " that has been deleted because \"" + self.params.reason + "\")" + "; " + TwinkleConfig.deletionSummaryAd,			'wpTextbox1': text		};		self.post( postData );	} }

title: MediaWiki:Gadget-twinkledelimages.js


text:
MediaWiki:Gadget-Twinklerevert

Revert using Twinkle: This allows all users to revert using the Twinkle script. This script does not work in Internet Explorer. (Configuration goes in your user JavaScript)

title: MediaWiki:Gadget-Twinklerevert


text:
MediaWiki:Gadget-twinkledelimages

Autodelete and delink images using Twinkle: This allows you to batch delete and unlink images using Twinkle. The script will not work in Internet Explorer. (Configuration goes in your user JavaScript)

title: MediaWiki:Gadget-twinkledelimages


text:
MediaWiki:Gadget-section-admin-gadgets

For users with extra rights

title: MediaWiki:Gadget-section-admin-gadgets


text:
User talk:204.38.89.211

Blocked
Play elsewhere please - thanks -- Herby talk thyme 18:34, 4 February 2008 (UTC)

title: User talk:204.38.89.211


text:
User talk:88.73.22.10

Welcome!
Welcome to Wikibooks, a collection of open-content textbooks! You don't have to log in to read or edit articles on Wikibooks, but creating an account is quick, free and non-intrusive, requires no personal information, and gives you many benefits, including:
 * The use of a username of your choice
 * The ability to view all your contributions via a "My contributions" link
 * Your own user page
 * Your own talk page which, if you choose, also allows users to send you messages without knowing your e-mail address
 * The use of your own personal watchlist to which you can add articles that interest you
 * The ability to rename pages.
 * The ability to upload images
 * The ability to customize the appearance and behavior of the website
 * The eligibility to become an administrator
 * The right to be heard in votes and elections
 * Your IP address will no longer be visible to other users.

We hope that you choose to become a Wikibookian and create an account. Feel free to ask me any questions you may have on my talk page. By the way, make sure to sign your posts and comments with four tildes (~~~~), which will let others know who left it.

– Mike.lifeguard  | talk 19:38, 18 December 2007 (UTC)

title: User talk:88.73.22.10


text: User:Judithhh

title: User:Judithhh


text: User talk:72.133.33.169

title: User talk:72.133.33.169


text:
User talk:Judithhh

Welcome!
Come introduce yourself at the new users page. If you have any questions, you can ask there or contact me personally.--Whiteknight (Page) (Talk) 19:59, 18 December 2007 (UTC)

title: User talk:Judithhh


text:
User talk:156.56.195.171

– Mike.lifeguard  | talk 20:16, 18 December 2007 (UTC)

title: User talk:156.56.195.171


text:
User talk:Gorila88~enwikibooks

Welcome!
Come introduce yourself at the new users page. If you have any questions, you can ask there or contact me personally.--Whiteknight (Page) (Talk) 21:20, 18 December 2007 (UTC)

Your account will be renamed
Hello,

The developer team at Wikimedia is making some changes to how accounts work, as part of our on-going efforts to provide new and better tools for our users like cross-wiki notifications. These changes will mean you have the same account name everywhere. This will let us give you new features that will help you edit and discuss better, and allow more flexible user permissions for tools. One of the side-effects of this is that user accounts will now have to be unique across all 900 Wikimedia wikis. See the announcement for more information.

Unfortunately, your account clashes with another account also called Gorila88. To make sure that both of you can use all Wikimedia projects in future, we have reserved the name Gorila88~enwikibooks that only you will have. If you like it, you don't have to do anything. If you do not like it, you can pick out a different name.

Your account will still work as before, and you will be credited for all your edits made so far, but you will have to use the new account name when you log in.

Sorry for the inconvenience.

Yours, Keegan Peterzell Community Liaison, Wikimedia Foundation 23:15, 17 March 2015 (UTC)

Renamed
 This account has been renamed as part of single-user login finalisation. If you own this account you can |log in using your previous username and password for more information. If you do not like this account's new name, you can choose your own using this form after logging in: . -- Keegan (WMF) (talk) 05:00, 19 April 2015 (UTC)

title: User talk:Gorila88~enwikibooks


text:
User talk:Mschofie~enwikibooks

Welcome!
Come introduce yourself at the new users page. If you have any questions, you can ask there or contact me personally.--Whiteknight (Page) (Talk) 21:50, 18 December 2007 (UTC)

Your account will be renamed
Hello,

The developer team at Wikimedia is making some changes to how accounts work, as part of our on-going efforts to provide new and better tools for our users like cross-wiki notifications. These changes will mean you have the same account name everywhere. This will let us give you new features that will help you edit and discuss better, and allow more flexible user permissions for tools. One of the side-effects of this is that user accounts will now have to be unique across all 900 Wikimedia wikis. See the announcement for more information.

Unfortunately, your account clashes with another account also called Mschofie. To make sure that both of you can use all Wikimedia projects in future, we have reserved the name Mschofie~enwikibooks that only you will have. If you like it, you don't have to do anything. If you do not like it, you can pick out a different name.

Your account will still work as before, and you will be credited for all your edits made so far, but you will have to use the new account name when you log in.

Sorry for the inconvenience.

Yours, Keegan Peterzell Community Liaison, Wikimedia Foundation 23:29, 17 March 2015 (UTC)

Renamed
 This account has been renamed as part of single-user login finalisation. If you own this account you can |log in using your previous username and password for more information. If you do not like this account's new name, you can choose your own using this form after logging in: . -- Keegan (WMF) (talk) 05:16, 19 April 2015 (UTC)

title: User talk:Mschofie~enwikibooks


text: User:Fasten/todo

title: User:Fasten/todo


text:
MediaWiki:Gadget-section-tab-gadgets

Tab gadgets

title: MediaWiki:Gadget-section-tab-gadgets


text:
MediaWiki:Gadget-section-other-gadgets

Other Gadgets

title: MediaWiki:Gadget-section-other-gadgets


text:
MediaWiki:Gadget-wknightcore.js

//

title: MediaWiki:Gadget-wknightcore.js


text:
MediaWiki:Gadget-wkprintversion.js

//Print Version Creator //Maintained by User:Whiteknight /*  This script operates a semi-automagical print version creator gadget. This gadget is  located at User:Whiteknight/Print Version Gadget. It uses Whiteknight's Javascript core library to help with some of the common tasks and the AJAX-related tasks. Documentation and the gadget itself is located at  User:Whiteknight/Print Version Gadget.

var pv = { require:  2.64, formspan: "WKPVFormSpan", textspan: "WKPVSpan", statspan: "WKPVStatSpan", boxspan: "WKPVFormSpanSub", form: null, text: "",      //raw converted text htmltext: ""  //html-formatted converted text }

pv.getForm = function { return pv._form; }

pv.createForm = function { var form = wk.spanText(pv.formspan, ""); if(form == null) return; pv._editB = wk.makeButton("WKPVEditB",   "Edit",    pv.edit); pv._displayB = wk.makeButton("WKPVDisplayB", "Display", pv.display); pv._booknameB = wk.makeInput("WKPVBookNameB", ""); pv._listB = wk.makeElement("Textarea", {name:"WKPVListB", rows:"15", cols:"70"}); pv._spanB = wk.makeElement("span", {id:pv.boxspan}, pv._listB); pv._form = wk.makeElement("form", {name:"WKPVFormB", method:"GET"}, [     pv._booknameB,      wk.makeButton("WKPVLoadB",    "Load",    pv.loadPage),      " ",      wk.makeButton("WKPVShowB",    "Show",    function{wk.toggleDisplay(pv.boxspan, "block")}),      wk.makeButton("WKPVHideB",    "Hide",    function{wk.toggleDisplay(pv.boxspan, "none")}),      " ",      wk.makeButton("WKPVClearB",   "Clear",   pv.clear),      " ",      wk.makeButton("WKPVConvertB", "Convert", pv.convert),      " ",      pv._editB,      pv._displayB,      pv._spanB,      wk.makeElement("br")  ]); wk.toggleDisplay(pv._spanB, "none"); wk.toggleDisable(pv._editB, true); wk.toggleDisable(pv._displayB, true); form.appendChild(pv._form); } if(typeof wk == 'object' && wk.testVersion(pv.require)) { $(pv.createForm); }

pv.clear = function { wk.spanText(pv.textspan, ""); wk.spanText(pv.statspan, ""); wk.toggleDisplay(pv.boxspan, "none"); pv._listB.value = ""; wk.toggleDisable(pv._editB, true); wk.toggleDisable(pv._displayB, true); }

pv.bookName = ""; pv.getBookName = function { pv.bookName = pv._booknameB.value; return pv.bookName; }

pv.loadPage = function { pv.getBookName; if(pv.bookName == "") { wk.spanText(pv.statspan, "Must enter a valid page name to load from"); return; } wk.loadWikiText(pv.bookName, function(text){pv._listB.value = text;}); wk.spanText(pv.statspan, "Successfully retrieved text from " +   wk.wikiLinkText(pv.bookName) +     ". Click Show to see and edit the page text. Click Convert " +     "to create the print version."  ); }

pv.setChapter = function (page, offset, xml) { page = page.replace("", ""); page = page.replace("", ""); var levels = page.split("/"); var subpage = levels[levels.length - 1]; if(subpage == "") {   subpage = levels[levels.length - 2]; } return ("{"+"{Print chapter heading|" + subpage + "}}\n\n"); }

pv.setSections = function (head, offset, xml) { head = head.replace(/=/g, ""); head = head.replace(/^\s*/g, ""); head = head.replace(/\s*$/g, ""); return("{"+"{Print unit page|" + head + "}}\n\n"); }

pv.xmlFormat = function(xml) { xml = xml.replace(/''+/g, ""); //kill text formatting xml = xml.replace(/\{\{[^\}]+\}\}/g, ""); //kill templates xml = xml.replace(/\[\[category:[^\]]+\]\]/ig, ""); //kill categories xml = xml.replace(/\|[^\]\}]+\]\]/g, "]]"); //kill piped links xml = xml.replace(/\[\[\/([^\]]+)\/\]\]/g, "" + pv.bookName + "/$1"); //properly address relative links xml = xml.replace(/\[\[([^\]]+)\]\]/g, pv.setChapter); //prepare chapter entries xml = xml.replace(/===([^=]+)===/g, pv.setSections); xml = xml.replace(/\* ?/g, ""); //kill bullets xml = xml.replace(/# ?/g, ""); //kill ordered lists xml = xml.replace(/==[^=]+==/g, ""); //kill headings xml = xml.replace(/\n\n\n+/g, "\n\n"); //kill extra whitespace xml = xml.replace(//g, ">"); var header = "\n" + " \n" + "
\n"; if(pv.bookName != "") { header = header + "\n" + "
\n"; } xml = header + xml + "\n= GNU Free Documentation License =\n\n\n\n"; return xml; }

pv.convert = function { wk.toggleDisable(pv._editB, false); wk.toggleDisable(pv._displayB, false); wk.toggleDisplay(pv.boxspan, "none"); pv.htmltext = pv.xmlFormat(pv._listB.value); pv.text = pv.htmltext.replace(/>/g, ">"); pv.text = pv.text.replace(/</g, "<"); wk.spanText(pv.statspan, "Successfully converted text. " +   "Click Display to show the current text for copy+paste. " +    "Click Edit to edit and save the current text."); wk.toggleDisable(pv._editB, false); wk.toggleDisable(pv._displayB, false); wk.spanText(pv.textspan, ""); }

pv.display = function { wk.toggleDisplay(pv.boxspan, "none"); wk.spanText(pv.statspan, "Print version text: "); if(pv.getBookName != "") { wk.appendSpanText(pv.statspan, "Copy the text below into " +     "" + pv.bookName + "/Print Version " +      " Warning : " +       "this text may need to be edited manually to remove unwanted " +       "templates, categories, links or odd formatting that the tool " +      "cannot account for. "); } wk.spanText(pv.textspan, "" + pv.htmltext + " "); }

pv.edit = function { if(pv.getBookName == "") { wk.spanText(pv.statspan, "Must enter a valid page name to edit"); return; }   wk.spanText(pv.statspan, "Loading..."); wk.toggleDisplay(pv.boxspan, "none"); wk.loadEditPage(pv.bookName + "/Print Version", pv._edit2); }

pv._edit2 = function(xml) { var edit = wk.showEditWindow(xml, pv.textspan); edit.summary.value = pv.bookName + " Printable Version: Created by Whiteknight's Print Version Creator"; edit.text.value = pv.text; wk.spanText(pv.statspan,    "Editing text for " +     wk.wikiLinkText(pv.bookName + "/Print Version") +     ". Edit the text below to remove any errors or artifacts " +    "of the conversion process. Once you have made your edits, " +    "click Save Page."); }

title: MediaWiki:Gadget-wkprintversion.js


text:
MediaWiki:Gadget-wkPrintVersion

Print Version Creator: Semi-automagical print version creator gadget. Create print versions for your favorite books at User:Whiteknight/Print Version Gadget.

title: MediaWiki:Gadget-wkPrintVersion


text:
User talk:69.125.243.137

Join Us!
--Whiteknight (Page) (Talk) 22:28, 18 December 2007 (UTC)

title: User talk:69.125.243.137