User:Anomie/ajaxpreview.js

/* If you want to use this script, simply add the following line to your monobook.js: importScript('User:Anomie/ajaxpreview.js'); // Linkback: [[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);