# மீடியாவிக்கி:Morebits.js

// Source:en.wikipedia.org/wiki/User:AzaToth/morebits.js  
// but as it is at the moment the only required file for all modules  
// of twinkle, it's here.  
// Shold perhaps be moved into a "twinklebase.js" file.  
var twinkleConfigExists = false;  

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

Cookies = {  
/*  
- Creates an cookie with the name and value pair. expiry is optional or null and defaults
- to browser standard (in seconds), path is optional and defaults to "/"
- throws error if the cookie already exists.
- /
create: function( name, value, max_age, path ) {  
if( Cookies.exists( name ) ) {  
throw "cookie " + name + " already exists";  
}  
Cookies.set( name, value, max_age, path );  
},  
/*  
- Sets an cookie with the name and value pair, overwrites any previous cookie of that name.
- expiry is optional or null and defaults to browser standard (in seconds),
- path is optional and defaults to /
- /
set: function( name, value, max_age, path ) {  
var cookie = name + "=" + encodeURIComponent( value );  
if( max_age ) {  
cookie += "; max-age=" + max_age;  
}  
cookie += "; path=" + path || "/";  
document.cookie = cookie;  
},  
/*  
- Retuns the cookie with the name "name", return null if no cookie found.
- /
read: function( name ) {  
var cookies = document.cookie.split(";");  
for( var i = 0; i < cookies.length; ++i ) {  
var current = cookies[i];  
current = current.trim;  
if( current.indexOf( name + "=" ) == 0 ) {  
return decodeURIComponent( current.substring( name.length + 1 ) );  
}  
}  
return null;  
},  
/*  
- Returns true if a cookie exists, false otherwise
- /
exists: function( name ) {  
var re = new RegExp( ";\\s*" + name + "=" );  
return re.test( document.cookie );  
},  
/*  
- Deletes the cookie named "name"
- /
remove: function( name ) {  
Cookies.set( name, '', -1 );  
}  
}  

/**  
- Quickform is a class for creation of simple and standard forms without much
- specific coding.
- /

QuickForm = function QuickForm( event, eventType ) {  

this.root = new QuickForm.element( { type: 'form', event: event, eventType:eventType } );  

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("form.quickform { width: 96%; margin:auto; padding: .5em; vertical-align: middle}", 0);  
styles.insertRule("form.quickform * { font-family: sans-serif; vertical-align: middle}", 0);  
styles.insertRule("form.quickform select { width: 30em; : solid gray; font-size: 1.1em}", 0);  
styles.insertRule("form.quickform h5 { -top: solid gray;}", 0);  
styles.insertRule("form.quickform textarea { width: 100%; height: 6em }", 0);  
styles.insertRule("form.quickform .tooltipButtonContainer { position: relative; width: 100%; }", 0);  
styles.insertRule("form.quickform .tooltipButton { padding: .2em; color: blue; font-weight: bold; cursor:help;}", 0);  
styles.insertRule(".quickformtooltip { z-index: 200; position: absolute; padding: .1em; : dotted red; background-color: Linen; font: caption; font-size: 10pt; max-width: }", 0);  
}  

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

QuickForm.element = function QuickFormElement( data ) {  
this.data = data;  
this.childs = [];  
this.id = QuickForm.element.id++;  
}  

QuickForm.element.id = 0;  

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

QuickForm.element.prototype.render = function QuickFormElementRender {  
var currentNode = this.compute( this.data );  

for( var i = 0; i < this.childs.length; ++i ) {  
currentNode[1].appendChild( this.childs[i].render );  
}  
return currentNode[0];  
}  

QuickForm.element.prototype.compute = function QuickFormElementCompute( data ) {  
var node;  
var childContainder = null;  
var label;  
var id = 'node_' + this.id;  
if( data.adminonly && !userIsInGroup( 'sysop' ) ) {  
// hell hack alpha  
data.type = hidden;  
}  
switch( data.type ) {  
case 'form':  
node = document.createElement( 'form' );  
node.setAttribute( 'name', 'id' );  
node.className = "quickform";  
node.setAttribute( 'action', 'javascript:void(0);');  
if( data.event ) {  
node.addEventListener( data.eventType || 'submit', data.event , false );  
}  
break;  
case 'select':  
node = document.createElement( 'div' );  

node.setAttribute( 'id', 'div_' + id );  
if( data.label ) {  
label = node.appendChild( document.createElement( 'label' ) );  
label.setAttribute( 'for', id );  
label.appendChild( document.createTextNode( data.label ) );  
}  
var select = node.appendChild( document.createElement( 'select' ) );  
if( data.event ) {  
select.addEventListener( 'change', data.event, false );  
}  
if( data.multiple ) {  
select.setAttribute( 'multiple', 'multiple' );  
}  
if( data.size ) {  
select.setAttribute( 'size', data.size );  
}  
select.setAttribute( 'name', data.name );  

if( data.list ) {  
for( var i = 0; i < data.list.length; ++i ) {  

var current = data.list[i];  

if( current.list ) {  
current.type = 'optgroup';  
} else {  
current.type = 'option';  
}  

var res = this.compute( current );  
select.appendChild( res[0] );  
}  
}  
childContainder = select;  
break;  
case 'option':  
node = document.createElement( 'option' );  
node.setAttribute( 'value', data.value );  
if( data.selected ) {  
node.setAttribute( 'selected', 'selected' );  
}  
if( data.disabled ) {  
node.setAttribute( 'disabled', 'disabled' );  
}  
node.setAttribute( 'label', data.label );  
node.appendChild( document.createTextNode( data.label ) );  
break;  
case 'optgroup':  
node = document.createElement( 'optgroup' );  
node.setAttribute( '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  

var res = this.compute( current );  
node.appendChild( res[0] );  
}  
}  
break;  
case 'field':  
node = document.createElement( 'fieldset' );  
label = node.appendChild( document.createElement( 'legend' ) );  
label.appendChild( document.createTextNode( data.label ) );  
if( data.name ) {  
node.setAttribute( 'name', data.name );  
}  
break;  
case 'checkbox':  
case 'radio':  
node = document.createElement( 'div' );  
if( data.list ) {  
for( var i = 0; i < data.list.length; ++i ) {  
var cur_id = id + '_' + i;  
var current = data.list[i];  
cur_node = node.appendChild( document.createElement( 'div' ) );  
var input = cur_node.appendChild( document.createElement( 'input' ) );  
input.setAttribute( 'value', current.value );  
input.setAttribute( 'name', current.name || data.name );  
input.setAttribute( 'type', data.type );  
input.setAttribute( 'id', cur_id );  
if( current.checked ) {  
input.setAttribute( 'checked', 'checked' );  
}  
if( current.disabled ) {  
input.setAttribute( 'disabled', 'disabled' );  
}  
if( data.event ) {  
input.addEventListener( 'change', data.event, false );  
} else if ( current.event ) {  
input.addEventListener( 'change', current.event, true );  
}  
var label = cur_node.appendChild( document.createElement( 'label' ) );  
label.appendChild( document.createTextNode( current.label ) );  
label.setAttribute( 'for', cur_id );  
if( current.tooltip ) {  
QuickForm.element.generateTooltip( label, current );  
}  
}  
}  
break;  
case 'input':  
node = document.createElement( 'div' );  

if( data.label ) {  
label = node.appendChild( document.createElement( 'label' ) );  
label.appendChild( document.createTextNode( data.label ) );  
label.setAttribute( 'for', id );  
}  

var input = node.appendChild( document.createElement( 'input' ) );  
if( data.value ) {  
input.setAttribute( 'value', data.value );  
}  
input.setAttribute( 'name', data.name );  
input.setAttribute( 'type', 'text' );  
if( data.size ) {  
input.setAttribute( 'size', data.size );  
}  
if( data.disabled ) {  
input.setAttribute( 'disabled', 'disabled' );  
}  
if( data.readonly ) {  
input.setAttribute( 'readonly', 'readonly' );  
}  
if( data.maxlength ) {  
input.setAttribute( 'maxlength', data.maxlength );  
}  
if( data.event ) {  
input.addEventListener( 'keyup', data.event, false );  
}  
break;  
case 'hidden':  
var node = document.createElement( 'input' );  
node.setAttribute( 'type', 'hidden' );  
node.setAttribute( 'value', data.value );  
node.setAttribute( 'name', data.name );  
break;  
case 'header':  
node = document.createElement( 'h5' );  
node.appendChild( document.createTextNode( data.label ) );  
break;  
case 'div':  
node = document.createElement( 'div' );  
break;  
case 'submit':  
node = document.createElement( 'span' );  
childContainder = node.appendChild(document.createElement( 'input' ));  
childContainder.setAttribute( 'type', 'submit' );  
if( data.label ) {  
childContainder.setAttribute( 'value', data.label );  
}  
childContainder.setAttribute( 'name', data.name || 'submit' );  
if( data.disabled ) {  
childContainder.setAttribute( 'disabled', 'disabled' );  
}  
break;  
case 'button':  
node = document.createElement( 'span' );  
childContainder = node.appendChild(document.createElement( 'input' ));  
childContainder.setAttribute( 'type', 'button' );  
if( data.label ) {  
childContainder.setAttribute( 'value', data.label );  
}  
childContainder.setAttribute( 'name', data.name );  
if( data.disabled ) {  
childContainder.setAttribute( 'disabled', 'disabled' );  
}  
if( data.event ) {  
childContainder.addEventListener( 'click', data.event, false );  
}  
break;  
case 'textarea':  
node = document.createElement( 'div' );  
if( data.label ) {  
label = node.appendChild( document.createElement( 'label' ) );  
label.appendChild( document.createTextNode( data.label ) );  
label.setAttribute( 'for', id );  
}  
node.appendChild( document.createElement( 'br' ) );  
textarea = node.appendChild( document.createElement( 'textarea' ) );  
textarea.setAttribute( 'name', data.name );  
if( data.cols ) {  
textarea.setAttribute( 'cols', data.cols );  
}  
if( data.rows ) {  
textarea.setAttribute( 'rows', data.rows );  
}  
if( data.disabled ) {  
textarea.setAttribute( 'disabled', 'disabled' );  
}  
if( data.readonly ) {  
textarea.setAttribute( 'readonly', 'readonly' );  
}  
break;  

}  

if( childContainder == null ) {  
childContainder = node;  
}  
if( data.tooltip ) {  
QuickForm.element.generateTooltip( label || node , data );  
}  

if( data.extra ) {  
childContainder.extra = extra;  
}  
childContainder.setAttribute( 'id', data.id || id );  

return [ node, childContainder ];  
}  

QuickForm.element.generateTooltip = function QuickFormElementGenerateTooltip( node, data ) {  
var tooltipButtonContainer = node.appendChild( document.createElement( 'span' ) );  
tooltipButtonContainer.className = 'tooltipButtonContainer';  
var tooltipButton = tooltipButtonContainer.appendChild( document.createElement( 'span' ) );  
tooltipButton.className = 'tooltipButton';  
tooltipButton.appendChild( document.createTextNode( '?' ) );  
var tooltip = document.createElement( 'div' );  
tooltip.className = 'quickformtooltip';  
tooltip.appendChild( document.createTextNode( data.tooltip ) );  
tooltipButton.tooltip = tooltip;  
tooltipButton.showing = false;  
tooltipButton.interval = null;  
tooltipButton.addEventListener( 'mouseover', QuickForm.element.generateTooltip.display, false );  
tooltipButton.addEventListener( 'mouseout', QuickForm.element.generateTooltip.fade, false );  

}  
QuickForm.element.generateTooltip.display = function QuickFormElementGenerateTooltipDisplay(e) {  
window.clearInterval( e.target.interval );  
e.target.tooltip.style.setProperty( '-moz-opacity', 1, null);  
e.target.tooltip.style.setProperty( 'opacity', 1, null);  
e.target.tooltip.style. = (e.pageX - e.layerX + 24) + "px";  
e.target.tooltip.style.top = (e.pageY - e.layerY + 12) + "px";  
document.body.appendChild( e.target.tooltip );  
e.target.showing = true;  
}  

QuickForm.element.generateTooltip.fade = function QuickFormElementGenerateTooltipFade( e ) {  
e.target.opacity = 1.2;  
e.target.interval = window.setInterval(function(e){  
e.target.tooltip.style.setProperty( '-moz-opacity', e.target.opacity, null);  
e.target.tooltip.style.setProperty( 'opacity', e.target.opacity, null);  
e.target.opacity -= 0.1;  
if( e.target.opacity <= 0 ) {  
window.clearInterval( e.target.interval );  
document.body.removeChild( e.target.tooltip );e.target.showing = false;  
}  
},50,e);  
}  

/*  
- 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).
- 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];  
var return_array = [];  
for( var i = 0; i < elements.length; ++i ) {  
if( elements[i].checked ) {  
if( type != null && elements[i].type != type ) {  
continue;  
}  
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];  
var return_array = [];  
for( var i = 0; i < elements.length; ++i ) {  
if( elements[i].type == type && elements[i].value != '' ) {  
return_array.push( elements[i].value );  
}  
}  
return return_array;  
}  
/**  
- Will escape a string to be used in a RegExp
- /
RegExp.escape = function( text, space_fix ) {  

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;  
}  

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;  
}  

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;  
}  

namespaces = {  
'-2': 'Media',  
'-1': 'Special',  
'0' : '',  
'1' : 'Talk',  
'2' : 'User',  
'3' : 'User_talk',  
'4' : 'Project',  
'5' : 'Project talk',  
'6' : 'Image',  
'7' : 'Image talk',  
'8' : 'MediaWiki',  
'9' : 'MediaWiki talk',  
'10': 'Template',  
'11': 'Template talk',  
'12': 'Help',  
'13': 'Help talk',  
'14': 'Category',  
'15': 'Category talk',  
'100': 'Portal',  
'101': 'Portal talk'  
};  
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 + "\}\}";  
}  
Namespace = {  
MAIN: 0,  
TALK: 1,  
USER: 2,  
USER_TALK: 3,  
PROJECT: 4,  
PROJECT_TALK: 5,  
IMAGE: 6,  
IMAGE_TALK: 7,  
MEDIAWIKI: 8,  
MEDIAWIKI_TALK: 9,  
TEMPLATE: 10,  
TEMPLATE_TALK: 11,  
HELP: 12,  
HELP_TALK: 13,  
CATEGORY: 14,  
CATEGORY_TALK: 15,  
PORTAL: 100,  
PORTAL_TALK: 101,  
MEDIA: -2,  
SPECIAL: -1  
};  

// 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  
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 );  
}  
}  
wpActionCompletedTimeOut = typeof(wpActionCompletedTimeOut) == 'undefined' ? 5000 : wpActionCompletedTimeOut;  
wpMaxLag = typeof(wpMaxLag) == 'undefined' ? 10 : wpMaxLag; // 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 (required)  
- /
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 {  
var self = this.obj;  
self.statelem.error( "Error " + this.target.status + " occurred while quering the api." );  
}  
xmlhttp.onload = function {  
this.obj.responseXML = this.responseXML;  
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?' + 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 " + e.target.status + " occurred while posting the document." );  
}  
xmlhttp.onload = function(e) {  
var self = this.obj;  
var status = e.target.status;  
if( status != 200 ) {  
if( status == 503 ) {  
var retry = e.target.getResponseHeader( 'Retry-After' );  
var lag = e.target.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.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?' + 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;  
}  

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 == '\{\{' ) {  
current += '\{\{';  
++i;  
++level;  
continue;  
}  
if( test2 == '\}\}' ) {  
current += '\}\}';  
++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:\\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:\\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:\\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:\\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; } );  
}  

/**  
- 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');
- /
function QueryString(qString) {  
this.string = qString;  
this.params = {};  

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;  
for( var i in arr ) {  
if( typeof arr[i] == 'undefined' ) {  
continue;  
}  
if( arr[i] instanceof Array ){  
var v = Array;  
for(var j = 0; j < arr[i].length; ++j ) {  
v[j] = encodeURIComponent( arr[i][j] );  
}  
resarr.push( encodeURIComponent( i ) + '=' + v.join('|') );  
} else {  
resarr.push( encodeURIComponent( i ) + '=' + encodeURIComponent( arr[i] ) );  
}  
}  

return resarr.join('&');  
}  
QueryString.prototype.create = QueryString.create;  

/**  
- Simple exception handling
- /

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 stylesheet = document.createElement('style');  
stylesheet.type = 'text/css';  
stylesheet.rel = 'stylesheet';  
stylesheet.appendChild( document.createTextNode("") ); // Safari bugfix  
document.getElementsByTagName("head")[0].appendChild(stylesheet);  
var styles = stylesheet.sheet ? stylesheet.sheet : stylesheet.styleSheet;  
styles.insertRule(  
".simplewindow { "+  
"position: fixed; "+  
"background-color: AliceBlue; "+  
": ridge Black; "+  
"z-index: 100; "+  
"}",  
0  
);  

styles.insertRule(  
".simplewindow .content { "+  
"position: absolute; "+  
"top: ; "+  
"bottom: 0; "+  
"overflow: auto; "+  
"width: 100%; "+  
"}",  
0  
);  

styles.insertRule(  
".simplewindow .resizebuttonhorizontal { "+  
"position: absolute; "+  
"background-color: MediumPurple; "+  
"opacity: 0.5; "+  
": -; "+  
"bottom: -; "+  
"width: ; "+  
"height: ; "+  
"cursor: se-resize; "+  
"}",  
0  
);  
styles.insertRule(  
".simplewindow .resizebuttonvertical { "+  
"position: absolute; "+  
"opacity: 0.5; "+  
"background-color: MediumPurple; "+  
": -; "+  
"bottom: -; "+  
"width: ; "+  
"height: ; "+  
"cursor: se-resize; "+  
"}",  
0  
);  

styles.insertRule(  
".simplewindow .closebutton {"+  
"position: absolute; "+  
"font: 100 0.8em sans-serif; "+  
"top: ; "+  
": ; "+  
"height: 100%; "+  
"cursor: pointer; "+  
"}",  
0  
);  

styles.insertRule(  
".simplewindow .topbar { "+  
"position: absolute; "+  
"background-color: LightSteelBlue; "+  
"font: 900 1em sans-serif; "+  
"vertical-align: baseline; "+  
"text-align: ; "+  
"width: 100%; "+  
"height: ; "+  
"cursor: move; "+  
"}",  
0  
);  

this.width = width;  
this.height = height;  

var = document.createElement( 'div' );  
var content = document.createElement( 'div' );  
var topbar = document.createElement( 'div' );  
var title = document.createElement( 'span' );  
var closeButton = document.createElement( 'span' );  
var resizeButton2 = document.createElement( 'div' );  
var resizeButton1 = document.createElement( 'div' );  

this. = ;  
this.title = title;  
this.content = content;  

.className = 'simplewindow';  
content.className = 'content';  
topbar.className = 'topbar';  
resizeButton1.className = 'resizebuttonvertical';  
resizeButton2.className = 'resizebuttonhorizontal';  
closeButton.className = 'closebutton';  
title.className = 'title';  

topbar.appendChild( closeButton );  
topbar.appendChild( title );  
.appendChild( topbar );  
.appendChild( content );  
.appendChild( resizeButton1 );  
.appendChild( resizeButton2 );  

.style.width = width + 'px';  
.style.height = height + 'px';  
.style.top = parseInt( window.innerHeight - this.height )/2 + 'px' ;  
.style. = parseInt( window.innerWidth - this.width )/2 + 'px';  
var img = document.createElement( 'img' );  
img.src = "//upload.wikimedia.org/wikipedia/commons//5/52/Nuvola_apps_error.png/-Nuvola_apps_error.png";  
closeButton.appendChild( img );  

var self = this;  

// Specific events  
.addEventListener( 'mousedown', function(event) { self.focus(event); }, false );  
closeButton.addEventListener( 'click', function(event) {self.close(event); }, false );  
topbar.addEventListener( 'mousedown', function(event) {self.initMove(event); }, false );  
resizeButton1.addEventListener( 'mousedown', function(event) {self.initResize(event); }, false );  
resizeButton2.addEventListener( 'mousedown', function(event) {self.initResize(event); }, false );  

// Generic events  
window.addEventListener( 'mouseover', function(event) {self.handleEvent(event); }, false );  
window.addEventListener( 'mousemove', function(event) {self.handleEvent(event); }, false );  
window.addEventListener( 'mouseup', function(event) {self.handleEvent(event); }, false );  
this.currentState = this.initialState;  
}  

SimpleWindow.prototype = {  
focusLayer: 100,  
width: 800,  
height: 600,  
initialState: "Inactive",  
currentState: null, // current state of finite state machine (one of 'actionTransitionFunctions' properties)  
focus: function(event) {  
this..style.zIndex = ++this.focusLayer;  
},  
close: function(event) {  
event.preventDefault;  
document.body.removeChild( this. );  
},  
initMove: function(event) {  
event.preventDefault;  
this.initialX = parseInt( event.clientX - this..offsetLeft );  
this.initialY = parseInt( event.clientY - this..offsetTop );  
this..style.opacity = '0.5';  
this.currentState = 'Move';  
},  
initResize: function(event) {  
event.preventDefault;  
this..style.opacity = '0.5';  
this.currentState = 'Resize';  
},  
handleEvent: function(event) {  
event.preventDefault;  
var actionTransitionFunction = this.actionTransitionFunctions[this.currentState][event.type];  
if( !actionTransitionFunction ) {  
actionTransitionFunction = this.unexpectedEvent;  
}  
var nextState = actionTransitionFunction.call(this, event);  
if( !nextState ){  
nextState = this.currentState;  
}  
if( !this.actionTransitionFunctions[nextState] ){  
nextState = this.undefinedState(event, nextState);  
}  
this.currentState = nextState;  
event.stopPropagation;  
},  
unexpectedEvent: function(event) {  
throw ("Handled unexpected event '" + event.type + "' in state '" + this.currentState);  
return this.initialState;  
},  

undefinedState: function(event, state) {  
throw ("Transitioned to undefined state '" + state + "' from state '" + this.currentState + "' due to event '" + event.type);  
return this.initialState;  
},  
actionTransitionFunctions: {  
Inactive: {  
mouseover: function(event) {  
return this.currentState;  
},  
mousemove: function(event) {  
return this.currentState;  
},  
mouseup: function(event) {  
return this.currentState;  
}  
},  
Move: {  
mouseover: function(event) {  
this.moveWindow( event.clientX, event.clientY );  
return this.currentState;  
},  
mousemove: function(event) {  
return this.doActionTransition("Move", "mouseover", event);  
},  
mouseup: function(event) {  
this..style.opacity = '1';  
return 'Inactive';  
}  
},  
Resize: {  
mouseover: function(event) {  
this.resizeWindow( event.clientX, event.clientY );  
return this.currentState;  
},  
mousemove: function(event) {  
return this.doActionTransition("Resize", "mouseover", event);  
},  
mouseup: function(event) {  
this..style.opacity = '1';  
return 'Inactive';  
}  
}  
},  
doActionTransition: function(anotherState, anotherEventType, event) {  
return this.actionTransitionFunctions[anotherState][anotherEventType].call(this,event);  
},  
display: function {  
document.body.appendChild( this. );  
},  
setTitle: function( title ) {  
this.title.textContent = title;  
},  
setWidth: function( width ) {  
this..style.width = width;  
},  
setHeight: function( height ) {  
this..style.height = height;  
},  
setContent: function( content ) {  
this.purgeContent;  
this.addContent( content );  
},  
addContent: function( content ) {  
this.content.appendChild( content );  
},  
purgeContent: function( content ) {  
while( this.content.hasChildNodes ) {  
this.content.removeChild( this.content.firstChild );  
}  
},  
moveWindow: function( x, y ) {  
this..style. = x - this.initialX + 'px';  
this..style.top = y - this.initialY + 'px';  
},  
resizeWindow: function( x, y ) {  
this..style.height = Math.max( parseInt( y - this..offsetTop ), 200 ) + 'px';  
this..style.width = Math.max( parseInt( x - this..offsetLeft ), 200 ) + 'px';  
}  
}