User:David Condrey/editing/scripts.js

/* */ /* Links */ var LinkClassifier={ /* This object maps classes to the categories for which to apply them. Values may be an array of strings or a regex. */   cats:{ deletion:[ 'Category:All articles proposed for deletion', 'Category:All books proposed for deletion', 'Category:All disputed non-free Wikipedia files', 'Category:All orphaned non-free use Wikipedia files', 'Category:All possibly unfree Wikipedia files', 'Category:All replaceable non-free use Wikipedia files', 'Category:All Wikipedia files with no copyright tag', 'Category:All Wikipedia files with no non-free use rationale', 'Category:All Wikipedia files with unknown copyright status', 'Category:All Wikipedia files with unknown source', 'Category:Articles for deletion', 'Category:Articles on deletion review', 'Category:Candidates for speedy deletion', 'Category:Candidates for undeletion', 'Category:Categories for conversion', 'Category:Categories for deletion', 'Category:Categories for listifying', 'Category:Categories for merging', 'Category:Categories for renaming', 'Category:Categories for speedy renaming', 'Category:Categories to be listified then deleted', 'Category:Duplicate or hardcoded templates awaiting deletion', 'Category:Items pending OTRS confirmation of permission for over 30 days', 'Category:Miscellaneous pages for deletion', 'Category:Redirects for discussion', 'Category:Stub categories for deletion', 'Category:Stub template deletion candidates', 'Category:Templates for deletion', 'Category:Wikipedia deprecated and orphaned templates', 'Category:Wikipedia files for deletion', 'Category:Wikipedia files with unknown source for deletion in dispute', 'Category:Wikipedia templates for deletion' ].sort, disambiguation:[ 'Category:All disambiguation pages' ].sort, 'set-index':[ 'Category:All set index articles' ].sort, 'featured-content':[ 'Category:Featured articles', 'Category:Featured lists', 'Category:Featured pictures', 'Category:Featured sounds', 'Category:Featured videos', 'Category:Featured portals' ].sort, 'good-content':[ 'Category:Good articles' ].sort, 'soft-redirect-cats':[ 'Category:Wikipedia soft redirected categories', ].sort, 'spoken-articles':[ 'Category:Spoken articles' ].sort, stubcls:/^Category:.* stubs$/, 'nonfree-media':[ 'Category:All non-free media' ].sort, unprintworthy:[ 'Category:Unprintworthy redirects', 'Category:Middle-earth redirects from redundant titles' ].sort, 'unprintworthy-shortcut':[ 'Category:Redirects from shortcuts', ].sort },

/* This object maps page props to CSS classes for which to apply them. Values may be an array of strings or a function returning such. */   props: { disambiguation:[ 'disambiguation' ],   },

/* This regex matches page titles to be marked as intentional links to disambiguation pages */ intentionaldab: / \(disambiguation\)$/, callback:function(r, sts, xhr){ if(!r.query) { if(typeof(window.console)=='undefined' || typeof(window.console.error)!='function') throw new Error('Bad response'); window.console.error("Bad response", r); return; }       if(r['query-continue']){ var cc=this.rawdata; for(var k in r['query-continue']){ for(var k2 in r['query-continue'][k]){ cc[k2]=r['query-continue'][k][k2]; }           }            $.ajax({                url:mw.util.wikiScript('api'),                dataType:'json',                type:'POST',                data:cc,                rawdata:cc,                success:arguments.callee,                error:function(xhr,textStatus,errorThrown){                    throw new Error('AJAX error: '+textStatus+' '+errorThrown);                }            }); }       r=r.query;

var a=document.getElementById('wikiPreview'); if(!a) a=document.getElementById('bodyContent'); if(!a) throw new Error('Huh? No body content?'); a=a.getElementsByTagName('A'); if(a.length==0) return;

var redir={}; var redirlist=[]; if(r.redirects) for(var i=r.redirects.length-1; i>=0; i--){ redir[r.redirects[i].from]=r.redirects[i].to; redirlist.push(r.redirects[i].from); }       if(redirlist.length>0) { var q = { format:'json', action:'query', titles:redirlist.join('|'), prop:'categories|info', inprop:'protection', cllimit:'max' };           $.ajax({                url:mw.util.wikiScript('api'),                dataType:'json',                type:'POST',                data:q,                rawdata:q,                success:arguments.callee,                error:function(xhr,textStatus,errorThrown){                    throw new Error('AJAX error: '+textStatus+' '+errorThrown);                }            }); }

var prefix=(this.rawdata.redirects?'':'redir-'); var cats={}; var missing={}; var classes={}; if(r.pages) for(var i in r.pages){ classes[r.pages[i].title] = []; missing[r.pages[i].title]=(typeof(r.pages[i].missing)!='undefined'); if(typeof(r.pages[i].categories)!='undefined'){ cats[r.pages[i].title]=r.pages[i].categories.map(function(a){ return a.title; }).sort; }           if(typeof(r.pages[i].pageprops)!='undefined'){ for ( var k in r.pages[i].pageprops ) { if ( !LinkClassifier.props[k] ) { continue; }                       var v = LinkClassifier.props[k]; if ( $.isFunction( v ) ) { v = v( r.pages[i].pageprops[k], k, r.pages[i].title ); }                       classes[r.pages[i].title].push.apply( classes[r.pages[i].title], v ); }           }            if(typeof(r.pages[i].protection)!='undefined'){ var x={}; for(var j=r.pages[i].protection.length-1; j>=0; j--){ var p=prefix+'protection-'+r.pages[i].protection[j].type+'-'+r.pages[i].protection[j].level; if(typeof(x[p])=='undefined'){ x[p]=1; classes[r.pages[i].title].push(p); }                   if(r.pages[i].protection[j].expiry=='infinity'){ p+='-indef'; if(typeof(x[p])=='undefined'){ x[p]=1; classes[r.pages[i].title].push(p); }                   }                }            }            if(typeof(r.pages[i].flagged)!='undefined'){ if(r.pages[i].lastrevid!=r.pages[i].flagged.stable_revid){ classes[r.pages[i].title].push('needs-review'); }           }        }        Array.prototype.forEach.call(a, function(a){            if(typeof(a.wikipage)=='undefined') return;            if(typeof(redir[a.wikipage])!='undefined'){                $(a).addClass('redirect');                a.wikipage=redir[a.wikipage];                a.title=a.wikipage;                var cns=mw.config.get('wgCanonicalNamespace');                if(a.wikipage==(cns?cns+':':)+mw.config.get('wgTitle'))                    $(a).addClass('self-redirect');                if(missing[a.wikipage])                    $(a).addClass('broken-redirect');            }            var m=a.href.match(/#.*/);            if(m && m[0].substr(0,10)!=="#cite_note"){                a.title=a.title.replace(/#.*/,)+m[0].replace(/_/g,' ').replace(/\.([0-9A-F][0-9A-F])/gi, function(x,n){ return String.fromCharCode(parseInt(n,16)); });            }            if(LinkClassifier.intentionaldab.test(a.origwikipage)){ $(a).addClass('intentional-disambiguation'); }           if(typeof(classes[a.wikipage])!='undefined'){ for(var j=classes[a.wikipage].length-1; j>=0; j--) $(a).addClass(classes[a.wikipage][j]); }           if(a.wikipage!=a.origwikipage && typeof(classes[a.origwikipage])!='undefined'){ for(var j=classes[a.origwikipage].length-1; j>=0; j--) $(a).addClass(classes[a.origwikipage][j]); }           var c1=[]; if(typeof(cats[a.wikipage])!='undefined'){ c1=c1.concat(cats[a.wikipage]); }           if(a.wikipage!=a.origwikipage && typeof(cats[a.origwikipage])!='undefined'){ c1=c1.concat(cats[a.origwikipage]); }           if(c1.length>0){ c1=c1.sort; for(var cls in LinkClassifier.cats){ var i1=c1.length-1; var c2=LinkClassifier.cats[cls]; if(c2 instanceof RegExp){ while(i1>=0){ if(c2.test(c1[i1])){ $(a).addClass(cls); break; }                           i1--; }                   } else { var i2=c2.length-1; while(i1>=0 && i2>=0){ if(c1[i1]==c2[i2]){ $(a).addClass(cls); break; }                           (c1[i1]>c2[i2])?--i1:--i2; }                   }                }            }        });    },

getPageName:function(url){ var m=url.match(/\/wiki\/([^?#]+)/); if(!m) m=url.match(/\/w\/index.php\?(?:.*&)?title=([^&#]+)/); if(!m) return ''; var t=decodeURIComponent(m[1]).replace(/_/g,' '); if(t.substr(0,6)=='Image:') t='File:'+t.substr(6); if(t.substr(0,11)=='Image talk:') t='File talk:'+t.substr(6); if(t.substr(0,8)=='Special:') t=''; return t;   },

classifyChildren:function(node){ mw.loader.using(['mediawiki.util','mediawiki.user'], function{           var a=node.getElementsByTagName('A');            if(a.length==0) return;            var self=LinkClassifier.getPageName(location.href);            a=Array.prototype.map.call(a, function(a){ a.wikipage=''; if(/(^|\s)(external|extiw)(\s|$)/.test(a.className)) return ''; if(!/(^|\s)(image)(\s|$)/.test(a.className)) a.className+=" nonimage"; a.wikipage=LinkClassifier.getPageName(a.href); if(a.wikipage==self) a.wikipage=''; a.origwikipage=a.wikipage; return a.wikipage; }).sort.filter(function(e,i,a){ return e!=='' && (i==0 || a[i-1]!==e); });

function processLinks(limit){ var props = []; for ( var k in LinkClassifier.props ) { props.push( k ); }               while(a.length>0){ var q={ format:'json', action:'query', rawcontinue:'', titles:a.splice(0,limit).join('|'), prop:'categories|pageprops|info|flagged', redirects:1, cllimit:'max', inprop:'protection' };                   if ( props.length <= limit ) { q.ppprop = props.join( '|' ); }                   $.ajax({                        url:mw.util.wikiScript('api'),                        dataType:'json',                        type:'POST',                        data:q,                        rawdata:q,                        success:LinkClassifier.callback,                        error:function(xhr,textStatus,errorThrown){                            throw new Error('AJAX error: '+textStatus+' '+errorThrown);                        }                    }); }           }

if(a.length<=100){ // Not worth querying the API to see if the user has apihighlimits processLinks(50); } else { // Note mw.user.getRights queries the API mw.user.getRights(function(rights){                   processLinks( (rights.indexOf('apihighlimits')>=0) ? 500 : 50 );               });            }        });    },    onLoad:function{        if(window.LinkClassifierOnDemand) return;        if(window.AJAXPreview) window.AJAXPreview.AddOnLoadHook(LinkClassifier.classifyChildren);        LinkClassifier.onDemand;    },    onDemand:function{        var node=document.getElementById('wikiPreview');        if(!node) node=document.getElementById('bodyContent');        if(node) LinkClassifier.classifyChildren(node);    } };

if(!window.LinkClassifierOnDemand) $(document).ready(LinkClassifier.onLoad);

/* Dup Links */ $( function($) {   if((wgNamespaceNumber != 0) && (wgNamespaceNumber != 2)) {        // only check links in mainspace and userspace (for userspace drafts)        return;    }    var portletlink = mw.util.addPortletLink('p-tb', '#', 'Highlight duplicate links', 'ca-findduplicatelinks');    $(portletlink).click( function(e) { e.preventDefault; // create a separate div surrounding the lead // first get the element immediately surrounding the article text. Unfortunately, MW doesn't seem to provide a non-fragile way for that. var content = ".mw-content-ltr"; $(content).prepend(document.createElement('div')); var lead = $(content).children[0]; $(lead).attr('id', 'lead'); $(content).children.each( function {           if(this.nodeName.toLowerCase == 'h2') {                return false;            }            if($(this).attr('id') != 'lead') {                $(lead).append(this);            }            return true;        }); // detect duplicate links mw.util.addCSS(".duplicate-link { border: 1px solid red; }"); var finddups = function { var href = $(this).attr('href'); if(href != undefined && href.indexOf('#') != 0) { if(seen[href]) { $(this).addClass("duplicate-link"); }               else { seen[href] = true; }           }            return true; };       // array to keep track of whether we've seen a link before var seen = []; mw.util.$content.find('p a').not('#lead *, .infobox *, .navbox *').each(finddups); var seen = []; mw.util.$content.find('#lead p a').not('.infobox *, .navbox *').each(finddups); }); });

/* Edit Buttons */ if(typeof XEBPopups== 'undefined')XEBPopups=true; if(typeof XEBHideDelay== 'undefined')XEBHideDelay=0.5; //Time before the popup disappears after the mouse moves out if(typeof XEBExtendEditSummary == 'undefined')XEBExtendEditSummary=true; // Is the edit summary extended after a popup

function addCustomButton(imageFile, speedTip, tagOpen, tagClose, sampleText){ mwCustomEditButtons.push({ "imageFile": imageFile,  "speedTip": speedTip,  "tagOpen": tagOpen,  "tagClose": tagClose,  "sampleText": sampleText}); }

if (typeof usersignature == 'undefined') var usersignature = '-- \~\~\~\~';

var Isrc='//upload.wikimedia.org/wikipedia/commons/';

var enExtraButtons=mwCustomEditButtons.length;

var BDict={ 'A':['e/e9/Button_headline2.png','Secondary headline','\n===','===','Secondary headline'], 'B':['1/13/Button_enter.png','Line break',' ',,], 'C':['5/5f/Button_center.png','Center',' \n','\n<\/div>','Centred text'], 'D':['e/ea/Button_align_left.png','Left-Align',' \n','\n<\/div>','Left-aligned text'], 'D1':['a/a5/Button_align_right.png','Right-Align',' \n','\n<\/div>','Right-aligned text'], 'E':['0/04/Button_array.png','Table','\n{| class="wikitable" \n|- \n| 1 || 2\n|- \n| 3 || 4','\n|}\n',''], 'F':['1/1e/Button_font_color.png','Insert coloured text','Coloured text<\/span>','ColourName'], 'FS':['1/1b/Button_miss_signature.png','Unsigned post',' — Preceding unsigned comment added by ',' (talk • contribs) date ','user name or IP'], 'G':, 'H':['7/74/Button_comment.png','Comment',"",'Comment'], 'I1':['6/6a/Button_sup_letter.png','Superscript',' ','<\/sup>','Superscript text'], 'I2':['a/aa/Button_sub_letter.png','Subscript',' ','<\/sub>','Subscript text'], 'J1':['5/58/Button_small.png','Small',' ','<\/small>','Small Text'], 'J2':['5/56/Button_big.png','Big text',' ','<\/big>','Big text'], 'K':['b/b4/Button_category03.png','Category',"",'Category name'], 'L':['8/8e/Button_shifting.png','Insert tab(s)',':','',':'], 'M':['f/fd/Button_blockquote.png','Insert block of quoted text','\n','\n<\/blockquote>','Block quote'], 'N':['4/4b/Button_nbsp.png','nonbreaking space',' ',,], 'O':['2/23/Button_code.png','Insert code',' ' +'Size:' +'small' +'[Normal]' +'big' +' <\/td><\/tr>' +'<\/form>' +'Sample:' +' Text "'		+' Insert '		+'Cancel ';

curPopup.setInnerHTML(mt);

return true; }

function XEBUpdateSampleText {	f=document.XEBPopupImageForm; }

function XEBMenuMouseOut(e) {	var targ; if (!e) var e = window.event; if (e.target) targ = e.target; else if (e.srcElement) targ = e.srcElement;

targ.style.color='black'; }

function XEBMenuMouseOver(e) {	var targ; if (!e) var e = window.event; if (e.target) targ = e.target; else if (e.srcElement) targ = e.srcElement;

targ.style.color='red'; }

function XEBgetElementsByClassName(parent,clsName,htmltag){ var arr = new Array; var elems = parent.getElementsByTagName(htmltag); for ( var cls, i = 0; ( elem = elems[i] ); i++ ){ if ( elem.className == clsName ){ arr[arr.length] = elem; }	}	return arr; }

function extendSummary(newText) { if(!XEBExtendEditSummary)return; s=document.editform.elements['wpSummary'].value; s+=(s=='')?newText:' +'+newText; document.editform.elements['wpSummary'].value=s; }

var ct = ct || {};

ct.setSelectionRange = function (ta, start, end) { var _static = arguments.callee; if (ta.setSelectionRange) { if (_static.NEWLINES == null) { _static.NEWLINES = '\n'; // 64 of them should be enough. for (var i = 0; i < 6; i++) { _static.NEWLINES += _static.NEWLINES; }		}		if (_static.helperTextarea == null) { _static.helperTextarea = document.createElement('TEXTAREA'); _static.helperTextarea.style.display = 'none'; document.body.appendChild(_static.helperTextarea); }		var hta = _static.helperTextarea; hta.style.display = ''; hta.style.width = ta.clientWidth + 'px'; hta.style.height = ta.clientHeight + 'px'; hta.value = _static.NEWLINES.substring(0, ta.rows) + ta.value.substring(0, start); var yOffset = hta.scrollHeight; hta.style.display = 'none'; ta.focus; ta.setSelectionRange(start, end); if (yOffset > ta.clientHeight) { yOffset -= Math.floor(ta.clientHeight / 2); ta.scrollTop = yOffset; // Opera does not support setting the scrollTop property if (ta.scrollTop != yOffset) { // todo: Warn the user or apply a workaround }		} else { ta.scrollTop = 0; }	} else { // IE incorrectly counts '\r\n' as a signle character start -= ta.value.substring(0, start).split('\r').length - 1; end -= ta.value.substring(0, end).split('\r').length - 1; var range = ta.createTextRange; range.collapse(true); range.moveStart('character', start); range.moveEnd('character', end - start); range.select; } };

ct.getPosition = function (e) { var x = 0; var y = 0; do { x += e.offsetLeft || 0; y += e.offsetTop || 0; e = e.offsetParent; } while (e); return {x: x, y: y}; };

ct.observe = function (e, eventName, f) { if (e.addEventListener) { e.addEventListener(eventName, f, false); } else { e.attachEvent('on' + eventName, f); } };

ct.stopObserving = function (e, eventName, f) { if (e.removeEventListener) { e.removeEventListener(eventName, f, false); } else { e.detachEvent('on' + eventName, f); } };

ct.stopEvent = function (event) { if (event.preventDefault) { event.preventDefault; event.stopPropagation; } else { event.returnValue = false; event.cancelBubble = true; } };

// ct.anchor is a shortcut to creating a link as a DOM node: ct.anchor = function (text, href, title) { var e = document.createElement('A'); e.href = href; e.appendChild(document.createTextNode(text)); e.title = title || ''; return e; };

ct.hlink = function (toWhat, text) { var wgServer = window.wgServer || 'http://en.wikipedia.org'; var wgArticlePath = window.wgArticlePath || '/wiki/$1'; var url = (wgServer + wgArticlePath).replace('$1', toWhat); return '' + (text || toWhat) + ''; };

ct.makeCached = function (f) { var cache = {}; // a closure; the cache is private for f	return function (x) { return (cache[x] != null) ? cache[x] : (cache[x] = f(x)); }; };

ct.REG_EXP_REPLACEMENTS = { '{letter}': // all Unicode letters // http://www.codeproject.com/dotnet/UnicodeCharCatHelper.asp '\\u0041-\\u005a\\u0061-\\u007a\\u00aa' + '\\u00b5\\u00ba\\u00c0-\\u00d6' + '\\u00d8-\\u00f6\\u00f8-\\u01ba\\u01bc-\\u01bf' + '\\u01c4-\\u02ad\\u0386\\u0388-\\u0481\\u048c-\\u0556' + '\\u0561-\\u0587\\u10a0-\\u10c5\\u1e00-\\u1fbc\\u1fbe' + '\\u1fc2-\\u1fcc\\u1fd0-\\u1fdb\\u1fe0-\\u1fec' + '\\u1ff2-\\u1ffc\\u207f\\u2102\\u2107\\u210a-\\u2113' + '\\u2115\\u2119-\\u211d\\u2124\\u2126\\u2128' + '\\u212a-\\u212d\\u212f-\\u2131\\u2133\\u2134\\u2139' + '\\ufb00-\\ufb17\\uff21-\\uff3a\\uff41-\\uff5a', '{month}': // English only '(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|'			+ 'January|February|March|April|June|July|August|September|'			+ 'October|November|December)', '{year}': '[12][0-9]{3}' };

ct.fixRegExp = function (re) { // : RegExp if (re.__fixedRE != null) { return re.__fixedRE; }	var s = re.source; for (var alias in ct.REG_EXP_REPLACEMENTS) { s = s.replace(				new RegExp(ct.escapeRegExp(alias), 'g'),				ct.REG_EXP_REPLACEMENTS[alias]		); }	re.__fixedRE = new RegExp(s); // the fixed copy is cached re.__fixedRE.global = re.global; re.__fixedRE.ignoreCase = re.ignoreCase; re.__fixedRE.multiline = re.multiline; return re.__fixedRE; };

ct.escapeRegExp = ct.makeCached(function (s) { // : RegExp	var r = '';	for (var i = 0; i < s.length; i++) {		var code = s.charCodeAt(i).toString(16);		r += '\\u' + '0000'.substring(code.length) + code;	}	return r; });

ct.getAllMatches = function (re, s) { // : Match[] var p = 0; var a = []; while (true) { re.lastIndex = 0; var m = re.exec(s.substring(p)); if (m == null) { return a;		} m.start = p + m.index; m.end = p + m.index + m[0].length; a.push(m); p = m.end; } };

ct.DEFAULT_MAX_SUGGESTIONS = 8; ct.maxSuggestions = ct.DEFAULT_MAX_SUGGESTIONS; ct.suggestions; // : Suggestion[] ct.eSuggestions; // : Element; that's where suggestions are rendered ct.eAddToSummary; // : Element; the proposed edit summary appears there ct.eTextarea; // : Element; the one with id="wpTextbox1" ct.appliedSuggestions = {}; // : Map

ct.scannedText = null; // remember what we scan, to check if it is                      // still the same when we try to fix it

ct.BIG_THRESHOLD = 100 * 1024; ct.isBigScanConfirmed = false; // is the warning about a big article confirmed ct.isTalkPageScanConfirmed = false;

ct.scanTimeoutId = null; // a timeout is set after a keystroke and before // a scan, this variable tracks its id

// === int main === // This is the entry point ct.observe(window, 'load', function {	ct.eTextarea = document.getElementById('wpTextbox1');	if (ct.eTextarea == null) {		// This is not an ``?action=edit'' page		return;	}	ct.eSuggestions = document.createElement('DIV');	ct.eSuggestions.style.border = 'dashed #ccc 1px';	ct.eSuggestions.style.color = '#888';	var e = document.getElementById('editform');	while (true) {		var p = e.previousSibling;		if ( (p == null) || ((p.nodeType == 1) && (p.id != 'toolbar')) ) {			break;		}		e = p;	}	e.parentNode.insertBefore(ct.eSuggestions, e);	ct.eAddToSummary = document.createElement('DIV');	ct.eAddToSummary.style.border = 'dashed #ccc 1px';	ct.eAddToSummary.style.color = '#888';	ct.eAddToSummary.style.display = 'none';	var wpSummaryLabel = document.getElementById('wpSummaryLabel');	wpSummaryLabel.parentNode.insertBefore(ct.eAddToSummary, wpSummaryLabel);	ct.scan; // do a scan now ...	ct.observeWikiText(ct.delayScan); // ... and every time the user pauses typing });

ct._ = function (s) { if (ct.translation && ct.translation[s]) { s = ct.translation[s]; }	var index = 1; while (arguments[index]) { s = s.replace('$' + index, arguments[index]); // todo: replace all? index++; }	return s; };

ct.getWikiText = function { if (window.wikEdUseWikEd) { var obj = {sel: WikEdGetSelection}; WikEdParseDOM(obj, wikEdFrameBody); return obj.plain; }	return ct.eTextarea.value; };

ct.setWikiText = function (s) { if (window.wikEdUseWikEd) { // todo: wikEd compatibility alert(ct._('Changing text in wikEd is not yet supported.')); return; };	ct.eTextarea.value = s; };

ct.focusWikiText = function { if (window.wikEdUseWikEd) { wikEdFrameWindow.focus; return; }	ct.eTextarea.focus; };

ct.selectWikiText = function (start, end) { if (window.wikEdUseWikEd) { var obj = x = {sel: WikEdGetSelection, changed: {}}; WikEdParseDOM(obj, wikEdFrameBody); var i = 0; while ((obj.plainStart[i + 1] != null) && (obj.plainStart[i + 1] <= start)) { i++; }		var j = i;		while ((obj.plainStart[j + 1] != null) && (obj.plainStart[j + 1] <= end)) { j++; }		obj.changed.range = document.createRange; obj.changed.range.setStart(obj.plainNode[i], start - obj.plainStart[i]); obj.changed.range.setEnd(obj.plainNode[j], end - obj.plainStart[j]); WikEdRemoveAllRanges(obj.sel); obj.sel.addRange(obj.changed.range); return; }	ct.setSelectionRange(ct.eTextarea, start, end); };

ct.observeWikiText = function (callback) { // todo: wikEd compatibility ct.observe(ct.eTextarea, 'keyup', ct.delayScan); };

ct.scan = function (force) { ct.scanTimeoutId = null; var s = ct.getWikiText; if ((s === ct.scannedText) && !force) { return; // Nothing to do, we've already scanned the very same text }	ct.scannedText = s;	while (ct.eSuggestions.firstChild != null) { ct.eSuggestions.removeChild(ct.eSuggestions.firstChild); }	// Warn about scanning a big article if ((s.length > ct.BIG_THRESHOLD) && !ct.isBigScanConfirmed) { ct.eSuggestions.appendChild(document.createTextNode( ct._('This article is rather long. Advisor.js may consume a lot of '				+ 'RAM and CPU resources while trying to parse the text.  You could limit '				+ 'your edit to a single section, or ') ));		ct.eSuggestions.appendChild(ct.anchor( ct._('scan the text anyway.'), 'javascript: ct.isBigScanConfirmed = true; ct.scan(true); void(0);', ct._('Ignore this warning.') ));		return; }	// Warn about scanning a talk page if ((window.wgCanonicalNamespace != null)				&& /(\b|_)talk$/i.test(window.wgCanonicalNamespace)				&& !ct.isTalkPageScanConfirmed) { ct.eSuggestions.appendChild(document.createTextNode( ct._('Advisor.js is disabled on talk pages, because ' +				'it might suggest changing other users\' comments. That would be ' +				'something against talk page conventions.  If you promise to be ' +				'careful, you can ') ));		ct.eSuggestions.appendChild(ct.anchor( ct._('scan the text anyway.'), 'javascript: ct.isTalkPageScanConfirmed = true; ct.scan(true); void(0);', ct._('Ignore this warning.') ));		return; }	ct.suggestions = ct.getSuggestions(s); if (ct.suggestions.length == 0) { ct.eSuggestions.appendChild(document.createTextNode( ct._('OK \u2014 Advisor.js found no issues with the text.') // U+2014 is an mdash ));		return; }	var nSuggestions = Math.min(ct.maxSuggestions, ct.suggestions.length); ct.eSuggestions.appendChild(document.createTextNode( (ct.suggestions.length == 1) ? ct._('1 suggestion: ') : ct._('$1 suggestions: ', ct.suggestions.length) ));	for (var i = 0; i < nSuggestions; i++) { var suggestion = ct.suggestions[i]; var eA = ct.anchor(				suggestion.name,				'javascript:ct.showSuggestion(' + i + '); void(0);',				suggestion.description		); suggestion.element = eA; ct.eSuggestions.appendChild(eA); if (suggestion.replacement != null) { var eSup = document.createElement('SUP'); ct.eSuggestions.appendChild(eSup); eSup.appendChild(ct.anchor( ct._('fix'), 'javascript:ct.fixSuggestion(' + i + '); void(0);' ));		}		ct.eSuggestions.appendChild(document.createTextNode(' ')); }	if (ct.suggestions.length > ct.maxSuggestions) { ct.eSuggestions.appendChild(ct.anchor( '...', 'javascript: ct.maxSuggestions = 1000; ct.scan(true); void(0);', ct._('Show All') ));	} };

ct.getSuggestions = function (s) { var suggestions = []; for (var i = 0; i < ct.rules.length; i++) { var a = ct.rules[i](s); for (var j = 0; j < a.length; j++) { suggestions.push(a[j]); }	}	suggestions.sort(function (x, y) {		return (x.start < y.start) ? -1 :		      (x.start > y.start) ? 1 :		       (x.end < y.end) ? -1 :		       (x.end > y.end) ? 1 : 0;	}); return suggestions; };

ct.delayScan = function { if (ct.scanTimeoutId != null) { clearTimeout(ct.scanTimeoutId); ct.scanTimeoutId = null; }	ct.scanTimeoutId = setTimeout(ct.scan, 500); };

ct.showSuggestion = function (k) { if (ct.getWikiText != ct.scannedText) { // The text has changed - just do another scan and don't change selection ct.scan; return; }	var suggestion = ct.suggestions[k]; var now = new Date.getTime; if ((suggestion.help != null) && (ct.lastShownSuggestionIndex === k) && (now - ct.lastShownSuggestionTime < 1000)) { // Show help var p = ct.getPosition(suggestion.element); var POPUP_WIDTH = 300; var eDiv = document.createElement('DIV'); eDiv.innerHTML = suggestion.help; eDiv.style.position = 'absolute'; eDiv.style.left = Math.max(0, Math.min(p.x, document.body.clientWidth - POPUP_WIDTH)) + 'px'; eDiv.style.top = (p.y + suggestion.element.offsetHeight) + 'px'; eDiv.style.border = 'solid ThreeDShadow 1px'; eDiv.style.backgroundColor = 'InfoBackground'; eDiv.style.fontSize = '12px'; eDiv.style.color = 'InfoText'; eDiv.style.width = POPUP_WIDTH + 'px'; eDiv.style.padding = '0.3em'; eDiv.style.zIndex = 10; document.body.appendChild(eDiv); ct.observe(document.body, 'click', function (event) {			event = event || window.event;			var target = event.target || event.srcElement;			var e = target;			while (e != null) {				if (e == eDiv) {					return;				}				e = e.parentNode;			}			document.body.removeChild(eDiv);			ct.stopObserving(document.body, 'click', arguments.callee);		}); ct.focusWikiText; return; }	ct.lastShownSuggestionIndex = k;	ct.lastShownSuggestionTime = now; ct.selectWikiText(suggestion.start, suggestion.end); };

// Usually, there is a ``fix'' link next to each suggestion. It is handled by: ct.fixSuggestion = function (k) { var s = ct.getWikiText; if (s != ct.scannedText) { ct.scan; return; }	var suggestion = ct.suggestions[k]; if (suggestion.replacement == null) { // the issue is not automatically fixable return; }	ct.setWikiText(			s.substring(0, suggestion.start)			+ suggestion.replacement			+ s.substring(suggestion.end)	); ct.selectWikiText(			suggestion.start,			suggestion.start + suggestion.replacement.length	); // Propose an edit summary unless it's a new section var editform = document.getElementById('editform'); if (!editform['wpSection'] || (editform['wpSection'].value != 'new')) { if (ct.appliedSuggestions[suggestion.name] == null) { ct.appliedSuggestions[suggestion.name] = 1; } else { ct.appliedSuggestions[suggestion.name]++; }		var a = []; for (var i in ct.appliedSuggestions) { a.push(i); }		a.sort(function (x, y) {			return (ct.appliedSuggestions[x] > ct.appliedSuggestions[y]) ? -1 :				  (ct.appliedSuggestions[x] < ct.appliedSuggestions[y]) ? 1 :				   (x < y) ? -1 : (x > y) ? 1 : 0;		}); var s = ''; for (var i = 0; i < a.length; i++) { var count = ct.appliedSuggestions[a[i]]; s += ', ' + ((count == 1) ? a[i] : (count + 'x ' + a[i])); }		// Cut off the leading ``,  and add ``formatting:  and ``using Advisor.js'' s = ct._(				'formatting: $1 (using Advisor.js)',				s.substring(2)		); // Render in DOM while (ct.eAddToSummary.firstChild != null) { ct.eAddToSummary.removeChild(ct.eAddToSummary.firstChild); }		ct.eAddToSummary.style.display = ''; ct.eAddToSummary.appendChild(ct.anchor( ct._('Add to summary'), 'javascript:ct.addToSummary(unescape("' + escape(s) + '"));', ct._('Append the proposed summary to the input field below') ));		ct.eAddToSummary.appendChild(document.createTextNode(': "' + s + '"')); }	// Re-scan immediately ct.scan; };

ct.addToSummary = function (summary) { var wpSummary = document.getElementById('wpSummary'); if (wpSummary.value != '') { summary = wpSummary.value + '; ' + summary; }	if ((wpSummary.maxLength > 0) && (summary.length > wpSummary.maxLength)) { alert(ct._( 'Error: If the proposed text is added to the summary, ' + 'its length will exceed the $1-character maximum by $2 characters.', /* $1 = */ wpSummary.maxLength, /* $2 = */ summary.length - wpSummary.maxLength ));		return; }	wpSummary.value = summary; ct.eAddToSummary.style.display = 'none'; };

if (!window.wgContentLanguage || (window.wgContentLanguage === 'en')) { // from this line on, a level of indent is spared

// The rules are stored in an array: ct.rules = []; // : Function[] // and are grouped into categories.

// === Linking rules ===

ct.rules.push(function (s) {	var re = /\[\[([{letter} ,\(\)\-]+)\|\1\]\]/g;	re = ct.fixRegExp(re);	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement:  + m[1] + ,				name: 'A|A',				description: '"A|A" can be simplified to A.',				help: ct.hlink('WP:Syntax#Wiki_markup', 'MediaWiki syntax')					+ ' allows links of the form A|A to be abbreviated as A. '		};	}	return a; });

ct.rules.push(function (s) {	var re = /\[\[([{letter} ,\(\)\-]+)\|\1([{letter}]+)\]\]/g;	re = ct.fixRegExp(re);	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement:  + m[1] +  + m[2],				name: 'A|AB',				description: '"A|AB" can be simplified to AB.',				help: ct.hlink('WP:Syntax#Wiki_markup', 'MediaWiki syntax')					+ ' allows links of the form <tt>A|AB</tt> to be abbreviated as <tt>AB.</tt>'		};	}	return a; });

ct.rules.push(function (s) {	// Initialise statics	var _static = arguments.callee;	if (_static.MONTH_MAP == null) {		_static.MONTH_MAP = {				Jan: 'January', Feb: 'February', Mar: 'March', Apr: 'April', May: 'May',				Jun: 'June', Jul: 'July', Aug: 'August', Sep: 'September', Oct: 'October',				Nov: 'November', Dec: 'December', January: 'January', February: 'February',				March: 'March', April: 'April', June: 'June', July: 'July',				August: 'August', September: 'September', October: 'October',				November: 'November', December: 'December'		};	}

var re = /(?:\[\[((?:(\d\d?) +({month}))|(?:({month}) +(\d\d?)))\]\],?? *)?\[\[({year})\]\](-\[\[\d\d-\d\d\]\])?/; re = ct.fixRegExp(re); var a = ct.getAllMatches(re, s); var b = []; for (var i = 0; i < a.length; i++) { var m = a[i]; var date = m[1] || null; var year = m[7] || null; if (date == null) { if (!m[8]) { // protect ISO dates---m[8] is the ISO remainder b.push({						start: m.start,						end: m.end,						replacement: year,						name: 'year link',						description: 'Convert link to normal text',						help: 'It is useless to link a year unless it is preceded by a day and month.'							+ ' Years with a day and month are normally linked so that the user '							+ 'preferences for date format can be applied, but linking a year alone '							+ 'has no effect.'				}); }		} else { var isAmerican = !m[2]; var day = (isAmerican) ? m[5] : m[2]; var month = _static.MONTH_MAP[(isAmerican) ? m[4] : m[3]]; var ws = m[6] || ''; // whitespace between date and year var replacement = (isAmerican) ? ( + month + ' ' + day + ',' + ws +  + year + '') : ( + day + ' ' + month +  + ws +  + year + ); if (replacement != m[0]) { b.push({						start: m.start,						end: m.end,						replacement: replacement,						name: 'date format',						description: 'Fix date format',						help: 'Commas in dates should follow one of these styles: '								+ '<tt>1 January 1970</tt> '								+ '<tt>January 1, 1970</tt> '								+ 'and month names should not be abbreviated.'				}); }		}	}	return b; });

ct.rules.push(function (s) {	// Matches decades in the range 1000s ... 2990s,	// linked either as xxx0s or as xxx0s	var re = /\[\[([12][0-9][0-9]0)(\]\]s\b|'?s\]\])/g;	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement: m[1] + 's',				name: 'decade link',				description: 'Convert link to normal text',				help: 'Decades should not be linked, unless they deepen the '					+ 'readers\' understanding of the topic.'		};	}	return a; });

ct.rules.push(function (s) {	// Matches decades in the range 1000s ... 2990s	var re = /\bthe +([12][0-9][0-9]0)'s\b/g;	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement: m[1] + 's',				name: 'decade format',				description: 'Remove the apostrophe from the decade',				help: 'The preferred decade format is without an apostrophe, per '						+ ct.hlink('WP:DATE#Longer_periods') + '.'		};	}	return a; });

ct.rules.push(function (s) {	var re = /\[\[([0-9]{1,2}(st|nd|rd|th) century)\]\]/g	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement: m[1],				name: 'century link',				description: 'Convert link to normal text',				help: 'Centuries should not be linked, unless they deepen the '					+ 'readers\' understanding of the topic.'		};	}	return a; });

// === Character formatting rules ===

ct.rules.push(function (s) {	var a = ct.getAllMatches(/ +$/gm, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		if (/^[=\|]$/.test(s[m.start - 1])) { // this can be tolerated, it happens too often in templates			continue;		}		b.push({ start: m.start, end: m.end, replacement: '', name: 'whitespace', description: 'Delete trailing whitespace', help: 'Trailing whitespace at the end of a line is unnecessary.' });	}	return b; });

ct.rules.push(function (s) {	var re = /[{letter}]( +- +)[{letter}]/g;	re = ct.fixRegExp(re);	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		// Be careful not to break wikilinks. If we find a ']' before we find an '['---drop the suggestion.		var rightContext = s.substring(m.end);		var indexOfOpening = rightContext.indexOf('[');		var indexOfClosing = rightContext.indexOf(']');		if ((indexOfClosing != -1) && ((indexOfOpening == -1) || (indexOfOpening > indexOfClosing))) {			continue;		}		b.push({ start: m.start + 1, end: m.end - 1, replacement: ' \u2014 ', // U+2014 is an mdash name: 'mdash', description: 'In a sentence, a hyphen surrounded by spaces means almost certainly an mdash.' });	}	return b; });

ct.rules.push(function (s) {	var re = /[^0-9]({year}) *(?:-|\u2014|&mdash;|--) *({year})[^0-9]/g; // U+2014 is an mdash	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start + 1,				end: m.end - 1,				replacement: m[1] + '\u2013' + m[2], // U+2013 is an ndash				name: 'ndash',				description: 'Year ranges look better with an n-dash.'		};	}	return a; });

ct.rules.push(function (s) {	var re = / (\u2014|\u2013|&mdash;|–)/g; // an m/ndash surrounded by normal spaces	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement: ' ' + m[1], // a non-breaking space and the dash				name: 'nbsp-dash',				description: 'Put a non-breaking space before the dash',				help: 'Putting a ' + ct.hlink('non-breaking space') + ' (<tt>&amp;nbsp;</tt>) before a dash would '					+ 'prevent the user agent from wrapping it at the beginning of the next line.'		};	}	return a; });

ct.rules.push(function (s) {	var a = ct.getAllMatches( /(\{\{\s*(?:IPA[0-3]?|IPAAusE|IPAEng|IPAHe|[Pp]ronAusE|[Pp]ronEng|[Pp]ronounced)\s*\|\s*)([^\|\}]+)/gi, s	);	var b = [];	var ipaSubstitions = {			':': {					replacement: '\u02d0', // U+02D0 is a ``Modifier letter triangular colon (used to denote vowel lengthening in IPA)					additionalHelp: " In this case the triangular colon (``\u02d0, <tt>U+02D0</tt>), "						+ "used to denote vowel lengthening, looks like a regular colon (``:, <tt>U+003A</tt>)."			},			'\: {					replacement: '\u02c8', // U+02C8 is a ``Modifier letter vertical line (put before a stresses syllable)					additionalHelp: " In this case the vertical line (``\u02c8, <tt>U+02c8</tt>), "						+ " which is put before a stressed syllable, looks like an apostrophe (`` ' '', <tt>U+0027</tt>)."			}	};	for (var i = 0; i < a.length; i++) {		var m = a[i];		var ipaText = m[2];		for (var j = 0; j < ipaText.length; j++) {			var ch = ipaText[j]; if (ipaSubstitions[ch] != null) { b.push({						start: m.start + m[1].length + j,						end: m.start + m[1].length + j + 1,						replacement: ipaSubstitions[ch].replacement,						name: 'IPA character',						description: "Replace ``false friend with the correct IPA character",						help: 'The correct IPA character '							+ ct.hlink('WP:IPA#Entering_IPA_characters', 'should be used')							+ " instead of its ``false friend."							+ ' Unicode contains a reserved range of characters for '							+ ct.hlink('International Phonetic Alphabet', 'IPA')							+ ' transcription. Some of them look very similar to other, '							+ 'more commonly used, alphabetic or punctuation characters (' + ct.hlink('False friend', 'false friends') + ').' + (ipaSubstitions[ch].additionalHelp || '')				}); }		}	}	return b; });

ct.rules.push(function (s) {	var re = /&#(([1-9][0-9]{0,4})|x([a-fA-F0-9]{1,4}));/g;	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		var charCode = (m[2]) ? parseInt(m[2]) : parseInt(m[3], 16);		if ((charCode < 128) || (charCode > 0xffff)) {			continue;		}		var ch = String.fromCharCode(charCode);		var chHex = charCode.toString(16).toUpperCase;		chHex = '0000'.substring(chHex.length) + chHex;		b.push({ start: m.start, end: m.end, replacement: ch, name: 'unicode-escape', description: 'Replace with an inline Unicode character', help: ct.hlink('WP:EDIT#Character_formatting', 'HTML-style escapes') + " like ``<tt>&amp;#" + m[1] + ";</tt>'' can be written inline using a Unicode character&mdash;in this case ``" + ch + "'' (<tt>U+" + chHex + "</tt>)." });	}	return b; });

ct.rules.push(function (s) {	var re = /&([A-Za-z]+);/g;	var a = ct.getAllMatches(re, s);	var b = [];	// Use a DOM element and its innerHTML property to do	// the unescaping, let the browser do the dirty job.	var e = document.createElement('DIV');	for (var i = 0; i < a.length; i++) {		var m = a[i];		if (m[1] == 'nbsp') {			// Opera incorrectly replaces nbsp-s with regular spaces:			// http://en.wikipedia.org/w/index.php?title=User_talk%3ACameltrader&diff=179233698&oldid=175946199			continue;		}		e.innerHTML = m[0];		var ch = e.innerHTML;		if (ch.length != 1) {			// The entity is not a single Unicode character---ignore it			continue;		}		var chHex = ch.charCodeAt(0).toString(16).toUpperCase;		chHex = '0000'.substring(chHex.length) + chHex;		b.push({ start: m.start, end: m.end, replacement: e.innerHTML, // the entity, unescaped name: 'HTML entity', description: 'Replace with an inline Unicode character', help: ct.hlink('WP:EDIT#Character_formatting', 'HTML-style escapes') + " like ``<tt>&amp;" + m[1] + ";</tt>'' can be written inline using a Unicode character&mdash;in this case ``" + ch + "'' (<tt>U+" + chHex + "</tt>)." });	}	return b; });

ct.rules.push(function (s) {	var a = ct.getAllMatches(/\u2026/g, s); // ellipsis	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		b.push({ start: m.start, end: m.end, replacement: '...', name: 'ellipsis', description: 'Replace ellipsis with three periods/full stops', help: "The ellipsis character (``\u2026'', U+2026) should be replaced with " + "three periods/full stops per " + ct.hlink('WP:MOS#Ellipses') });	}	return b; });

ct.rules.push(function (s) {	var a = ct.getAllMatches(/\b(NOT)\b/g, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		if ((s.substring(m.start - 2, m.start) == "''") && (s.substring(m.end, m.end + 2) == "''")) {			continue;		}		var noMoreLinksRemainder = ' A COLLECTION OF LINKS NOR SHOULD IT BE USED FOR';		if (s.substring(m.end, m.end + noMoreLinksRemainder.length) === noMoreLinksRemainder) {			// Tolerate subst'ed Template:NoMoreLinks			continue;		}		b.push({ start: m.start, end: m.end, replacement: "not", name: 'all-caps', description: 'Change to lowercase', help: 'According to the ' + ct.hlink('WP:MOS#Capital_letters', 'Manual of Style') + ', the word  + m[1].toLowerCase +  should be italicised instead ' + 'of being written in all caps.' });	}	return b; });

// === Template usage rules ===

ct.rules.push(function (s) {	// Initialise statics	var _static = arguments.callee;	if (_static.LANGUAGE_MAP == null) {		_static.LANGUAGE_MAP = { // : Hashtable<String, String>			// From http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes			// Note, that not all of these have a lang-xx template, but finding a reference			// to such a language is a good reason to create the template.			aa: 'Afar', ab: 'Abkhazian', ae: 'Avestan', af: 'Afrikaans', ak: 'Akan', am: 'Amharic', an: 'Aragonese', ar: 'Arabic',			as: 'Assamese', av: 'Avaric', ay: 'Aymara', az: 'Azerbaijani', ba: 'Bashkir', be: 'Belarusian', bg: 'Bulgarian',			bh: 'Bihari', bi: 'Bislama', bm: 'Bambara', bn: 'Bengali', bo: 'Tibetan', br: 'Breton', bs: 'Bosnian', ca: 'Catalan',			ce: 'Chechen', ch: 'Chamorro', co: 'Corsican', cr: 'Cree', cs: 'Czech', cu: 'Church Slavic', cv: 'Chuvash', cy: 'Welsh',			da: 'Danish', de: 'German', dv: 'Divehi', dz: 'Dzongkha', ee: 'Ewe', el: 'Greek', en: 'English', eo: 'Esperanto', es: 'Spanish', et: 'Estonian', eu: 'Basque', fa: 'Persian', ff: 'Fulah', fi: 'Finnish', fj: 'Fijian', fo: 'Faroese', fr: 'French', fy: 'Western Frisian', ga: 'Irish', gd: 'Gaelic', gl: 'Galician', gn: 'Guaran\u00ed', gu: 'Gujarati', gv: 'Manx', ha: 'Hausa', he: 'Hebrew', hi: 'Hindi', ho: 'Hiri Motu', hr: 'Croatian', ht: 'Haitian', hu: 'Hungarian', hy: 'Armenian', hz: 'Herero', ia: 'Interlingua (International Auxiliary Language Association)', id: 'Indonesian', ie: 'Interlingue', ig: 'Igbo', ii: 'Sichuan Yi', ik: 'Inupiaq', io: 'Ido', is: 'Icelandic', it: 'Italian', iu: 'Inuktitut', ja: 'Japanese', jv: 'Javanese', ka: 'Georgian', kg: 'Kongo', ki: 'Kikuyu', kj: 'Kuanyama', kk: 'Kazakh', kl: 'Kalaallisut', km: 'Khmer', kn: 'Kannada', ko: 'Korean', kr: 'Kanuri', ks: 'Kashmiri', ku: 'Kurdish', kv: 'Komi', kw: 'Cornish', ky: 'Kirghiz', la: 'Latin', lb: 'Luxembourgish', lg: 'Ganda', li: 'Limburgish', ln: 'Lingala', lo: 'Lao', lt: 'Lithuanian', lu: 'Luba-Katanga', lv: 'Latvian', mg: 'Malagasy', mh: 'Marshallese', mi: 'M\u0101ori', mk: 'Macedonian', ml: 'Malayalam', mn: 'Mongolian', mo: 'Moldavian', mr: 'Marathi', ms: 'Malay', mt: 'Maltese', my: 'Burmese', na: 'Nauru', nb: 'Norwegian Bokm\u00e5l', nd: 'North Ndebele', ne: 'Nepali', ng: 'Ndonga', nl: 'Dutch', nn: 'Norwegian Nynorsk', no: 'Norwegian', nr: 'South Ndebele', nv: 'Navajo', ny: 'Chichewa', oc: 'Occitan', oj: 'Ojibwa', om: 'Oromo', or: 'Oriya', os: 'Ossetian', pa: 'Panjabi', pi: 'P\u0101li', pl: 'Polish', ps: 'Pashto', pt: 'Portuguese', qu: 'Quechua', rm: 'Raeto-Romance', rn: 'Kirundi', ro: 'Romanian', ru: 'Russian', rw: 'Kinyarwanda', sa: 'Sanskrit', sc: 'Sardinian', sd: 'Sindhi', se: 'Northern Sami', sg: 'Sango', sh: 'Serbo-Croatian', si: 'Sinhala', sk: 'Slovak', sl: 'Slovenian', sm: 'Samoan', sn: 'Shona', so: 'Somali', sq: 'Albanian', sr: 'Serbian', ss: 'Swati', st: 'Southern Sotho', su: 'Sundanese', sv: 'Swedish', sw: 'Swahili', ta: 'Tamil', te: 'Telugu', tg: 'Tajik', th: 'Thai', ti: 'Tigrinya', tk: 'Turkmen', tl: 'Tagalog', tn: 'Tswana', to: 'Tonga', tr: 'Turkish', ts: 'Tsonga', tt: 'Tatar', tw: 'Twi', ty: 'Tahitian', ug: 'Uighur', uk: 'Ukrainian', ur: 'Urdu', uz: 'Uzbek', ve: 'Venda', vi: 'Vietnamese', vo: 'Volap\u00fck', wa: 'Walloon', wo: 'Wolof', xh: 'Xhosa', yi: 'Yiddish', yo: 'Yoruba', za: 'Zhuang', zh: 'Chinese', zu: 'Zulu' };		_static.REVERSE_LANGUAGE_MAP = {}; // : Hashtable<String, String> for (var i in _static.LANGUAGE_MAP) { _static.REVERSE_LANGUAGE_MAP[_static.LANGUAGE_MAP[i]] = i;		} }

// U+201e and U+201c are opening and closing double quotes // U+2013 and U+2014 are an ndash and an mdash var re = /\[\[(\w+) language\|\1\]\] *: (\'+)*([{letter} \"\'\u201e\u201c\/\u2014\u2013\-]+)(?:\2)/g;	re = ct.fixRegExp(re);	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		if (_static.REVERSE_LANGUAGE_MAP[m[1]] == null) {			continue;		}		var code = _static.REVERSE_LANGUAGE_MAP[m[1]];		// Markers for italics and bold are stripped off		b.push({				start: m.start,				end: m.end,				replacement: ,				name: 'lang-' + code,				description: 'Apply the template',				help: 'The <tt>' + ct.hlink('Template:lang-' + code, )					+ '</tt> template can be applied for this text.'					+ ' Similar templates are available in the '					+ ct.hlink('Category:Multilingual_support_templates', 'multilingual support templates category')					+ '.'		});	}	return b; });

ct.rules.push(function (s) {	var re = /^[ ':]*(?:Main +article)[ ']*:[ ']*\[\[([^\]]+)\]\][ ']*$/mig;	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		if ((m[1] != null) && (m[1] != "")) {			b.push({ start: m.start, end: m.end, replacement: '', name: 'template-main', description: 'Use the template', help: 'Template <tt>' + ct.hlink('Template:Main', '') + '</tt> can be used in this place.' });		}	}	return b; });

ct.rules.push(function (s) {	var re = /^[ ':]*(?:(?:Further|More) +info(?:rmation)?)[ ']*:[ ']*\[\[([^\]]+)\]\][ ']*$/mig;	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		if ((m[1] != null) && (m[1] != '')) {			b.push({ start: m.start, end: m.end, replacement: '', name: 'template-further', description: 'Use the template', help: 'Template <tt>' + ct.hlink('Template:Further', '') + '</tt> can be used in this place.' });		}	}	return b; });

ct.rules.push(function (s) {	var exceptions = {};	var wgTitle = window.wgTitle || '';	if (exceptions[wgTitle]) {		return [];	}	var re0 = /^([{letter}\-]+(?: [{letter}\-]+\.?)?) ([{letter}\-]+(?:ov|ev|ski))$/;	re0 = ct.fixRegExp(re0);	var m0 = re0.exec(wgTitle);	if (m0 == null) {		return [];	}	if (s.indexOf('DEFAULTSORT:') != -1) {		return [];	}	var firstNames = m0[1];	var lastName = m0[2];	var re1 = new RegExp( '\\[\\[(Category:[\\w _\\(\\),\\-]+)\\| *' + ct.escapeRegExp(lastName) + ', *' + ct.escapeRegExp(firstNames) + ' *\\]\\]', 'gi' );	var a = ct.getAllMatches(re1, s);	if (a.length == 0) {		return [];	}	var aStart = a[0].start;	var aEnd = a[a.length - 1].end;	var original = s.substring(aStart, aEnd);	var replacement = '\n'	               + original.replace(re1, '$1');	return [{			start: aStart,			end: aEnd,			replacement: replacement,			name: 'default-sort',			description: 'Use DEFAULTSORT to specify the common sort key',			help: 'The <tt>' + ct.hlink('Help:Categories#Default_sort_key', 'DEFAULTSORT')				+ '</tt> magic word can be used to specify sort keys for categories.  It was '				+ ct.hlink('Wikipedia:Wikipedia_Signpost/2007-01-02/Technology_report', 'announced in January 2007')				+ '.'	}]; });

ct.rules.push(function (s) {	var wgTitle = window.wgTitle || ;	var reTitle = /^(a|the) (.*)$/i;	if (!reTitle.test(wgTitle) || (s.indexOf('DEFAULTSORT') !== -1)) {		return [];	}	var a = ct.getAllMatches(/(\[\[)[Cc]ategory:[^\]]+\]\]/g, s);	if (a.length === 0) {		return [];	}	var mTitle = ct.getAllMatches(reTitle, wgTitle)[0]; // the match object for the title	var article = mTitle[1];	var nounPhrase = mTitle[2];	var highlightStart = a[0].start;	var highlightEnd = a[a.length - 1].end;	return [{			start: highlightStart,			end: highlightEnd,			replacement: '\n'						+ s.substring(highlightStart, highlightEnd),			name: 'defaultsort-' + article.toLowerCase,			description: 'Add DEFAULTSORT',			help: "Articles starting with ``a or ``the'' should participate in categories without the first word."	}]; });

ct.rules.push(function (s) {	var re = /(\{\{\s*)DEFAULTSORT\s*\|/g;	var a = ct.getAllMatches(re, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		b.push({ start: m.start, end: m.end, replacement: m[1] + 'DEFAULTSORT:', name: 'default-sort-magic-word', description: 'Replace the template with a magic word', help: 'Usage of the <tt></tt> template is discouraged. The magic word with the same name should be used instead.' });	}	return b; });

ct.rules.push(function (s) {	var _static = arguments.callee;	if (_static.DEPRECATED_TEMPLATES_ARRAY == null) {		_static.DEPRECATED_TEMPLATES_ARRAY = [				'ArB', 'ArTranslit', 'ArabDIN', 'BridgeType', 'CFB Coaching Record End', 'CFB Coaching Record Entry',				'CFB Coaching Record Start', 'CFB Coaching Record Team', 'CFB Coaching Record Team End', 'CURRENTWEEKDAY', 'Canada CP 2001',				'CelsiusToKelvin', 'Chembox', 'Chembox simple inorganic', 'Chembox simple organic', 'Chinesename', 'ConvertVolume',				'ConvertWeight', 'Country', 'Cultivar hybrid', 'Dated episode notability', 'Doctl', 'Dynamic navigation box',				'Dynamic navigation box with image', 'Dynamic navigation small', 'Episode-unreferenced', 'Extra album cover', 'Extra chronology',				'Fa', 'Factor', 'Fn', 'Fnb', 'Football stadium', 'Footnote', 'GUE', 'Geolinks-US-loc', 'Getamap', 'Harvard reference',				'Hiddenkey', 'IAST-hi', 'IAST1', 'ISOtranslit', 'Iftrue', 'Illinois Area Codes', 'Infobox Minor Planet', 'Infobox Ship', 'Infobox music venue', 'Ivrit', 'JER', 'Lang-yi2', 'Lang2iso', 'LangWithNameNoItals', 'Latinx', 'Military-Insignia', 'Mmuk mapdet', 'Mmuk mapho25', 'Mmuk maphot', 'Mmuknr map', 'Mmuknr photo', 'Mmukpc prim', 'Navbox generic', 'Navigation', 'Navigation box with image', 'Navigation no hide', 'Navigation with columns', 'Navigation with image', 'NavigationBox', 'Novelinfoboxincomp', 'Novelinfoboxneeded', 'OldVGpeerreview', 'Ordinal date', 'PD-LOC', 'PIqaD', 'Pekinensis tail familia Amaranthaceae', 'Pekinensis tail genus Chenopodium', 'Pekinensis tail regnum Plantae', 'PerB', 'PerTranslit', 'Pound avoirdupois', 'Prettyinfobox', 'Prettytable', 'Qif', 'Rating-10', 'Rating-3', 'Rating-4', 'Rating-5', 'Rating-6', 'Ref num', 'Reqimage', 'Rewrite-section', 'Ruby', 'Sectionrewrite', 'Semxlit', 'Skyscraper', 'Sortdate', 'Source', 'Storm pics', 'Supertribus', 'Switch', 'Tablabonita', 'Taxobox superregnum entry', 'Taxobox supertribus entry', 'IPA fonts', 'Unicode fonts', 'User R-proglang', 'User asm', 'User cobol', 'User css', 'User haskell', 'User html', 'User java', 'User mobile', 'User programming', 'User unicode', 'User xhtml', 'User xml', 'Tfd-kept', 'Timeline infobox finish', 'Timeline infobox start', 'Translit-yi2', 'WAFerry', 'Weight' ];		_static.DEPRECATED_TEMPLATES_SET = {}; for (var i = _static.DEPRECATED_TEMPLATES_ARRAY.length - 1; i >= 0; i--) { _static.DEPRECATED_TEMPLATES_SET[_static.DEPRECATED_TEMPLATES_ARRAY[i]] = true; }	}	var a = ct.getAllMatches(ct.fixRegExp(/(\{\{\s*)([{letter}0-9\s\-]+)(\s*(\||\}\}))/g), s); var b = []; for (var i = 0; i < a.length; i++) { var m = a[i]; var name = m[2].replace(/ /g, '_'); name = name.charAt(0).toUpperCase + name.substring(1); if (_static.DEPRECATED_TEMPLATES_SET[name]) { b.push({					start: m.start,					end: m.end,					name: 'deprecated-template',					description: 'Template has been deprecated',					help: 'Template <tt>' + ct.hlink('Template:' + name, '')						+ ' is ' + ct.hlink('Category:Deprecated templates', 'deprecated')						+ '.  Consider using another one as recommended on the template page.'			}); }	}	return b; });

// === Other rules ===

ct.rules.push(function (s) {	var re = /^(?: *)(==+)( *)([^=]*[^= ])( *)\1/gm;	var a = ct.getAllMatches(re, s);	if (a.length == 0) {		return [];	}	var b = [];	var level = 0; // == Level 1 ==, === Level 2 ===, ==== Level 3 ====, etc.	var editform = document.getElementById('editform');	// If we are editing a section, we have to be tolerant to the first heading's level	var isSection = editform &&	               (editform['wpSection'] != null) &&	                (editform['wpSection'].value != '');	// Count spaced and non-spaced headings to find out the majority	var counters = {spaced: 0, nonSpaced: 0, unclear: 0};	for (var i = 0; i < a.length; i++) {		var m = a[i];		counters[(!m[2] && !m[4]) ? 'nonSpaced' : (m[2] && m[4]) ? 'spaced' : 'unclear']++;	}	var predominantSpacingStyle;	if (counters.spaced > counters.nonSpaced) {		predominantSpacingStyle = 'spaced';	} else if (counters.spaced < counters.nonSpaced) { predominantSpacingStyle = 'nonSpaced'; } else { predominantSpacingStyle = 'unclear'; // We cannot decide which spacing style is predominant, // so we show a suggestion attached to the first heading, // recommending consistent spacing: b.push({				start: a[0].start,				end: a[0].end,				replacement: null,				name: 'heading',				description: 'Consider using consistent heading spacing',				help: 'Heading style should be either '					+ "``<tt>== Heading ==</tt> or ``<tt>==Heading==</tt>. "					+ "Headings in this article use an equal number of both.  "					+ "Consider choosing a heading style and using it consistently."		}); }	var titleSet = {}; // a set of title names, will be used to detect duplicates for (var i = 0; i < a.length; i++) { var m = a[i]; if (m[2] != m[4]) { var spacer = (predominantSpacingStyle == 'spaced') ? ' ' : (predominantSpacingStyle == 'nonSpaced') ? '' : m[2]; b.push({					start: m.start,					end: m.end,					replacement: m[1] + spacer + m[3] + spacer + m[1],					name: 'heading',					description: 'Fix whitespace',					help: 'Heading style should be either '						+ "``<tt>== Heading ==</tt> or ``<tt>==Heading==</tt>."			}); } else if ((m[2] && (predominantSpacingStyle == 'nonSpaced'))		      || (!m[2] && (predominantSpacingStyle == 'spaced'))) { var spacer = (m[2]) ? '' : ' ';			b.push({					start: m.start,					end: m.end,					replacement: m[1] + spacer + m[3] + spacer + m[1],					name: 'heading-style',					description: 'Conform to the existing majority of '						+ ((m[2]) ? 'non-spaced' : 'spaced') + ' headings',					help: 'There are two styles of writing headings in wikitext:<tt><ul><li>== Spaced ==<li>==Non-spaced==</ul>'						+ 'Most of the headings in this article are '						+ ((m[2]) ? 'non-spaced' : 'spaced')						+ ' (' + counters.spaced + ' vs ' + counters.nonSpaced + ').  '						+ 'It is recommended that you adapt your style to the majority.'			}); }		var oldLevel = level; level = m[1].length - 1; if ( (level - oldLevel > 1) && (!isSection || (oldLevel > 0)) ) { var h = '======='.substring(0, oldLevel + 2); b.push({					start: m.start,					end: m.end,					replacement: h + m[2] + m[3] + m[2] + h,					name: 'heading-nesting',					description: 'Fix improper nesting',					help: 'A heading ' + ct.hlink('WP:MOS#Section_headings', 'should be')						+ ' nested one level deeper than its parent heading.'			}); }		var frequentMistakes = [ { code: 'see-also', wrong: /^see *al+so$/i,          correct: 'See also' }, { code: 'ext-links', wrong: /^external links?$/i,    correct: 'External links' }, { code: 'refs',     wrong: /^ref+e?r+en(c|s)es?$/i,  correct: 'References' } ];		for (var j = 0; j < frequentMistakes.length; j++) { var fm = frequentMistakes[j]; if (fm.wrong.test(m[3]) && (m[3] != fm.correct)) { var r = m[1] + m[2] + fm.correct + m[2] + m[1]; if (r != m[0]) { b.push({							start: m.start,							end: m.end,							replacement: r,							name: fm.code,							description: 'Change to ``' + fm.correct + ".",							help: 'The correct spelling/capitalisation is ``<tt>' + fm.correct + "</tt>."					}); }			}		}		if (titleSet[m[3]] != null) { b.push({					start: m.start + (m[1] || ).length + (m[2] || ).length,					end: m.start + (m[1] || ).length + (m[2] || ).length + m[3].length,					replacement: null, // we cannot propose anything, it's the editor who has to choose a different title					name: 'duplicate-title',					description: 'Avoid duplicate section titles',					help: 'Section names '						+ ct.hlink('WP:MOS#Section_headings', 'should preferably be unique')						+ ' within a page; this applies even for the names of subsections.'			}); }		titleSet[m[3]] = true; }	return b; });

ct.rules.push(function (s) {	// U+2013 and U+2014 are an ndash and an mdash	var re = /\( *(?:b\.? *)?({year}) *(?:[\-\\u2013\\u2014]|–|&mdash;|--) *\)/g;	var a = ct.getAllMatches(re, s);	for (var i = 0; i < a.length; i++) {		var m = a[i];		a[i] = {				start: m.start,				end: m.end,				replacement: '(born ' + m[1] + ')',				name: 'born',				description: 'The word \'born\' should be fully written.',				help: 'According to '					+ ct.hlink('WP:DATE#Dates_of_birth_and_death', 'WP:DATE')					+ ', the word born should be fully written.'		};	}	return a; });

ct.rules.push(function (s) {	// ISBN: ten or thirteen digits, each digit optionally followed by a hyphen, the last digit can be 'X' or 'x'	var a = ct.getAllMatches(/ISBN *=? *(([0-9Xx]-?)+)/gi, s);	var b = [];	for (var i = 0; i < a.length; i++) {		var m = a[i];		var s = m[1].replace(/[^0-9Xx]+/g, '').toUpperCase; // remove all non-digits		if ((s.length !== 10) && (s.length !== 13)) {			b.push({ start: m.start, end: m.end, name: 'ISBN', description: 'Should be either 10 or 13 digits long', help: 'ISBN numbers should be either 10 or 13 digits long. '							+ 'This one consists of ' + s.length + ' digits: <tt>' + m[1] + '</tt>' });			continue;		}		var isNew = (s.length === 13); // old (10 digits) or new (13 digits)		var xIndex = s.indexOf('X');		if ((xIndex !== -1) && ((xIndex !== 9) || isNew)) {			b.push({ start: m.start, end: m.end, name: 'ISBN', description: 'Improper usage of X as a digit', help: "``<tt>X</tt>'' can only be used in 10-digit ISBN numbers " + ' as the last digit: <tt>' + m[1] + '</tt>' });			continue;		}		var computedChecksum = 0;		var modulus = (isNew) ? 10 : 11;		for (var j = s.length - 2; j >= 0; j--) {			var digit = s.charCodeAt(j) - 48; // 48 is the ASCII code of '0'			var quotient = (isNew)								? ((j & 1) ? 3 : 1) // the new way: 1 for even, 3 for odd								: (10 - j);        // the old way: 10, 9, 8, etc			computedChecksum = (computedChecksum + (quotient * digit)) % modulus;		}		computedChecksum = (modulus - computedChecksum) % modulus;		var c = s.charCodeAt(s.length - 1) - 48;		var actualChecksum = ((c < 0) || (9 < c)) ? 10 : c;		if (computedChecksum === actualChecksum) {			continue;		}		b.push({ start: m.start, end: m.end, name: 'ISBN', description: 'Bad ISBN checksum', help: 'Bad ISBN checksum for <tt>' + m[1] + '</tt> ' });	}	return b; });

} // end if (window.wgContentLanguage === 'en')

// /* * DisamAssist: a tool for repairing links from articles to disambiguation pages. */ ( function( mw, $, undefined ) {	var cfg = {};	var txt = {};	var startLink, ui;	var links, pageChanges;	var currentPageTitle, currentPageParameters, currentLink;	var possibleBacklinkDestinations;	var forceSamePage = false;	var running = false;	var choosing = false;	var canMarkIntentionalLinks = false;	var displayedPages = {};	var editCount = 0;	var editLimit;	var pendingSaves = [];	var pendingEditBox = null;	var pendingEditBoxText;	var lastEditMillis = 0;	var runningSaves = false;	/*	 * Entry point. Check whether we are in a disambiguation page. If so, add a link to start the tool	 */	var install = function {		cfg = window.DisamAssist.cfg;		txt = window.DisamAssist.txt;		if ( mw.config.get( 'wgAction' ) === 'view' && isDisam ) {			mw.loader.using( ['mediawiki.Title', 'mediawiki.api'], function { $( document ).ready( function {					// This is a " (disambiguation)" page					if ( new RegExp( cfg.disamRegExp ).exec( mw.config.get( 'wgTitle' ) ) ) {						var startMainLink = $( mw.util.addPortletLink( 'p-cactions', '#', txt.startMain ) )							.click( startMain );						var startSameLink = $( mw.util.addPortletLink( 'p-cactions', '#', txt.startSame ) )							.click( startSame );						startLink = startMainLink.add( startSameLink );					} else {						startLink = $( mw.util.addPortletLink( 'p-cactions', '#', txt.start ) ).click( start );					}				} ); } );		}	};	/*	 * Start the tool. Display the UI and begin looking for links to fix	 */	var start = function {		if ( !running ) {			running = true;			links = [];			pageChanges = [];			displayedPages = {};			ensureDABExists.then( function( canMark ) { canMarkIntentionalLinks = canMark; createUI; addUnloadConfirm; markDisamOptions; checkEditLimit.then( function {					togglePendingEditBox( false );					doPage;				} ); } );		}	};	/*	 * Start DisamAssist. Disambiguate incoming links to the current page, regardless	 * of the title.	 */	var startSame = function {		forceSamePage = true;		start;	};	/*	 * Start DisamAssist. If the page title ends with " (disambiguation)", disambiguate	 * links to the primary topic article. Otherwise, disambiguate links to the current	 * page.	 */	var startMain = function {		forceSamePage = false;		start;	};	/*	 * Create and show the user interface.	 */	var createUI = function {		ui = {			display: $( ' ' ).addClass( 'disamassist-box disamassist-mainbox' ),			finishedMessage: $( '  ' ).text( txt.noMoreLinks ).hide,			pageTitleLine: $( '  ' ).addClass( 'disamassist-pagetitleline' ),			pendingEditCounter: $( '  ').addClass( 'disamassist-editcounter' ),			context: $( '  ' ).addClass( 'disamassist-context' ),			undoButton: createButton( txt.undo, undo ),			omitButton: createButton( txt.omit, omit ), endButton: createButton( txt.close, saveAndEnd ), refreshButton: createButton( txt.refresh, refresh ), titleAsTextButton: createButton( txt.titleAsText, chooseTitleFromPrompt ), intentionalLinkButton: canMarkIntentionalLinks ? createButton( txt.intentionalLink, chooseIntentionalLink ) : $( ' ' ), disamNeededButton: cfg.disamNeededText ? createButton( txt.disamNeeded, chooseDisamNeeded ) : $( ' ' ), removeLinkButton: createButton( txt.removeLink, chooseLinkRemoval ) };		var top = $( ' ' ).addClass( 'disamassist-top' ) .append( [ui.pendingEditCounter, ui.finishedMessage, ui.pageTitleLine] ); var leftButtons = $( ' ' ).addClass( 'disamassist-leftbuttons' ) .append( [ui.titleAsTextButton, ui.removeLinkButton, ui.intentionalLinkButton, ui.disamNeededButton, ui.omitButton] ); var rightButtons = $( ' ' ).addClass( 'disamassist-rightbuttons' ) .append( [ui.undoButton, ui.refreshButton, ui.endButton] ); var allButtons = $( ' ' ).addClass( 'disamassist-allbuttons' ) .append( [leftButtons, rightButtons] ); ui.display.append( [top, ui.context, allButtons] ); updateEditCounter; toggleActionButtons( false ); // Insert the UI in the page $( '#mw-content-text' ).before( ui.display ); ui.display.hide.fadeIn; };	/*	 * If there are pending changes, show a confirm dialog before closing */	var addUnloadConfirm = function { $( window ).bind( 'beforeunload', function( ev ) {			if ( running && checkActualChanges ) {				return txt.pending;			} else if ( editCount !== 0 ) {				return txt.editInProgress;			}		}); };	/*	 * Mark the disambiguation options as such */	var markDisamOptions = function { var optionPageTitles = []; var optionMarkers = []; getDisamOptions.each( function {			var link = $( this );			var title = extractPageName( link );			var optionMarker = $( '<a></a>' ).attr( 'href', '#' ).addClass( 'disamassist-optionmarker' )				.text( txt.optionMarker ).click( function( ev ) { ev.preventDefault; chooseReplacement( title ); } );			link.after( optionMarker );			optionMarkers.push( optionMarker );			optionPageTitles.push( title );		} ); // Now check the disambiguation options and display a different message for those that are // actually the same as the target page where the links go, as choosing those options doesn't really // accomplish anything (except bypassing redirects, which might be useful in some cases) var targetPage = getTargetPage; fetchRedirects( optionPageTitles.concat( targetPage ) ).done( function( redirects ) {			var endTargetPage = resolveRedirect( targetPage, redirects );			for ( var ii = 0; ii < optionPageTitles.length; ii++ ) {				var endOptionTitle = resolveRedirect( optionPageTitles[ii], redirects );				if ( isSamePage( endOptionTitle, targetPage ) || isSamePage( optionPageTitles[ii], targetPage ) ) {					optionMarkers[ii].text( txt.targetOptionMarker ).addClass( 'disamassist-curroptionmarker');				} else if ( isSamePage( endOptionTitle, endTargetPage ) ) {					optionMarkers[ii].text( txt.redirectOptionMarker ).addClass( 'disamassist-curroptionmarker');				}			}		} ).fail( error ); };	/*	 * Check whether intentional links to disambiguation pages can be explicitly marked * as such in this wiki. If so, ensure that a "Foo (disambiguation)" page exists. * Returns a jQuery promise */	var ensureDABExists = function { var dfd = new $.Deferred; var title = mw.config.get( 'wgTitle' ); // That concept doesn't exist in this wiki. if ( !cfg.intentionalLinkOption ) { dfd.resolve( false ); // "Foo (disambiguation)" exists: it's the current page. } else if ( new RegExp( cfg.disamRegExp ).exec( title ) ) { dfd.resolve( true ); } else { var disamTitle = cfg.disamFormat.replace( '$1', title ); loadPage( disamTitle ).done( function( page ) {				// "Foo (disambiguation)" doesn't exist.				if ( page.missing ) {					// We try to create it					page.content = cfg.redirectToDisam.replace( '$1', title );					var summary = txt.redirectSummary.replace( '$1', title );					savePage( disamTitle, page, summary, false, true ).done( function { dfd.resolve( true ); } ).fail( function( description ) { error( description ); dfd.resolve( false ); } );				// It does exist				} else {					dfd.resolve( true );				}			} ).fail( function( description ) {				error( description );				dfd.resolve( false );			} ); }		return dfd.promise; };	/*	 * Check whether the edit cooldown applies and sets editLimit accordingly. * Returns a jQuery promise */	var checkEditLimit = function { var dfd = new $.Deferred; if ( cfg.editCooldown <= 0 ) { editLimit = false; dfd.resolve; } else { fetchRights.done( function( rights ) {				editLimit = $.inArray( 'bot', rights ) === -1;			} ).fail( function( description ) {				error( description );				editLimit = true;			} ).always( function {				dfd.resolve;			} ); }		return dfd.promise; };	/*	 * Find and ask the user to fix all the incoming links to the disambiguation ("target") * page from a single "origin" page */	var doPage = function { if ( pageChanges.length > cfg.historySize ) { applyChange( pageChanges.shift ); }		if ( links.length === 0 ) { var targetPage = getTargetPage; getBacklinks( targetPage ).done( function( backlinks, pageTitles ) {				var pending = {};				$.each( pendingSaves, function { pending[this[0]] = true; } );				possibleBacklinkDestinations = $.grep( pageTitles, function( t, ii) { if ( t == targetPage ) { return true; }					return removeDisam(t) != targetPage; } );				// Only incoming links from pages we haven't seen yet and we aren't currently				// saving (displayedPages is reset when the tool is closed and opened again, // while the list of pending changes isn't; if the edit cooldown is disabled, // it will be empty)				links = $.grep( backlinks, function( el, ii ) { return !displayedPages[el] && !pending[el]; } );				if ( links.length === 0 ) {					updateContext;				} else {					doPage;				}			} ).fail( error ); } else { currentPageTitle = links.shift; displayedPages[currentPageTitle] = true; toggleActionButtons( false ); loadPage( currentPageTitle ).done( function( data ) {				currentPageParameters = data;				currentLink = null;				doLink;			} ).fail( error ); }	};	/*	 * Find and ask the user to fix a single incoming link to the disambiguation ("target") * page */	var doLink = function { currentLink = extractLinkToPage( currentPageParameters.content,			possibleBacklinkDestinations, currentLink ? currentLink.end : 0 ); if ( currentLink ) { updateContext; } else { doPage; }	};	/*	 * Replace the target of a link with a different one * pageTitle: New link target * extra: Additional text after the link (optional) * summary: Change summary (optional) */	var chooseReplacement = function( pageTitle, extra, summary ) { if ( choosing ) { choosing = false; if ( !summary ) { if ( pageTitle ) { summary = txt.summaryChanged.replace( '$1', pageTitle ); } else { summary = txt.summaryOmitted; }			}			addChange( currentPageTitle, currentPageParameters, currentPageParameters.content, currentLink, summary ); if ( pageTitle && ( pageTitle !== getTargetPage || extra ) ) { currentPageParameters.content = replaceLink( currentPageParameters.content, pageTitle, currentLink, extra || '' ); }			doLink; }	};	/*	 * Replace the link with an explicit link to the disambiguation page */	var chooseIntentionalLink = function { var disamTitle = cfg.disamFormat.replace( '$1', getTargetPage ); chooseReplacement( disamTitle, '', txt.summaryIntentional ); };	/*	 * Prompt for an alternative link target and use it as a replacement */	var chooseTitleFromPrompt = function { var title = prompt( txt.titleAsTextPrompt ); if ( title !== null ) { chooseReplacement( title ); }	};	/*	 * Remove the current link, leaving the text unchanged */	var chooseLinkRemoval = function { if ( choosing ) { var summary = txt.summaryRemoved; addChange( currentPageTitle, currentPageParameters, currentPageParameters.content, currentLink, summary ); currentPageParameters.content = removeLink( currentPageParameters.content, currentLink ); doLink; }	};	/*	 * Add a "disambiguation needed" template after the link */	var chooseDisamNeeded = function { chooseReplacement( currentLink.title, cfg.disamNeededText, txt.summaryHelpNeeded ); };	/*	 * Undo the last change */	var undo = function { if ( pageChanges.length !== 0 ) { var lastPage = pageChanges[pageChanges.length - 1]; if ( currentPageTitle !== lastPage.title ) { links.unshift( currentPageTitle ); currentPageTitle = lastPage.title; }			currentPageParameters = lastPage.page; currentPageParameters.content = lastPage.contentBefore.pop; currentLink = lastPage.links.pop; lastPage.summary.pop; if ( lastPage.contentBefore.length === 0 ) { pageChanges.pop; }			updateContext; }	};	/*	 * Omit the current link without making a change */	var omit = function { chooseReplacement( null ); };	/*	 * Save all the pending changes and restart the tool. */	var refresh = function { saveAndEnd; start; };	/*	 * Enable or disable the buttons that can perform actions on a page or change the current link. * enabled: Whether to enable or disable the buttons */	var toggleActionButtons = function( enabled ) { var affectedButtons = [ui.omitButton, ui.titleAsTextButton, ui.removeLinkButton, ui.intentionalLinkButton, ui.disamNeededButton, ui.undoButton]; $.each( affectedButtons, function( ii, button ) {			button.prop( 'disabled', !enabled );		} ); };	/*	 * Show or hide the 'no more links' message * show: Whether to show or hide the message */	var toggleFinishedMessage = function( show ) { toggleActionButtons( !show ); ui.undoButton.prop( 'disabled', pageChanges.length === 0 ); ui.finishedMessage.toggle( show ); ui.pageTitleLine.toggle( !show ); ui.context.toggle( !show ); };	var togglePendingEditBox = function( show ) { if ( pendingEditBox === null ) { pendingEditBox = $( ' ' ).addClass( 'disamassist-box disamassist-pendingeditbox' ); pendingEditBoxText = $( ' ' ); pendingEditBox.append( pendingEditBoxText ).hide; if ( editLimit ) { pendingEditBox.append( $( ' ' ).text( txt.pendingEditBoxLimited )					.addClass( 'disamassist-subtitle' ) ); }			$( '#mw-content-text' ).before( pendingEditBox ); updateEditCounter; }		if ( show ) { pendingEditBox.fadeIn; } else { pendingEditBox.fadeOut; }	};	var notifyCompletion = function { var oldTitle = document.title; document.title = txt.notifyCharacter + document.title; $( document.body ).one( 'mousemove', function {			document.title = oldTitle;		} ); };	/*	 * Update the displayed information to match the current link * or lack thereof */	var updateContext = function { updateEditCounter; if ( !currentLink ) { toggleFinishedMessage( true ); } else { ui.pageTitleLine.html( txt.pageTitleLine.replace( '$1', mw.util.getUrl( currentPageTitle, {redirect: 'no'} ) ).replace( '$2', mw.html.escape( currentPageTitle ) ) ); var context = extractContext( currentPageParameters.content, currentLink ); ui.context.empty .append( $( ' ' ).text( context[0] ) ) .append( $( ' ' ).text( context[1] ).addClass( 'disamassist-inclink' ) ) .append( $( ' ' ).text( context[2] ) ); var numLines = Math.ceil( ui.context.height / parseFloat( ui.context.css( 'line-height' ) ) ); if ( numLines < cfg.numContextLines ) { // Add cfg.numContextLines - numLines + 1 line breaks, so that the total number // of lines is cfg.numContextLines ui.context.append( new Array( cfg.numContextLines - numLines + 2 ).join( ' ' ) ); }			toggleFinishedMessage( false ); ui.undoButton.prop( 'disabled', pageChanges.length === 0 ); ui.removeLinkButton.prop( 'disabled', currentPageParameters.redirect ); ui.intentionalLinkButton.prop( 'disabled', currentPageParameters.redirect ); ui.disamNeededButton.prop( 'disabled', currentPageParameters.redirect || currentLink.hasDisamTemplate ); choosing = true; }	};	/*	 * Update the count of pending changes */	var updateEditCounter = function { if ( ui.pendingEditCounter ) { ui.pendingEditCounter.text( txt.pendingEditCounter.replace( '$1', editCount )				.replace( '$2', countActuallyChangedFullyCheckedPages ) ); }		if ( pendingEditBox ) { if ( editCount === 0 && !running ) { togglePendingEditBox( false ); notifyCompletion; }			var textContent = editCount; if ( editLimit ) { textContent = txt.pendingEditBoxTimeEstimation.replace( '$1', editCount ) .replace( '$2', secondsToHHMMSS( cfg.editCooldown * editCount ) ); }			pendingEditBoxText.text( txt.pendingEditBox.replace( '$1', textContent ) ); }	};	/*	 * Apply the changes made to an "origin" page * pageChange: Change that will be saved */	var applyChange = function( pageChange ) { if ( pageChange.page.content !== pageChange.contentBefore[0] ) { editCount++; var changeSummaries = pageChange.summary.join( txt.summarySeparator ); var summary = txt.summary.replace( '$1', getTargetPage ).replace( '$2', changeSummaries ); var save = editLimit ? saveWithCooldown : savePage; save( pageChange.title, pageChange.page, summary, true, true ).always( function {				if ( editCount > 0 ) {					editCount--;				}				updateEditCounter;			} ).fail( error ); updateEditCounter; }	};	/*	 * Save all the pending changes */	var applyAllChanges = function { for ( var ii = 0; ii < pageChanges.length; ii++ ) { applyChange( pageChanges[ii] ); }		pageChanges = []; };	/*	 * Record a new pending change * pageTitle: Title of the page * page: Content of the page * oldContent: Content of the page before the change * link: Link that has been changed * summary: Change summary */	var addChange = function( pageTitle, page, oldContent, link, summary ) { if ( ( pageChanges.length === 0 ) || ( pageChanges[pageChanges.length - 1].title !== pageTitle ) ) { pageChanges.push( {				title: pageTitle,				page: page,				contentBefore: [],				links: [],				summary: []			} ); }		var lastPageChange = pageChanges[pageChanges.length - 1]; lastPageChange.contentBefore.push( oldContent ); lastPageChange.links.push( link ); lastPageChange.summary.push( summary ); };	/*	 * Check whether actual changes are stored in the history array */	var checkActualChanges = function { return countActualChanges !== 0; };	/*	 * Return the number of entries in the history array that represent actual changes */	var countActualChanges = function { var changeCount = 0; for ( var ii = 0; ii < pageChanges.length; ii++ ) { if ( pageChanges[ii].page.content !== pageChanges[ii].contentBefore[0] ) { changeCount++; }		}		return changeCount; };	/*	 * Return the number of changed pages in the history array, ignoring the last entry * if we aren't done with that page yet */	var countActuallyChangedFullyCheckedPages = function { var changeCount = countActualChanges; if ( pageChanges.length !== 0 ) { var lastChange = pageChanges[pageChanges.length - 1]; if ( lastChange.title === currentPageTitle && currentLink !== null					&& lastChange.page.content !== lastChange.contentBefore[0] ) { changeCount--; }		}		return changeCount; };	/*	 * Find the links to disambiguation options in a disambiguation page */	var getDisamOptions = function { return $( '#mw-content-text a' ).filter( function {			return !!extractPageName( $( this ) );		} ); };	/*	 * Save all the pending changes and close the tool */	var saveAndEnd = function { applyAllChanges; end; };	/*	 * Close the tool */	var end = function { var currentToolUI = ui.display; choosing = false; running = false; startLink.removeClass( 'selected' ); $( '.disamassist-optionmarker' ).remove; currentToolUI.fadeOut( { complete: function {			currentToolUI.remove;			if ( editCount !== 0 ) {				togglePendingEditBox( true );			}		} } ); };	/*	 * Display an error message */	var error = function( errorDescription ) { var errorBox = $( ' ' ).addClass( 'disamassist-box disamassist-errorbox' ); errorBox.text( txt.error.replace( '$1', errorDescription ) ); errorBox.append( createButton( txt.dismissError, function { errorBox.fadeOut; } ).addClass( 'disamassist-errorbutton' ) ); var uiIsInPlace = ui && $.contains( document.documentElement, ui.display[0] ); var nextElement = uiIsInPlace ? ui.display : $( '#mw-content-text' ); nextElement.before( errorBox ); errorBox.hide.fadeIn; }	/*	 * Change a link so that it points to the title * text: The wikitext of the whole page * title: The new destination of the link * link: The link that will be modified * extra: Text that will be added after the link (optional) */	var replaceLink = function( text, title, link, extra ) { var newContent; if ( isSamePage( title, link.description ) ) { // B|A should be replaced with A rather than A|A newContent = link.description; } else { newContent = title + '|' + link.description; }		var linkStart = text.substring( 0, link.start ); var linkEnd = text.substring( link.end ); return linkStart +  + newContent +  + link.afterDescription + ( extra || '' ) + linkEnd; };	/*	 * Remove a link from the text * text: The wikitext of the whole page * link: The link that will be removed */	var removeLink = function( text, link ) { var linkStart = text.substring( 0, link.start ); var linkEnd = text.substring( link.end ); return linkStart + link.description + link.afterDescription + linkEnd; };	/*	 * Extract a link from a string in wiki format, * starting from a given index. Return a link if one can be found, * otherwise return null. The "link" includes "disambiguation needed" * templates inmediately following the link proper * text: Text from which the link will be extracted * lastIndex: Index from which the search will start */	var extractLink = function( text, lastIndex ) { // FIXME: Not an actual title regex (lots of false positives		// and some false negatives), but hopefully good enough. var titleRegex = /\[\[(.*?)(?:\|(.*?))?]]/g; // Ditto for the template regex. Disambiguation link templates // should be simple enough for this not to matter, though. var templateRegex = /^(\w*[.,:;?!)}\s]*){{\s*([^|{}]+?)\s*(?:\|[^{]*?)?}}/;		titleRegex.lastIndex = lastIndex;		var match = titleRegex.exec( text );		if ( match !== null && match.index !== -1 ) {			var possiblyAmbiguous = true;			var hasDisamTemplate = false;			var end = match.index + 4 + match[1].length + ( match[2] ? 1 + match[2].length : 0 );			var afterDescription = ;			var rest = text.substring( end );			var templateMatch = templateRegex.exec( rest );			if ( templateMatch !== null ) {				var templateTitle = getCanonicalTitle( templateMatch[2] );				if ( $.inArray( templateTitle, cfg.disamLinkTemplates ) !== -1 ) {					end += templateMatch[0].length;					afterDescription = templateMatch[1].replace(/\s$/, );					hasDisamTemplate = true;				} else if ( $.inArray( templateTitle, cfg.disamLinkIgnoreTemplates ) !== -1 ) {					possiblyAmbiguous = false;				}			}			return {				start: match.index,				end: end,				possiblyAmbiguous: possiblyAmbiguous,				hasDisamTemplate: hasDisamTemplate,				title: match[1],				description: match[2] ? match[2] : match[1],				afterDescription: afterDescription			};		}		return null;	};	/*	 * Extract a link to one of a number of destination pages from a string 	 * ("text") in wiki format, starting from a given index ("lastIndex"). * "Disambiguation needed" templates are included as part of the links, * while "R to disambiguation page" and * text: Page in wiki format * destinations: Array of page titles to look for * lastIndex: Index from which the search will start */	var extractLinkToPage = function( text, destinations, lastIndex ) { var link, title; do { link = extractLink( text, lastIndex ); if ( link !== null ) { lastIndex = link.end; title = getCanonicalTitle( link.title ); }		} while ( link !== null			&& ( !link.possiblyAmbiguous || $.inArray( title, destinations ) === -1 ) ); return link; };	/*	 * Find the "target" page: either the one we are in or the "main" one found by extracting * the title from ".* (disambiguation)" or whatever the appropiate local format is	 */ var getTargetPage = function { var title = mw.config.get( 'wgTitle' ); return forceSamePage ? title : removeDisam(title); };	/*	 * Extract a "main" title from ".* (disambiguation)" or whatever the appropiate local format is	 */ var removeDisam = function( title ) { var match = new RegExp( cfg.disamRegExp ).exec( title ); return match ? match[1] : title; };	/*	 * Check whether two page titles are the same */	var isSamePage = function( title1, title2 ) { return getCanonicalTitle( title1 ) === getCanonicalTitle( title2 ); };	/*	 * Return the 'canonical title' of a page */	var getCanonicalTitle = function( title ) { try { title = new mw.Title( title ).getPrefixedText; } catch ( ex ) { // mw.Title seems to be buggy, and some valid titles are rejected // FIXME: This may cause false negatives }		return title; };	/*	 * Extract the context around a given link in a text string */	var extractContext = function( text, link ) { var contextStart = link.start - cfg.radius; var contextEnd = link.end + cfg.radius; var contextPrev = text.substring( contextStart, link.start ); if ( contextStart > 0 ) { contextPrev = txt.ellipsis + contextPrev; }		var contextNext = text.substring( link.end, contextEnd ); if ( contextEnd < text.length ) { contextNext = contextNext + txt.ellipsis; }		return [contextPrev, text.substring( link.start, link.end ), contextNext]; };	/*	 * Extract the prefixed page name from a link */	var extractPageName = function( link ) { var pageName = extractPageNameRaw( link ); if ( pageName ) { var sectionPos = pageName.indexOf( '#' ); var section = ''; if ( sectionPos !== -1 ) { section = pageName.substring( sectionPos ); pageName = pageName.substring( 0, sectionPos ); }			return getCanonicalTitle( pageName ) + section; } else { return null; }	};	/*	 * Extract the page name from a link, as is	 */ var extractPageNameRaw = function( link ) { if ( !link.hasClass( 'image' ) ) { var href = link.attr( 'href' ); if ( link.hasClass( 'new' ) ) { // "Red" link if ( href.indexOf( mw.config.get( 'wgScript' ) ) === 0 ) { return mw.util.getParamValue( 'title', href ); }			} else { var regex = mw.config.get( 'wgArticlePath' ).replace( '$1', '(.*)' ); var regexResult = RegExp( '^' + regex + '$' ).exec( href ); if ( $.isArray( regexResult ) && regexResult.length > 1 ) { return decodeURIComponent( regexResult[1] ); }			}		}		return null; };	/*	 * Check whether this is a disambiguation page */	var isDisam = function { var categories = mw.config.get( 'wgCategories' ); for ( var ii = 0; ii < categories.length; ii++ ) { if ( $.inArray( categories[ii], cfg.disamCategories ) !== -1 ) { return true; }		}		return false; };	var secondsToHHMMSS = function( totalSeconds ) { var hhmmss = ''; var hours = Math.floor( totalSeconds / 3600 ); var minutes = Math.floor( totalSeconds % 3600 / 60 ); var seconds = Math.floor( totalSeconds % 3600 % 60 ); if ( hours >= 1 ) { hhmmss = pad( hours, '0', 2 ) + ':'; }		hhmmss += pad( minutes, '0', 2 ) + ':' + pad( seconds, '0', 2 ); return hhmmss; };	var pad = function( str, z, width ) { str = str.toString; if ( str.length >= width ) { return str; } else { return new Array( width - str.length + 1 ).join( z ) + str; }	}	/*	 * Create a new button * text: Text that will be displayed on the button * onClick: Function that will be called when the button is clicked */	var createButton = function( text, onClick ) { var button = $( ' ', {'type': 'button', 'value': text } ); button.addClass( 'disamassist-button' ).click( onClick ); return button; };	/*	 * Given a page title and an array of possible redirects {from, to} ("canonical titles"), find the page * at the end of the redirect chain, if there is one. Otherwise, return the page title that was passed */	var resolveRedirect = function( pageTitle, possibleRedirects ) { var appliedRedirect = true; var visitedPages = {}; var currentPage = getCanonicalTitle( pageTitle ); while ( appliedRedirect ) { appliedRedirect = false; for ( var ii = 0; ii < possibleRedirects.length; ii++ ) { if ( possibleRedirects[ii].from === currentPage ) { if ( visitedPages[possibleRedirects[ii].to] ) { // Redirect chain detected return pageTitle; }					visitedPages[currentPage] = true; appliedRedirect = true; currentPage = possibleRedirects[ii].to; }			}		}		// No redirect rules applied for an iteration of the outer loop: // no more redirects. We are done return currentPage; };	/*	 * Fetch the incoming links to a page. Returns a jQuery promise * (success - array of titles of pages that contain links to the target page and	 * array of "canonical titles" of possible destinations of the backlinks (either * the target page or redirects to the target page), failure - error description) * page: Target page */	var getBacklinks = function( page ) { var dfd = new $.Deferred; var api = new mw.Api; api.get( {			'action': 'query',			'list': 'backlinks',			'bltitle': page,			'blredirect': true,			'bllimit': cfg.backlinkLimit,			'blnamespace': cfg.targetNamespaces.join( '|' )		} ).done( function( data ) {			// There might be duplicate entries in some corner cases. We don't care,			// since we are going to check later, anyway			var backlinks = [];			var linkTitles = [getCanonicalTitle( page )];			$.each( data.query.backlinks, function { backlinks.push( this.title ); if ( this.redirlinks ) { linkTitles.push( this.title ); $.each( this.redirlinks, function {						backlinks.push( this.title );					} ); }			} );			dfd.resolve( backlinks, linkTitles );		} ).fail( function( code, data ) {			dfd.reject( txt.getBacklinksError.replace( '$1', code ) );		} ); return dfd.promise; };	/*	 * Download a list of redirects for some pages. Returns a jQuery callback (success -	 * array of redirects ({from, to}), failure - error description ) * pageTitles: Array of page titles */	var fetchRedirects = function( pageTitles ) { var dfd = new $.Deferred; var api = new mw.Api; var currentTitles = pageTitles.slice( 0, cfg.queryTitleLimit ); var restTitles = pageTitles.slice( cfg.queryTitleLimit ); api.get( {			action: 'query',			titles: currentTitles.join( '|' ),			redirects: true		} ).done( function( data ) {			var theseRedirects = data.query.redirects ? data.query.redirects : [];			if ( restTitles.length !== 0 ) {				fetchRedirects( restTitles ).done( function( redirects ) { dfd.resolve( theseRedirects.concat( redirects ) ); } ).fail( function( description ) { dfd.reject( description ); } );			} else {				dfd.resolve( theseRedirects );			}		} ).fail( function( code, data ) {			dfd.reject( txt.fetchRedirectsError.replace( '$1', code ) );		} ); return dfd.promise; };	/*	 * Download the list of user rights for the current user. Returns a	 * jQuery promise (success - array of right names, error - error description) */	var fetchRights = function { var dfd = $.Deferred; var api = new mw.Api; api.get( {			action: 'query',			meta: 'userinfo',			uiprop: 'rights'		} ).done( function( data ) {			dfd.resolve( data.query.userinfo.rights );		} ).fail( function( code, data ) {			dfd.reject( txt.fetchRightsError.replace( '$1', code ) );		} ); return dfd.promise; };	/*	 * Load the raw page text for a given title. Returns a jQuery promise (success - page	 * content, failure - error description) * pageTitle: Title of the page */	var loadPage = function( pageTitle ) { var dfd = new $.Deferred; var api = new mw.Api; api.get( {			action: 'query',			titles: pageTitle,			intoken: 'edit',			prop: 'info|revisions',			rvprop: 'timestamp|content'		} ).done( function( data ) {			var pages = data.query.pages;			for ( var key in pages ) {				if ( pages.hasOwnProperty( key ) ) {					break;				}			}			var rawPage = data.query.pages[key];			var page = {};			page.redirect = rawPage.redirect !== undefined;			page.missing = rawPage.missing !== undefined;			if ( rawPage.revisions ) {				page.content = rawPage.revisions[0]['*'];				page.baseTimeStamp = rawPage.revisions[0].timestamp;			} else {				page.content = '';				page.baseTimeStamp = null;			}			page.startTimeStamp = rawPage.starttimestamp;			page.editToken = rawPage.edittoken;			dfd.resolve( page );		} ).fail( function( code, data ) {			dfd.reject( txt.loadPageError.replace( '$1', pageTitle ).replace( '$2', code ) );		} ); return dfd.promise; };	/*	 * Register changes to a page, to be saved later. Returns a jQuery promise * (success - no params, failure - error description). Takes the same parameters * as savePage */	var saveWithCooldown = function { var deferred = new $.Deferred; pendingSaves.push( {args: arguments, dfd: deferred} ); if ( !runningSaves ) { checkAndSave; }		return deferred.promise; };	/*	 * Save the first set of changes in the list of pending changes, providing that * enough time has passed since the last edit */	var checkAndSave = function { if ( pendingSaves.length === 0 ) { runningSaves = false; return; }		runningSaves = true; var millisSinceLast = new Date.getTime - lastEditMillis; if ( millisSinceLast < cfg.editCooldown * 1000 ) { setTimeout( checkAndSave, cfg.editCooldown * 1000 - millisSinceLast ); } else { // The last edit started at least cfg.editCooldown seconds ago var save = pendingSaves.shift; savePage.apply( this, save.args ).done( function {				checkAndSave;				save.dfd.resolve;			} ).fail( function( description ) {				checkAndSave;				save.dfd.reject( description );			} ); // We'll use the time since the last edit started lastEditMillis = new Date.getTime; }	};	/*	 * Save the changes made to a page. Returns a jQuery promise (success - no params,	 * failure - error description) * pageTitle: Title of the page * page: Page data * summary: Summary of the changes made to the page * minorEdit: Whether to mark the edit as 'minor' * botEdit: Whether to mark the edit as 'bot' */	var savePage = function( pageTitle, page, summary, minorEdit, botEdit ) { var dfd = new $.Deferred; var api = new mw.Api; api.post( {			action: 'edit',			title: pageTitle,			token: page.editToken,			text: page.content,			basetimestamp: page.baseTimeStamp,			starttimestamp: page.startTimeStamp,			summary: summary,			watchlist: cfg.watch,			minor: minorEdit,			bot: botEdit		} ).done( function {			dfd.resolve;		} ).fail( function( code, data ) {			dfd.reject( txt.savePageError.replace( '$1', pageTitle ).replace( '$2', code ) );		} ); return dfd.promise; };	install; } )( mediaWiki, jQuery );

var xpagehistory = { loadinganimation : 0, execute : function { if (mw.config.get('wgArticleId') === 0) return; // no deleted articles, no special pages if (mw.config.get('wgCurRevisionId') != mw.config.get('wgRevisionId')) return; // only current revision $(" .  ").insertBefore("#contentSub"); loadinganimation = window.setInterval( function { if ($("#xtoolsloading").html == ". ") $("#xtoolsloading").html(" . "); else if ($("#xtoolsloading").html == " . ") $("#xtoolsloading").html("  ."); else $("#xtoolsloading").html(".  "); }, 300); mw.loader.load("//tools.wmflabs.org/xtools/api.php?pageid=" + mw.config.get('wgArticleId') + "&db=" + mw.config.get('wgDBname') + "&nsid=" + mw.config.get('wgNamespaceNumber') + "&pagetitle=" + mw.config.get('wgPageName') + "&wditemid=" + mw.config.get('wgWikibaseItemId') + "&uselang=" + mw.config.get('wgContentLanguage') ); }, resultloaded : function( res ) { $("#xtoolsresult").html(res); this.stoploading; }, stoploading : function { clearInterval(loadinganimation); $('#xtoolsloading').remove; } } if ( (mw.config.get('wgAction') == "view") ) $( xpagehistory.execute ); /* */