Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* CSS rules for use with [[User:Anomie/linkclassifier.js]]. To include these rules,
 * insert the following into your [[Special:Mypage/monobook.js]] (yes, monobook.js not monobook.css):

importStylesheet('User:Anomie/linkclassifier.css'); // Linkback: [[User:Anomie/linkclassifier.css]]

 * Please keep the linkback comment so I can know who is using this.
 */
A                  { color:#0033cc; }
A.stubcls          { color:#6000bf; }
A.new              { color:#bf0000; }
A.redirect         { color:#00bf00; }
A.self-redirect    { background-color:#88ff88; }
A.disambiguation   { background-color:#ffff88; }
A.intentional-disambiguation   { background-color:#ffffcc; }
A.soft-redirect-cats { background-color:#ffff88; }
A.deletion         { color:#ff0088; }
A.broken-redirect  { color:#bf0000; background-color:#88ff88; }
#bodyContent a.external, #bodyContent a.extiw { color:#338ebb; }

A:visited          { color:#4365cc; }
A.stubcls:visited  { color:#7f3ebf; }
A.new:visited      { color:#bf3e3e; }
A.redirect:visited { color:#3ebf3e; }
A.deletion:visited { color:#ff54af; }
A.broken-redirect:visited { color:#bf3e3e; background-color:#88ff88; }
#bodyContent a.external:visited, #bodyContent a.extiw:visited { color:#5f9cbb; }

A.unprintworthy:not(.unprintworthy-shortcut)    { outline:1px dotted #ff0000; }

A.image.redirect         > IMG { outline:2px solid #00bf00; } /* Doesn't work, because the generated link goes to the redirect target */
A.image.nonfree-media    > IMG { outline:3px double #ff0000; }
A.image.deletion         > IMG { outline:2px solid #ff0088; }
A.image.deletion:visited > IMG { outline-color:#ff54af; }

/* Images can be inserted after links with the ":after" pseudo-element, although this doesn't work in IE <8. */
/*
A.featured-content:after {
    content:url(//up.wiki.x.io/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/12px-Featured_article_star.svg.png);
}
A.protection-edit-sysop-indef:after {
    content:url(//up.wiki.x.io/wikipedia/commons/thumb/4/48/Padlock-red.svg/12px-Padlock-red.svg.png);
}
*/
A.nonimage.protection-create-sysop-indef:after {
    content:url(//up.wiki.x.io/wikipedia/commons/thumb/a/a0/Padlock-skyblue.svg/12px-Padlock-skyblue.svg.png);
}

/* If you're wanting to put multiple images after, you'll need to give specific
 * rules for each combination, something like this:
A.featured-content.protection-edit-sysop-indef:after {
    content:url(//up.wiki.x.io/wikipedia/commons/thumb/b/bc/Featured_article_star.svg/12px-Featured_article_star.svg.png) url(//up.wiki.x.io/wikipedia/commons/thumb/4/48/Padlock-red.svg/12px-Padlock-red.svg.png);
}
*/

/* If you want to use this script, simply add the following line to your monobook.js:
 
importScript('User:Anomie/ajaxpreview.js'); // Backlink: [[User:Anomie/ajaxpreview.js]]
 
* (Please keep the comment so I can see how many people use this).
*/

var AJAXPreview={
    node:null,
    txt:null,
    timer:null,
    idx:0,

    spinner:function(){
        switch(AJAXPreview.idx++){
          case 0:
            AJAXPreview.node.innerHTML='<center style="font-size:50pt">|</center>';
            break;
          case 1:
            AJAXPreview.node.innerHTML='<center style="font-size:50pt">/</center>';
            break;
          case 2:
            AJAXPreview.node.innerHTML='<center style="font-size:50pt">–</center>';
            break;
          case 3:
            AJAXPreview.node.innerHTML='<center style="font-size:50pt">\\</center>';
            AJAXPreview.idx=0;
            break;
        }
        AJAXPreview.node.style.display='block';
    },

    callback:function(r){
        if(AJAXPreview.timer) window.clearInterval(AJAXPreview.timer);
        AJAXPreview.timer=null;
        if(!r.parse || !r.parse.text || !r.parse.text['*']){
            AJAXPreview.node.innerHTML='<div style="border:1px solid #f00;background-color:#fcc;color:#f00;text-align:center">Bad response</div>';
            throw new Error('Bad response');
        }
        AJAXPreview.node.innerHTML=r.parse.text['*']+'<br />';
        AJAXPreview.node.style.display='block';

        // Set a timeout to allow the browser a chance to parse the innerHTML
        window.setTimeout(function(){
        	mw.hook( 'wikipage.content' ).fire( $( AJAXPreview.node ) );
            for(var i=AJAXPreview.$OnLoadHooks.length-1; i>=0; i--)
                AJAXPreview.$OnLoadHooks[i].call(window, AJAXPreview.node);
        }, 250);
    },

    doError:function(xhr,textStatus,errorThrown){
        if(AJAXPreview.timer) window.clearInterval(AJAXPreview.timer);
        AJAXPreview.timer=null;
        while(AJAXPreview.node.firstChild) AJAXPreview.node.removeChild(AJAXPreview.node.firstChild);
        var d=document.createElement('DIV');
        d.style.border='1px solid #f00';
        d.style.backgroundColor='#fcc';
        d.style.color='#f00';
        d.style.textAlign='center';
        d.appendChild(document.createTextNode('AJAX Error: '+textStatus+' '+errorThrown));
        AJAXPreview.node.appendChild(d);
        AJAXPreview.node.style.display='block';
        throw new Error('AJAX error: '+textStatus+' '+errorThrown);
    },

    doPreview:function(ev){
        if(!ev) ev=window.event;
        var txt=AJAXPreview.getTextContent();
        var refs=AJAXPreview.getRefs(txt);
        var need=[];
        var groups={};
        for(var g in refs){
            groups[g]='';
            for(var n in refs[g]){
                if(refs[g][n].text===null) need.push([g,n]);
            }
        }
        var doPreview2=function(wikitext,sts,xhr){
            if(wikitext){
                refs=AJAXPreview.getRefs(wikitext);
                for(var i=need.length-1; i>=0; i--){
                    var x=refs[need[i][0]][need[i][1]];
                    if(!x) continue;
                    if(x.type=='tag'){
                        groups[need[i][0]]+='\x7b\x7b#tag:ref|'+x.text+'|name='+need[i][1]+'|group='+need[i][0]+'\x7d\x7d';
                    } else {
                        groups[need[i][0]]+='\x3cref name="'+need[i][1]+'" group="'+need[i][0]+'"\x3e'+x.text+'\x3c/ref\x3e';
                    }
                }
            }
            txt+='\n\n\x7b\x7b-\x7d\x7d\n----\n';
            for(var g in groups){
                txt+='\n;'+(g?'Group '+g:'References')+'\n\x7b\x7breflist|2|group='+g+'|refs='+groups[g]+'\x7d\x7d';
            }

            jQuery.ajax({
                url:mw.util.wikiScript('api'),
                dataType:'json',
                type:'POST',
                data:{
                    format:'json',
                    action:'parse',
                    pst:1,
                    text:txt,
                    title:mw.config.get('wgPageName'),
                    prop:'text',
                    disableeditsection:1,
                    preview:1,
                    templatesandboxtitle:mw.config.get('wgPageName'),
                    templatesandboxtext:txt
                },
                success:AJAXPreview.callback,
                error:AJAXPreview.doError
            });
        };
        mw.loader.using('mediawiki.util', function(){
            if(need.length>0){
                jQuery.ajax({
                    url:mw.util.wikiScript('index'),
                    dataType:'text',
                    type:'GET',
                    data:{ action:'raw', title:mw.config.get('wgPageName') },
                    success:doPreview2,
                    error:AJAXPreview.doError
                });
            } else {
                doPreview2(null,null,null);
            }
        });

        var x=document.getElementById('wikiDiff');
        if(x) x.parentNode.removeChild(x);
        if(AJAXPreview.timer) window.clearInterval(AJAXPreview.timer);
        AJAXPreview.timer=window.setInterval(AJAXPreview.spinner, 250);

        this.blur();
        window.scrollTo(0,0);
        if(ev){ // OOUI may not have an event here
            if(ev.preventDefault) ev.preventDefault();
            if(ev.stopPropagation) ev.stopPropagation();
            ev.returnValue=false;
            ev.cancelBubble=true;
        }
        return false;
    },

    getRefs:function(txt){
        var g;

        var refs={};

        // The new "list-defined references" have to be handled specially,
        // which means we have to manage to pull them out of the wikitext. Fun.
        // First, do the XML-style tags.
        txt=txt.replace(/<references((?:\s+[^>]*[^\/>])?)(?:\/>|>((?:.|[\r\n])*?)(<\/references>|$))/ig, function(x,p,t,c){
            p=p.replace(/\s+$/g,'');
            g=p.match(/\sgroup="([^\x22]*)"/i);
            if(!g) g=p.match(/\sgroup='([^\x27]*)'/i);
            if(!g) g=p.match(/\sgroup=(\S*)/i);
            g=g?g[1]:'';
            refs=AJAXPreview.getRefs2(t,g,refs);
            return '';
        });

        // Next, to reflist and #tag:references
        txt=AJAXPreview.process_templates(txt,function(n,p,o){
            var c=null, g='';
            if(n=='Reflist'){
                for(var j=0; j<p.length; j++){
                    var m=p[j].match(/^\s*refs\s*=\s*((?:.|[\r\n])*?)\s*$/);
                    if(m) c=m[1];
                    var m=p[j].match(/^\s*group\s*=\s*([\x22\x27]?)([^\x22\x27]+?)\1\s*$/);
                    if(m) g=m[2];
                }
                if(c===null) c='';
            } else if(/^#tag:\s*references$/i.test(n)){
                c=p.length ? p.shift() : '';
                for(var j=0; j<p.length; j++){
                    var m=p[j].match(/^\s*group\s*=\s*([\x22\x27]?)([^\x22\x27]+?)\1\s*$/);
                    if(m) g=m[2];
                }
            } else {
                return null;
            }
            refs=AJAXPreview.getRefs2(c,g,refs);
            return '';
        });

        return AJAXPreview.getRefs2(txt,'',refs);
    },
    getRefs2:function(txt,defgroup,refs){
        var g,n;

        // First, pull out regular <refs>. We can do this with a regex.
        txt.replace(/<ref((?:\s+\S+=(?:"[^\x22]*"|'[^\x27]*'|\S*?))*)\s*(?:\/>|>((?:.|[\r\n])*?)<\/ref>)/ig, function(x,p,t){
            g=p.match(/\sgroup="([^\x22]*)"/i);
            if(!g) g=p.match(/\sgroup='([^\x27]*)'/i);
            if(!g) g=p.match(/\sgroup=(\S*)/i);
            g=g?g[1]:defgroup;
            if(typeof(refs[g])=='undefined') refs[g]={};
            n=p.match(/\sname="([^\x22]*)"/i);
            if(!n) n=p.match(/\sname='([^\x27]*)'/i);
            if(!n) n=p.match(/\sname=(\S*)/i);
            if(!n) return null;
            n=n[1];
            if(typeof(refs[g][n])=='undefined') refs[g][n]={text:null,type:'?'};
            if(refs[g][n].text===null && typeof(t)!='undefined' && t!=='' && t!==null){
                refs[g][n].text=t;
                refs[g][n].type='ref'
            }
            return null;
        });

        // Second, if it looks like there are #tag refs, parse them too
        AJAXPreview.process_templates(txt,function(nm,p,o){
            if(!/^#tag:\s*ref$/i.test(nm)) return null;
            g=defgroup; n=null;
            for(var j=p.length-1; j>=1; j--){
                var m=p[j].match(/^\s*group\s*=\s*([\x22\x27]?)([^\x22\x27]+?)\1\s*$/);
                if(m) g=m[2];
                var m=p[j].match(/^\s*name\s*=\s*([\x22\x27]?)([^\x22\x27]+?)\1\s*$/);
                if(m) n=m[2];
            }
            if(typeof(refs[g])=='undefined') refs[g]={};
            if(n!==null){
                if(typeof(refs[g][n])=='undefined') refs[g][n]={text:null,type:'?'};
                if(refs[g][n].text===null && p[0]!==''){
                    refs[g][n].text=p[0];
                    refs[g][n].type='tag'
                }
            }
            return null;
        });

        return refs;
    },

    process_templates:function(txt,cb,data){
        var stack=[], i=0;
        while(i<txt.length){
            var x=stack.length?stack[stack.length-1]:null;
            var xb=null;
            for(var j=0; j<stack.length; j++){
                if(stack[j].char=='\x5b') xb=stack[j];
            }
            if(txt.substr(i,2)=='\x7b\x7b'){
                var ct;
                for(ct=2; txt.substr(i+ct,1)=='\x7b'; ct++);
                stack.push({char:'\x7b',start:i,count:ct,pstart:i+ct,params:[]});
                i+=ct;
            } else if(txt.substr(i,2)=='\x5b\x5b'){
                var ct;
                for(ct=2; txt.substr(i+ct,1)=='\x5b'; ct++);
                stack.push({char:'\x5b',start:i,count:ct,pstart:i+ct,params:[]});
                i+=ct;
            } else if(x && x.char=='\x7b' && txt.substr(i,2)=='\x7d\x7d'){
                var ct;
                for(ct=2; txt.substr(i+ct,1)=='\x7d'; ct++);
                if(ct>x.count) ct=x.count;
                i+=ct;
                x.params.push(txt.substring(x.pstart,i-ct));
                // First, parse out variables
                while(ct>=3){
                    x.count-=3;
                    ct-=3;
                    var s=x.start-x.count;
                    x.params=[txt.substring(s,i-x.count)];
                }
                // Any left is templates
                while(ct>=2){
                    x.count-=2;
                    ct-=2;
                    var s=x.start+x.count;
                    var orig=txt.substring(s,i-ct);
                    var name=x.params.shift();
                    var oname=name;
                    name=name.replace(/_/g,' ');
                    name=name.replace(/^\s+|\s+$/g,'');
                    name=name.replace(/  +/g,' ');
                    name=name.replace(/^Template\s*:\s*/ig,'');
                    name=name.substr(0,1).toUpperCase()+name.substr(1);
                    var ret=cb(name, x.params, orig, data, oname);
                    if(ret===null){
                        x.params=[orig];
                    } else {
                        ret=""+ret;
                        var d=(ret=='' && (s==0 || txt.substr(s-1,1)=='\n') && txt.substr(i-ct,1)=='\n')?1:0;
                        txt=txt.substr(0,s)+ret+txt.substr(i-ct+d);
                        i=s+ret.length+ct;
                        x.params=[ret];
                    }
                }
                if(x.count<2){
                    stack.pop();
                } else {
                    // The one we just completed might not be the end of the
                    // param, so reset the param array and pstart
                    x.params=[];
                    x.pstart=x.start+x.count;
                }
            } else if(xb && txt.substr(i,2)=='\x5d\x5d'){
                // Drop any pending templates, they're not really templates
                while(stack[stack.length-1]!=xb) stack.pop();
                var ct;
                for(ct=2; txt.substr(i+ct,1)=='\x5d'; ct++);
                if(ct>xb.count) ct=xb.count;
                i+=ct;
                xb.count-=ct;
                if(xb.count<2){
                    stack.pop();
                } else {
                    // The one we just completed might not be the end of the
                    // param, so reset the param array and pstart
                    xb.params=[];
                    xb.pstart=xb.start+xb.count;
                }
            } else if(x && txt.substr(i,1)=='|'){
                x.params.push(txt.substring(x.pstart,i));
                x.pstart=++i;
            } else {
                i++;
            }
        }
        return txt;
    },
 
    onLoad:function(){
        var action=mw.config.get('wgAction');
        if(action!='edit' && action!='submit') return;

        var editForm=document.getElementById('editform');
        if(!editForm) return;
        var sectionField = editForm.elements['wpSection'];
        var isSection=(sectionField && sectionField.value!="");
        var p=editForm.elements["wpPreview"];
        if(!p) return;

        AJAXPreview.node=document.getElementById('wikiPreview');
        if(!AJAXPreview.node) return;
        AJAXPreview.txt=editForm.elements["wpTextbox1"];
        if(!AJAXPreview.txt) return;

    	mw.loader.using( [ 'oojs-ui-core' ] ).done( function () {
            var b = new OO.ui.ButtonWidget( {
	            label: 'Ajax Preview'+(isSection?' w/Refs':''),
    	        tabIndex: p.tabIndex
        	} );
        	b.on( 'click', AJAXPreview.doPreview );
        	$( p ).before( b.$element, ' ' );
    	} );
        p.value='Preview';

        // Hooks for standard functions
        if(typeof(window.createCollapseButtons) == 'function')
            AJAXPreview.AddOnLoadHook(createCollapseButtons);
        if(typeof(window.createNavigationBarToggleButton) == 'function')
            AJAXPreview.AddOnLoadHook(createNavigationBarToggleButton);
    },

    getTextContent:function(){
        return AJAXPreview.txt.value;
    },

    // Add callback functions here.
    AddOnLoadHook:function(f){
        AJAXPreview.$OnLoadHooks.push(f);
    },
    $OnLoadHooks:[]
};

$(document).ready(AJAXPreview.onLoad);

importScript('Wikipedia:AutoEd/complete.js'); // Backlink: [[Wikipedia:AutoEd/complete.js]]