Cleachdaiche:PiRSquared17/monobook.js/GT

O Uicipeid

// does google toolbar like translation of words and selected text // works on ie and firefox, epiphany, safari, chrome, and opera but still has bugs on // Konqueror (in fact the code is disabled on Konqueror) // selected text translation (up to 500 characters) only on IE, Firefox, and Epiphany, and chrome

// this has only been tested on the monobook skin


// default from and to language // the languages must be the same as in google.language.Languages array. // these are normally the international defaults but note: // portuguese: pt-PT // traditional chinese: zh-TW // simplified chinese: zh-CN // // must be set in this routine for dictionarylookup.js /* var SCLanguageDefaultTo = 'en'; var SCLanguageDefaultFrom='ru';

  • /

var SCLanguageTo='en'; var SCLanguageDefaultTo = SCLanguageTo; var SCLanguageDefaultFrom='ru';

if(typeof wgUserLanguage != "undefined" &&

  typeof wgContentLanguage!= "undefined")

{

  if(wgUserLanguage != wgContentLanguage)
     SCLanguageDefaultTo = wgUserLanguage;

}


var SCShiftKeyNeeded = true;

// need to set document.domain here, each language should set this field here

document.domain = wgServer.substring( wgServer.lastIndexOf("//")+2);



// default literals for system // must be set in this routine for dictionarylookup.js // the gadget manager can change these to the language he wishes var SCstrLanguage = "Language"; var SCstrSource = "Source"; var SCstrGoogle = "Google"; var SCstrCloseWindow = "Close window"; var SCstrSelectLanguage = "Select language (to)"; var SCstrWikipedialanguage = "Wikipedia language"; var SCstrDictionary = "Dictionary"; var SCstrWikipedia = "Wikipedia"; var SCstrPopupHelp = "GoogleTrans help?"; var SCstrTurnOffPopups = "Turn GoogleTrans off?"; var SCstrTurnOnPopups = "Turn GoogleTrans on?"; var SCstrGoogleTrans ="GoogleTrans"; var SCstrOff = " (off)"; var SCstrOn = " (on)"; var SCstrChangeOptions = "Change options for GoogleTrans"; var SCstrTranslatePage = "Google: translate page?"; var SCstrHelpUrl = "http://en.wikipedia.org/w/index.php?title=User:Endo999/dictionarylookuphelp.html&ctype=text/html";

var SCstrSingleWord = "Translation of single words"; var SCstrSelectedText = "Translation of selected text (> 500 characters)"; var SCstrKonqueror = "This feature is not supported on Konqueror"; var SCstrTextTooLarge = "Text element too large to parse!"; var SCstrGuessLanguage = "Any language"; var SCstrShiftKeyNeeded = "Shift key down to bring up Popup? Turn Option "; var SCbNoShiftKeyBrowser = false;

var SCPopupBackgroundColor = "beige"; // you can set the popup background color here



appendCSS( '.SCuserData { behavior:url(#default#userdata);} ' +

'#SCitem div small, #SCitem small,#SCitem div div small,#SCitem div div div small {font-size:65%;color:black}' +

'#SCitem div a:hover, #SCitem a:hover, #SCitem div div a:hover {text-decoration:underline;}' +

'.SCPopup {background-color:' + SCPopupBackgroundColor +';border: 1px solid blue;position:absolute;font-size:14pt;z-index:9999;' +'overflow:visible;line-height:normal;padding:.5em;display:block;}' +

'.SCxWindow {font-size:70%;color:black;' +

'position:absolute;right:.5em;}'+

'.SCyWindow{font-size:50%;color:black;position:absolute;right:.5em;'+ 'bottom:.5em}' + '.SCxTranslation {font-size:70%;color:black;' + 'position:absolute;left:.5em;top;.5em;}' + '.SCxText {color:black;}'+

'.SCPopupIE {position:absolute;background-color: ' +

SCPopupBackgroundColor +

';border: 1px solid blue;font-size:14pt;z-index:9999; ' + 'overflow:visible;display:block;line-height:normal;padding:.5em;width:auto;}' + '.SChidestuff {display:none;}'

);

function SCGoogleLanguageLoaded() {

 SCMakeGoogleLanguages();

}

function SCloadGoogleLanguage() {

 google.load("language", "1", {"callback" : SCGoogleLanguageLoaded});

}

// this api key is not strictly necessary, but Google maintains they will email // you if, for some reason, they withdraw the Google translation services // the gadget manager is welcome to log onto code.google.com and get his own // api key.

var SCGoogleAPIKey = "ABQIAAAAIL6lIdsFmMh3QB6iZZYqHBT5zsu4IrEqi6aTPGb6EkJ6C2zK0BQyWgLmv16JfxNy3RYUKg7GyR6XAg";

mw.loader.load("http://www.google.com/jsapi?callback=SCloadGoogleLanguage&key=" +

  SCGoogleAPIKey);







addOnloadHook(

   function () {
       var span1 = document.createElement('DIV');
       span1.setAttribute('id','SCitem');
       var thebody = document.getElementsByTagName('BODY');
       thebody[0].appendChild(span1);

       var span2 = document.createElement('SPAN');
       span2.setAttribute('id','SCPersistElement');
       span2.setAttribute('class','SCuserData');
       thebody[0].appendChild(span2);

   }

); var SCnewoptions; function SCMakeGoogleLanguages() {

var l;
var lcode;
var newoptions = "";
var bLangNotSupported = true;
var b2digitsLangNotSupported = true;
var language2digits = "";

for (l in google.language.Languages)
{
  lcode = google.language.Languages[l];
  if(google.language.isTranslatable(lcode))
  {
    if(!l.match(/UNKNOWN/i)) 
     newoptions += 
  '<option value="' + lcode + '">' + (l.substring(0,1).toUpperCase() + l.substring(1).toLowerCase()) + '</option>'+"\n";

  if(lcode.toUpperCase() == SCLanguageDefaultTo.toUpperCase())
     bLangNotSupported = false;
  var twodigitlang = "";
  if(lcode.match(/^(..)/))
     twodigitlang = RegExp.$1;
  if(twodigitlang.length == 2 && SCLanguageDefaultTo.match("^" + twodigitlang,"i"))
  {
     b2digitsLangNotSupported = false;
     language2digits = lcode;
  }

  }
}

SCnewoptions = newoptions; 
if(bLangNotSupported)
{
  var olddefaultlang = SCLanguageDefaultTo;
  var bSameAsContent = false;
  if(typeof wgContentLanguage != "undefined" && wgContentLanguage == language2digits)
     bSameAsContent = true;
  if(!b2digitsLangNotSupported && !bSameAsContent)
  {
     SCLanguageDefaultTo = language2digits;
  }
  else
   SCLanguageDefaultTo = SCLanguageTo;
  if(olddefaultlang == SCtranslateTo)
    SCtranslateTo = SCLanguageDefaultTo;

}

}

// contents of dictionarylookup1.js follow // this javascript isolates the text word under the //cursor when it rests on a word for 1.5 seconds // works in Firefox 1,2.0,3 and IE 6.0,7,8, chrome // works in Windows Safari, and Opera // does not work in Konqueror(that I know of, code is disabled for Konqueror) // with the Google language javascript api this does dictionary lookup // in several languages. // the word isolated is translated // and the translation is placed back in the new window // on IE, Firefox, chrome, and Epiphany a cursor place over // selected text (up to 500 characters) will use // that text

// author: endo999 // author: Paul Cheffers // email: paul@securecottage.com // 2008 // // this webpage is placed in the public domain by the author


// the default languages(the from language is redundant as Google guesses // the language) but SCLanguageDefaultTo is important is you are porting // to another Wikipedia Language


// set this in main gadget file // restrictive environments don't like wikipedia.org to set cookies with //document.domain = "wikipedia.org"


var SCSourcestrlength = SCstrSource.length + SCstrGoogle + 2; var SCMinLength = 22;

var SCgooglereference1 = //'' + ''+ SCstrSource +': <a href="#" ' + 'onMouseover="javascript:SCdonthide=true;" ' + 'onclick="javascript:window.open(\; var SCgooglereference1a = '\')">' + SCstrGoogle + '</a> ';

var SCgooglereference2 = '<a class="SCxWindow" href="javascript:SCdonthide=false;SChidespan(\;

var SCgooglereference3 = '\')">X</a>';



var SClanguageprompt1 = '<a ' + 'href="javascript:SCmakevisiblelanguagechange()" ' + 'onMouseover="javascript:SCdonthide=true;" ' + '>';

var SClanguageprompt2 = '</a> ';


var SClabel=""; var SCtext="";


var SCtranslate="FrenchToEnglish"; var SCtranslateFrom = SCLanguageDefaultFrom; var SCtranslateTo = SCLanguageDefaultTo; var SCTooManyTextElements=450;

var SCdonthide = false; // for change of language var SCwindows = 0; // number of yellow windows var SCMaxwindows = 1; // max number of yellow windows

var SCscreenWidth = 0, SCscreenHeight = 0; var SCwrheight=250, SCwrwidth=300; var SCscrOfX = 0, SCscrOfY = 0; var SCWikihtml="", SCDicthtml=""; var SCbInsertSpan=false; var SCalttranslate=""; var SCWikipedialanguage="en.wikipedia.org"; var SCshowwrad = false; var SCbIsIE = false; var SCbIsIE8 = false; var SCbIsKonqueror = false; var SCbIsOpera = false; var SCbIsMozilla = false; var SCdebug = false; var SCclientX=0; var SCclientY=0; var SCposx = 0; // position of cursor var SCposy = 0; var SCPosYAdjust = 42; // if over link with title drop popup window a little var SCpposx = -1; // previous position of cursor var SCpposy = -1; var SCsrcElement=null; var SCbIsKonquerorEvent=false; var SCbMouseClicked=false; var SChInterval = null; //var SCMaxWordLength = 50; var SCselectedText = ""; var SCrangeCurx = 0; var SCrangeCury = 0; var SCrangeCurx1 = 0; var SCrangeCury1 = 0; var SCselectionarray = new Array(); var SCselectionstart = new Array(); var SCselectionend = new Array(); var SCselectionarrayposition = new Array(); var SCselection = new Array(); var SCMaxWordLength=495; var SCIeRange; var SCSelectionType; var SCbIsWordInSelection=false; var SCGoogleTrans=true; var SCbIsChrome=false; var SCShiftKey=false;

if(SCPersistantLoad("GoogleTrans") == "1")

  SCGoogleTrans = true;

else SCGoogleTrans = false;


if(navigator.appVersion.match(/MSIE/i)) {

 SCbIsIE = true;
 if(navigator.userAgent.indexOf('MSIE 8') != -1)
    SCbIsIE8 = true;

} var SCbIsSafari = false; var SCbIsPre4Safari = false; if(navigator.appVersion.match(/Safari/i)) {

 SCbIsSafari = true;

if(navigator.userAgent.indexOf('Version/3.')!= -1 ||

  navigator.userAgent.indexOf('Version/2.')!= -1
 )

{

    SCbIsPre4Safari = true;
    SCbNoShiftKeyBrowser = true; 

}

}

if(navigator.userAgent.indexOf('Epiphany/1.')!= -1 ||

  navigator.userAgent.indexOf('Firefox/1.') != -1
 )
 SCbNoShiftKeyBrowser = true;

if(navigator.appVersion.match(/Chrome/i)) {

 SCbIsSafari = false;
 SCbIsChrome = true;

}

if(navigator.appVersion.match(/Konqueror/i)) {

 SCbIsKonqueror = true;

} if(navigator.appName.match(/Opera/i)) {

 SCbIsOpera = true;

} if(navigator.appName.match(/Netscape/i)) {

 SCbIsMozilla = true;

}


var SCnn=(document.layers)?true:false; var SCie=(document.all)?true:false;

function SCkeyUp(e) { var evt=(e)?e:(window.event)?window.event:null; if(evt) { // var key=(evt.charCode)?evt.charCode: ((evt.keyCode)?evt.keyCode:((evt.which)?evt.which:0)); } if(!evt)

 return;

try {

if (evt.shiftKey==1)
   SCShiftKey = true;
else SCShiftKey = false;

} catch(e) {

 SCShiftKey = false;
 SCShiftKeyNeeded = false;

} } function SCkeyDown(e) { var evt=(e)?e:(window.event)?window.event:null; var key=0; if(evt) { // var key=(evt.charCode)?evt.charCode: ((evt.keyCode)?evt.keyCode:((evt.which)?evt.which:0)); } if(!evt)

 return;

try {

if (evt.shiftKey==1)
   SCShiftKey = true;
else SCShiftKey = false;

} catch(e) {

 SCShiftKey = false;
 SCShiftKeyNeeded = false;

}


if(key == 27 && SCdebug == false) // escape key {

// SCdebug = true; 
// SCdeleteEvents();

} else if(key == 27 && SCdebug == true) { // SCdebug = false; // SCcreateEvents(); } }

function SCdeleteEvents() { document.onkeydown=null;

document.onmousedown = null;

document.body.onmousemove = null;

document.body.onmouseout = null;

} function SCeventhook(hook,func) { if(document.addEventListener) document.addEventListener(hook, func, false); else document.attachEvent("on" + hook, func);

} function SCcreateEvents() { SCeventhook("keyup",SCkeyUp); SCeventhook("keydown",SCkeyDown); SCeventhook("mousedown",SCcaptureMousedown); SCeventhook("mousemove",SCcaptureMousemove); SCeventhook("mouseout",SCcaptureMouseout); }

var shiftkeyneeded = SCPersistantLoad('shiftkeyneeded'); if(shiftkeyneeded == "0")

  SCShiftKeyNeeded = false;

else SCShiftKeyNeeded = true;

if(SCbNoShiftKeyBrowser)

    SCShiftKeyNeeded = false;


var SCpersistlangFrom = SCPersistantLoad('languageFrom'); if(SCpersistlangFrom != "")

  SCtranslateFrom = SCpersistlangFrom;

var SCpersistlangTo = SCPersistantLoad('languageTo'); if(SCpersistlangTo != "")

  SCtranslateTo = SCpersistlangTo;

var SCwikilang = SCPersistantLoad('Wikipedialanguage'); if(SCwikilang != "")

  SCWikipedialanguage = SCwikilang;

else SCPersistantSave('Wikipedialanguage',SCWikipedialanguage);

SCsetInterval();

function SCsetInterval() { if(SChInterval)

  clearInterval(SChInterval);

SChInterval = null; if(SCShiftKeyNeeded)

  SChInterval = setInterval("SCinterval()",250);

else SChInterval = setInterval("SCinterval()",1000); }


var SCbIsOutsideClientArea = false;

function SCcaptureMouseout(evt) {

   SCbIsOutsideClientArea = true;
   SCbMouseClicked = false;

}

var SClastposx=0; var SClastposy=0; var SCbInSCInterval = false; var SCintervalsession=0;

function SCinterval() {

if(SCbIsKonqueror)
  return; // current dont work on Konqueror

SCintervalsession++;

try {
if(SCbInSCInterval)
   return;
SCbInSCInterval = true;


if(SCbIsMozilla)
{
  if(SCsrcElement && 
     SCsrcElement.toString().match(/HTMLHtmlElement/i))
    SCbIsOutsideClientArea = true;

}

if(SCbIsKonqueror||SCbIsOpera)
{
  if(SCsrcElement && 
     SCsrcElement.toString().match(/HTMLBodyElement/i))
    SCbIsOutsideClientArea = true;
  else SCbIsOutsideClientArea = false;
}
else
if(SCbIsIE)
{
   SCgetScrollXY();
   SCScreenSize();
   if(SCposx > SCscreenWidth+SCscrOfX-4 ||
      SCposy > SCscreenHeight+SCscrOfY-4)
      SCbIsOutsideClientArea = true;

}
var mes = document.getElementById("SCmessage");
 if(mes && SCsrcElement)
   mes.innerHTML = SCbIsOutsideClientArea + ":" +
    SCbMouseClicked + ":" +
    SCposx+","+SCposy+"::"+SCsrcElement.toString();


var posx = SCpposx - SCposx; if(posx < 0) posx = - posx; var posy = SCpposy - SCposy; if(posy < 0) posy = - posy;


if(!SCbMouseClicked && posx < 4 &&

  posy < 4 
&& (SCcurrentLink == "" || SCcurrentLink == null ||!document.getElementById(SCcurrentLink)))

{

   if(SCsrcElement &&
      !(SClastposx == SCposx
       && SClastposy == SCposy)
       && !SCbIsOutsideClientArea
     )
   {
    SCFindElementUnderMouseOver(SCsrcElement);
    if(!SCShiftKeyNeeded)
    {
      SClastposx = SCposx;
      SClastposy = SCposy;
    }

   }

} else if((SCcurrentLink != "" && SCcurrentLink != null && document.getElementById(SCcurrentLink))

     &&
      (posx > 4 || posy > 4)
     && !SCbIsOutsideClientArea
     && ((SCShiftKeyNeeded && SCintervalsession%4==0)||!SCShiftKeyNeeded)
      )

    SChidespan(SCcurrentLink);



 if(!SCdonthide && !SCbMouseClicked
     && ((SCShiftKeyNeeded && SCintervalsession%4==0)||!SCShiftKeyNeeded)
   )
 {
   SCpposx = SCposx;
   SCpposy = SCposy;
 }
 SCbInSCInterval = false;
 } catch(e) {SCbInSCInterval = false; }

}


function SCcaptureMousemove(evt) {

  SCbIsOutsideClientArea = false;

  SCFindPositionOfMouseClick(evt);
  var posx = SCposx - SCpposx;
  var posy = SCposy - SCpposy;
  if(posx < 0) posx = - posx;
  if(posy < 0) posy = - posy;

  if(!(posx < 4 && posy < 4))
    SCbMouseClicked = false;

}

// capture right mouse click

function SCcaptureMousedown(evt) { var mouseClick;


if(evt) mouseClick = evt.which; else mouseClick = window.event.button;

if(/*mouseClick == 1 &&*/ SCbIsOutsideClientArea==false) {

 SCbMouseClicked = true;

}

} //http://www.quirksmode.org/js/events_properties.html function SCFindPositionOfMouseClick(e) { SCposx = 0; SCposy = 0;

if (!e) var e = window.event; if (e.pageX || e.pageY) {


SCposx = e.pageX; SCposy = e.pageY; } else if (e.clientX || e.clientY) {

            if(1==1)
            {


               SCclientX=e.clientX;
               SCclientY=e.clientY;

SCposx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; SCposy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;

             }
             else
             {
               SCposx = window.event.x+document.body.scrollLeft;
               SCposy = window.event.y+document.body.scrollTop;
             }      

} // SCposx and SCposy contain the mouse position relative to the document


if (e.target)

       {
        SCsrcElement = e.target;
       }

else if (e.srcElement)

       {
        SCsrcElement = e.srcElement;
       }

}


// find the element under the mouse click // see http://www.quirksmode.org/js/events_properties.html function SCFindElementUnderMouseOver(targ) {

       if(!targ)
         return;
       if(!SCGoogleTrans)
         return;
       if(SCShiftKeyNeeded && SCShiftKey == false)
         return;
       if(SCwindows >= SCMaxwindows)
         return;



if (targ.nodeType == 3) // defeat Safari bug

       {

targ = targ.parentNode;

       }

       if(targ.nodeName == "TEXTAREA" ||
          targ.nodeName == "INPUT")
            return; // if input area just return

        // only scan through text elements
        var SelectionNode=null;
        var SelectionOffset=null;
        var SelectionAnchorNode = null;
        var SelectionAnchorOffset=null;

       if(SCbIsIE8 && targ.childNodes.length > SCTooManyTextElements*2)
       {
           return;
       }

        var children = targ.childNodes;
        var ntextchildren = 0;

        if(SCbIsIE && children.length > SCTooManyTextElements*2)
           return;

        for(i=0;i<children.length;i++)
        {
  		if(children.item(i).nodeType == 3)
                    ntextchildren++;
               if(ntextchildren > SCTooManyTextElements)
               {
                   window.status = SCstrTextTooLarge;
                   return;
               }
        }
        // if there are too many text elements to parse just return

        if(ntextchildren > SCTooManyTextElements)
        {
            window.status = SCstrTextTooLarge;
            return;
        }


        if(!(SCbIsIE||SCbIsOpera||SCbIsPre4Safari) && window.getSelection)
        {

var userSelection = window.getSelection();

        SCselectedText = userSelection.toString();
        SelectionNode = userSelection.focusNode;
        SelectionOffset = userSelection.focusOffset;
        SelectionAnchorNode = userSelection.anchorNode;
        SelectionAnchorOffset = userSelection.anchorOffset;

        for(i=0;i<children.length;i++)
        {
          /*
          if(userSelection.containsNode(children[i],false))
           SCselection[i] = true;
          else
          */

          if(userSelection.containsNode(children[i],true))
          {
             SCselectionarray[i] = new Array();
             SCselectionstart[i] = -1;
             SCselectionend[i] = -1;
             SCAreWordsInSelection(userSelection,SCselectionarray[i],
             children[i],i)
             SCselection[i] = false;
          }
          else 
          {
             SCselectionarray[i] = new Array();
             SCselection[i] = false;
             SCselectionstart[i] = -1;
             SCselectionend[i] = -1;
          }
        }
        }
        else if(SCbIsIE)
        {
           SCIeRange = document.selection.createRange();
           SCIeRange1 = SCIeRange.duplicate();
           SCSelectionType = document.selection.type;
        }

        /*
        var userSelection="";
        if(!SCbIsIE && window.getSelection)
        {

userSelection = window.getSelection();

        }
        */
        for(i=0;i<children.length;i++)
        {

            if(children.item(i).nodeType == 3)
            {
                var bBreak = false;

                if(SCfindwordin(targ,children.item(i),i)==true) bBreak = true;


                if(bBreak) break;


            }


        }
          if(SelectionNode && window.getSelection && window.getSelection.toString()!="")
          {
             try {
             var userSelection = window.getSelection();
             if(userSelection)
             {

               userSelection.collapse(SelectionAnchorNode,SelectionAnchorOffset);
               userSelection.extend(SelectionNode,SelectionOffset);
             }
             } catch(e) { }

          }
        //  else if(SCbIsIE)
        //     SCIeRange1.select();



}

// parent is tag, child must be text node // 1) separate child text element into // a) SCleft span of all words left of current // b) SCcurrent: span of current word // c) SCright: span of all words right of current word // 2) do binarylookup-like search of SCcurrent // span to see if it is over the cursor position //


function SCfindwordin(parent,child,childindex) {


        var debug = SCdebug;

        SCbIsWordInSelection = false;

        var words = child.nodeValue.split(/[\s\r\n]+/);
        var delimiter = " ";
        var firstcharacter = "";
        var lastcharacter = "";

        // put a limit on the size of the text element: 4000 words

        if(words.length > 4000)
        {
            window.status = SCstrTextTooLarge;
            return false;
        }


 // if a selection text item then we dont have to do 
 // the search, we can proceed from here
 // if Wikipedia then dont do
 // only check here if IE
 if(typeof SCtranslateFrom != "undefined" &&
    SCtranslateFrom != "Wikipedia")
{


 if(SCbIsIE)
 {
    SCselectedText = SCNotTooBigAndInSelection(parent,child,words);    
    if(SCselectedText != "")
    {
      SCbIsWordInSelection = true;
      SCSetInLink(SCselectedText,parent,child,mid,delimiter,words,false);
      return true;
    }
 }
 }




        if(child.nodeValue.substring(0,1).match(/[\s\r\n]/))
           firstcharacter = delimiter;
        if(child.nodeValue.match(/[\s\r\n]$/))
           lastcharacter = delimiter;

        // if language is Chinese, split every letter
        // not on whitespace, as above
        if(typeof SCtranslateFrom != "undefined"
           && ((SCtranslateFrom.match("zh","i") || SCtranslateFrom == "ja")||
              (SCtranslateFrom == "Wikipedia" &&
               (SCalttranslateFrom.match("zh","i")
               || SCalttranslateFrom == "ja"
               )))
             )
       {
           var tempnodeValue = child.nodeValue;
           tempnodeValue.replace(/[\s\r\n]+/," ");
           words = tempnodeValue.split("");
        //   words = child.nodeValue.split("");
           delimiter = "";
       }

        // no sense in searching
        if(words.length <= 0)
           return false;


        var html1="",html2="",currentword="";
        var i,j;

        var high = words.length;
        var mid = Math.floor(high/2);
        var low = 0;
        var oldmid = -1;

        var iRepetition = 0;


        while(1)
        {

           if(oldmid == mid)
              break; // have failed, don't repeat
           if(++iRepetition > 20)
              break; // no element is that large
           if(typeof words[mid] == "undefined")
              break; // have failed, don't repeat

           html1 = firstcharacter; // usually nil

           // create SCleft text string
           /*
           for(j=0;j<mid;j++)
           {
              html1 += words[j] + delimiter;
           }
           */
           if(mid > 0)
           {
              html1 += words.slice(0,mid).join(delimiter);
              html1 += delimiter;
           }

           // create SCright text string
           /*
           html2 = "";
           for(j=mid+1;j<words.length;j++)
           {
              html2 += words[j] + 
              ((j!=words.length-1)?
                 delimiter:"");
           }
           */
           if(mid+1 < words.length)
           {
             html2 = words.slice(mid+1,words.length).join(delimiter);
             if(html2 != "")
                html2 += lastcharacter;
           }

           // SCcurrent text string
           currentword = words[mid];
           if(html2 != "")
              currentword += delimiter;

           if(html2 == "" && lastcharacter != "")
              currentword += lastcharacter;

           // sometimes words[mid] is nil
           // if the case just back up one and continue
          if(currentword == delimiter) // ie, space after period
           {
              if(debug)    alert("resetting currentword");
              if(mid<high)
              {
                mid++;
                if(oldmid == mid)
                   oldmid-=2;
              }
              else break;
              continue;
           }


           var left = document.createElement('SPAN');
           left.setAttribute("id","SCleft");


           var right = document.createElement('SPAN');
           right.setAttribute("id","SCright");


           var current = document.createElement('SPAN');
           current.setAttribute("id","SCcurrent");


           var str1 = document.createTextNode(html1);
           var str2 = document.createTextNode(html2);
           var str3 = document.createTextNode(currentword);
           left.appendChild(str1);
           right.appendChild(str2);
           current.appendChild(str3);



           // replace child text region with
           // left,current, and right span elements
           // TO GET CURSOR POSITIONING



           parent.replaceChild(right,child);
           parent.insertBefore(current,right);
           parent.insertBefore(left,current);




           var SCcurrent = document.getElementById("SCcurrent");
           var SCleft = document.getElementById("SCleft");
           var SCright = document.getElementById("SCright");

           if(debug)
          {
     // debugging code
           SCcurrent.style.backgroundColor = "yellow";
           SCleft.style.backgroundColor="red";
           SCright.style.backgroundColor="orange";
          }
           var Curx = SCfindPosX(SCcurrent);
           var Cury = SCfindPosY(SCcurrent);
           var Curx1 = Curx + SCcurrent.offsetWidth;
           var Cury1 = Cury + SCcurrent.offsetHeight;

           var Leftx = SCfindPosX(SCleft);
           var Lefty = SCfindPosY(SCleft);
           var Leftx1 = Leftx + SCleft.offsetWidth;
           var Lefty1 = Lefty + SCleft.offsetHeight;

           var Rightx = SCfindPosX(SCright);
           var Righty = SCfindPosY(SCright);
           var Rightx1 = Rightx + SCright.offsetWidth;
           var Righty1 = Righty + SCright.offsetHeight;

// special processing for IE

           var bInsertForIE = false;
           var bUpForIE = false;
           var bDownForIE = false;
           var bBreakForIE = false;

           if(SCbIsIE)
           {


              var sElem = "";
              sElem = SCcurrent.componentFromPoint(
                      SCclientX,SCclientY);

              if(sElem == "")
              {
                bInsertForIE = true;
              }
              else
              {
                var sElem1 = "";
                var sElem2 = "";
                sElem1 = SCleft.componentFromPoint(
                      SCclientX,SCclientY);
                sElem2 = SCright.componentFromPoint(
                      SCclientX,SCclientY);

                if(sElem1 == "outside" &&
                   sElem2 == "outside")
                  bBreakForIE=true;
                if(sElem1 == "")
                  bUpForIE = true;
                if(sElem2 == "")
                  bDownForIE = true;
              }

           }

 if(debug)

{

 alert(Curx+","+Curx1+":"+Cury+","+Cury1+"::"+SCposx+","+SCposy +
     ":::" + high+","+mid+","+low);

 alert(Lefty+","+Lefty1+":"+Righty+","+Righty1+"::"+SCposx+","+SCposy +
     ":::" + high+","+mid+","+low);

}

           var currentHeight = SCcurrent.offsetHeight;
           var currentWidth = SCcurrent.offsetWidth;



           parent.removeChild(right);
           parent.removeChild(current);
           parent.replaceChild(child,left);
          // parent.replaceChild(child,span1);




    // if cursor below whole element then fail
           if(1==0 && SCposy > Righty1 && Righty < Righty1 &&
              Lefty1 != Righty1
             )
            {
              if(debug)alert("left break");
              break;
            }
   //  if cursor before whole element then fail
           if(1==0 &&SCposy < Lefty && Lefty < Lefty1)
           {
             if(debug)alert("right break");
              break;
           }

           if(bBreakForIE)
              break;

           oldmid = mid;

           var SCbCursorOverSelection=false;
           if(!SCbIsIE&&!SCbIsOpera&&!SCbIsPre4Safari)
           {

              if(SCselection[childindex] == true)
              {
                SCbCursorOverSelection=true;

              }
              else if(SCselectionarray[childindex].length>=mid &&
                 SCselectionarray[childindex][mid] == true)
              {
                SCbCursorOverSelection=true;

              }


           }


           if((SCposx >= Curx && SCposx <= Curx1 
             && SCposy >= Cury && SCposy <= Cury1) ||
                bInsertForIE)
           {

              var linkText = words[mid];

              if(!SCbIsIE && SCbCursorOverSelection && SCtranslateFrom != "Wikipedia")
              {
                  linkText = SCselectedText;
                  SCbIsWordInSelection = true;
              }

              SCSetInLink(linkText,parent,child,mid,delimiter,words,true);

              return true;
           }
           if(bUpForIE)
           {
              high = mid;
              mid = Math.floor((low + high)/2);
           }
           else if(bDownForIE)
           {
              low = mid;
              mid = Math.floor((low + high)/2);
           }
           else
           if(Cury > SCposy) // go up in element
           {
              high = mid;
              mid = Math.floor((low + high)/2);
           }
           else if(Cury1 < SCposy) // go down in element
           {
              low = mid;
              mid = Math.floor((low + high)/2);
           }
           else if(SCposx < Curx) // go up in element
           {
              high = mid;
              mid = Math.floor((low + high)/2);
           }
           else if(SCposx > Curx1)  // go down in element
           {
              low = mid;
              mid = Math.floor((low + high)/2);
           }

        }
    return false;

} var SCindex = 0; // for ids of translation windows // put the translation window near the cursor point var SCcurrentLink = ""; var SClinkword = ""; function SCSetInLink(word,parent,child,wordindex,delimiter,words,bIsSearchDone) {

 if(SCwindows >= SCMaxwindows)
    return;
 SCwindows++;

 var linkname = "SC" + (++SCindex);
 SCcurrentLink = linkname;

 // take out punctuation if single word 
 if(word.split(/\s/).length==1)
    word = word.replace(/[.;:?!,]/g,"");

 SClinkword = word;

 if(word.length > SCMaxWordLength)
 {
   word = word.substring(0,SCMaxWordLength);
   word += "...";
 }
 if((SCtranslateFrom == "ja" ||
    SCtranslateFrom.match("zh","i")) &&
    !SCbIsWordInSelection
   )
 //   && SCtranslateTo != "en")
 {//ch|ja to non en language
      // translate to next punctuation mark

var i; var wordlist="";

       var beginindex = (wordindex > 0)?wordindex-1:0;
       var endindex = words.length-1;

for(i=beginindex;i<=endindex;i++) { if(words[i].match(/\s/)) break; wordlist += words[i]; } word = wordlist;

  }

 var str1 = document.createTextNode(word);

 var a1 = document.createElement("DIV");

 a1.setAttribute("id",linkname); 
 if(!SCbIsIE)
 	a1.setAttribute("class","SCPopup");
 else a1.className = "SCPopupIE";
 if(!SCbIsIE) 
 {
   a1.setAttribute("onMousedown",'SChidespan("' + linkname + '")');
 }
 a1.setAttribute("onMouseover","SCdonthide=true;");
 a1.setAttribute("onMouseout","SCdonthide=false;");


 SCbInsertSpan = false;

 if(SCtranslateFrom != "Wikipedia")
 {
    a1.appendChild(str1);
    SCWikihtml = SCWikipediaLink(wordindex,delimiter,words).innerHTML;
    SCDicthtml = "";

 }
 else
 {  a1.appendChild(SCWikipediaLink(wordindex,delimiter,words));
   SCWikihtml="";
   SCDicthtml=word;
  if(!SCbIsIE) 
  {
           a1.setAttribute("onMousedown","");
           a1.firstChild.setAttribute("onMousedown","");
  }

 }


 var thebody = document.getElementsByTagName("BODY");

// var html = document.getElementsByTagName("HTML");


     var item = document.getElementById("SCitem");
     if(item)
       item.appendChild(a1);



   if(SCtranslateFrom == "Wikipedia")
   {
      SCsetLanguageDefault("wikipedialanguageoptions",
                      "Wikipedialanguage")

   }

 // cursorposition is absolute, positioning of 
 // webpage elements may upset translation window 
 // positioning, so append this window to body.

 // position window
 SCPositionPopup(linkname,true);

 if(SCtranslateFrom != "Wikipedia")
 {
   var theword = word;
   if((SCtranslateFrom.match("zh","i")
     || SCtranslateFrom == "ja")
     && !SCbIsWordInSelection
     )
   {
     var currword = word;
     var beginindex = (wordindex > 0)?wordindex-1:0;
     var endindex = (wordindex < words.length-1)?
                     wordindex+1:wordindex;
     var numwords = endindex - beginindex;
     if(numwords == 2)
       theword += " " + words[beginindex]+currword +" "+currword + words[endindex];
     else if(numwords == 1 && beginindex == wordindex)
       theword += " " + currword + words[endindex]; 
     else if(numwords == 1 && endindex == wordindex)
       theword += " " + words[beginindex] + currword;

     beginindex = wordindex;
     // we use cedit and edict for english stuff
     if(SCtranslateTo == "en"&&1==0)
     {

endindex = (wordindex+10 < words.length)? wordindex+10:words.length-1; var i; var wordlist=""; for(i=beginindex;i<=endindex;i++) { if(words[i].match(/[\r\n\s]/)) break; wordlist += words[i]; } if(i-beginindex > 2) theword += " " + wordlist;

     }
     else 
     {//ch|ja to non en language
      // translate to next punctuation mark

var i; var wordlist="";

       endindex = words.length-1;

for(i=beginindex;i<=endindex;i++) { if(words[i].match(/\s/)) break;

               words[i] = words[i].replace(/\'/g,"\u0026#39;");
       	words[i] = words[i].replace(/\"/g,"\u0026quot;");

wordlist += words[i]; } theword = wordlist;

     }
   }
   SCasyncGet(linkname, theword);
 }

}

// is the current element in a link and does this link have a title? function SCIsSrcElementInALink() {

  if(!SCsrcElement)
     return false;


  if(SCsrcElement.nodeName.match(/^a$/i)
     && ((SCsrcElement.getAttribute("title") != null &&
         SCsrcElement.getAttribute("title") != "")
         ||
         typeof popupVersion != "undefined")
        )
     return true;

  var Elem = SCsrcElement;

  while(Elem.parentNode)
  {
     if(Elem.nodeName.match(/^a$/i)
     && ((Elem.getAttribute("title") != null &&
         Elem.getAttribute("title") != "")
         ||
         typeof popupVersion != "undefined")
        )
     return true;

     Elem = Elem.parentNode;
  }
  return false;

}

function SCPositionPopup(linkname,bAdjust) {

 var a2 = document.getElementById(linkname);
 var thebody = document.getElementsByTagName("BODY");

 var posyadjust = 0;
 var posxadjust = 0;

 var bAdjustPosition = SCIsSrcElementInALink();

 if(bAdjustPosition && typeof popupVersion != "undefined" && bAdjust)
     posyadjust = -SCPosYAdjust;
 else if(bAdjustPosition)
     posyadjust = SCPosYAdjust;

 if(a2 && a2.style)
 {

   if(SCbIsIE)
   {
     // for ie
     // body overflow needs to be set

   //  a2.style.position = "absolute";
     a2.style.left = SCposx;
     a2.style.top = SCposy + posyadjust;

     a2.onMouseover=new Function("SCdonthide=true;");
     a2.onMouseout=new Function("SCdonthide=false;");

// if in ie the initial span goes over the right

       // side of screen

var doclength = SCfinddoclength(a2); var a2overflow = (SCposx+a2.scrollWidth+1) - doclength;

if(a2overflow > 0) { var newposx = SCposx - a2overflow; // a2.style.position="absolute"; a2.style.left = newposx; a2.style.top=SCposy + posyadjust;

}

   }
   else
   {
       // a2 element returns 0 for following function
       var doclength = SCfinddoclength(thebody[0]);

var a2overflow = (SCposx+a2.scrollWidth+1) - doclength; if(a2overflow > 0) { var newposx = SCposx - a2overflow; // a2.style.position="absolute"; a2.style.left = newposx + "px"; a2.style.top=(SCposy + posyadjust) + "px"; // }

     else
     {
         a2.style.left = (SCposx) + "px";
         a2.style.top = (SCposy + posyadjust) + "px";
     }


   }

// a2.style.display="block";

 }

}

// after translation window has been left clicked // remove it function SChidespan(id) {


 if(SCdonthide)
   return; // so change of language can be done

 try
 {

 SCwindows--;
 if(SCwindows < 0) SCwindows = 0;

 var ob = document.getElementById(id);
 //alert(ob.innerHTML)
 if(SCbIsOpera && !ob)
    alert("ob not found");
 if(SCbIsOpera && 1==0)
 {
    ob = document.getElementById("SCitem");

// delete all children while(ob.childNodes.length>0) { ob.removeChild(ob.childNodes[0]); }

 }
 else
 {
   var styl = ob.style;
   styl.display = "none";

 if(ob && ob.parentNode)
    ob.parentNode.removeChild(ob);
 else
    if(SCbIsOpera) alert("maybe no parentnode");
 }
 }
 catch(err)
 {
   if(SCbIsOpera)
     alert("unable to hide " + id);
 }

 // clear the html
 SCWikihtml = "";
 SCDicthtml = "";


} function SCfinddoclength(obj) {

       var ScrollBarOffset = 0;
       if(SCbIsIE)
       {
           ScrollBarOffset = 20;
       }

if(SCbIsOpera)

         return window.innerWidth;
       else
         return document.body.clientWidth-ScrollBarOffset; // minus the scroll bar if IE

if (obj.offsetParent) { while (obj.offsetParent) {

obj = obj.offsetParent; }

}

       return obj.offsetWidth;

} // code from http://www.quirksmode.org/js/findpos.html function SCfindPosX(obj) { var curleft = 0;

       if(!obj)
          return 0;

if (obj.offsetParent) { while (obj.offsetParent) { curleft += obj.offsetLeft obj = obj.offsetParent; } } else if (obj.x) { curleft += obj.x; } return curleft; } // code from http://www.quirksmode.org/js/findpos.html function SCfindPosY(obj) { var curtop = 0;

       if(!obj)
          return 0;

if (obj.offsetParent) { while (obj.offsetParent) { curtop += obj.offsetTop obj = obj.offsetParent; } } else if (obj.y)

       {

curtop += obj.y;

       }

return curtop; }

// place meaning within translation window function SCInsertTranslation(translatedword) { // var translatedword = SCrequest.responseText;


var translation = translatedword.split("|"); // translation[0] id of window // translation[1] word // translation[2] translated word

         var meaning = translation[2];

if(meaning == '\n') meaning = "NOT FOUND!!";

         meaning = meaning.replace(/\\u0026#39;/gi,"'");
         meaning = meaning.replace(/\\u0026quot;/gi,'"');


         if(SCtranslateFrom == "Wikipedia" && !SCbInsertSpan)
         {
           SCDicthtml = meaning;
           return;
         }
         SCbInsertSpan = true;
         // SCbInsertSpan = false;

         var ob = document.getElementById(translation[0]);

if(ob) {

try{

                    var span1 = document.createElement("SPAN");
                    var newid = "x" + translation[0];
                    span1.setAttribute("id",newid);

                    span1.innerHTML = 
                            meaning;

if(SCbIsIE)

                    {
                        var newfunction = new Function("SCdonthide=false;");
                        span1.onmouseout = newfunction; 
                        var newfunction1 = new Function("SCdonthide=true;");
                        span1.onmouseover = newfunction1; 
                    }

ob.replaceChild(span1,ob.firstChild);

                    var span2 = document.getElementById(newid);
		     var bIsInLink = SCIsSrcElementInALink();
                    var pixelstring = "px";
                    if(SCbIsIE)
                        pixelstring = "";

                    // the following code handles the IE 
                    // case where the yellow window goes
                    // to the right of the body
                   if(!SCbIsSafari)
                    {
                    var doclength = SCfinddoclength(span2.parentNode);
                    var span2overflow = (SCposx+span2.scrollWidth+1) - doclength;

                    if(span2overflow > 0)
                    {

                      var newposx =  SCposx - span2overflow;
                      span2.parentNode.style.position="absolute";
                      span2.parentNode.style.left = newposx;
                      span2.parentNode.style.top=SCposy;

if(bIsInLink && typeof popupVersion == "undefined")

                         span2.parentNode.style.top = (SCposy + SCPosYAdjust) + pixelstring;
                    }
                   }

                   if(bIsInLink && typeof popupVersion != "undefined")
                   {
                      span2.parentNode.style.top = (SCposy - span2.parentNode.offsetHeight) + pixelstring;
                   }

} catch(err) { alert("unable to set innerHTML"); } }

       //else alert("no id:" + translatedword);

}

// issue Google Ajax request

function SCasyncGet(id,word) {

  var languagepath = "en|fr";
  // consult SCtranslate for language translation
  if(typeof SCtranslateFrom != "undefined"
     && typeof SCtranslateTo != "undefined"
    )
  {
      languagepath = SCtranslateFrom + "|" + 
                     SCtranslateTo;
  }

  var persistantlanguagepathFrom = SCPersistantLoad('languageFrom');
  var persistantlanguagepathTo = SCPersistantLoad('languageTo');



  if(persistantlanguagepathFrom != "" &&
     persistantlanguagepathTo != "")
       languagepath = persistantlanguagepathFrom + 
            "|" + persistantlanguagepathTo;
  if(languagepath=="Wikipedia")
       languagepath = "en|fr";

// if(languagepath == "Wikipedia") // { // } // else

  {
    // google limits to 500 characters of translation
    if(word.length > SCMaxWordLength)
       word = word.substring(0,SCMaxWordLength);
    var bIsSelectedText = "NO";
    if(SCbIsWordInSelection)
        bIsSelectedText = "YES";

    SCgoogleLookup(word,id);
  }

} function SCreplaceHtml(obj,newhtml) {

while(obj.childNodes.length>0)
{
  obj.removeChild(obj.childNodes[0]);
}
var newspan = document.createElement("SPAN");
newspan.innerHTML = newhtml
obj.appendChild(newspan);

} function SClanguageChange(obj,id) {

var langoptions = 
obj.options[
    obj.selectedIndex
    ].value;
if(id.match(/From/))
{
  SCtranslateFrom = langoptions;
  SCPersistantSave('languageFrom',SCtranslateFrom);
}
else if(id.match(/To/))
{
  SCtranslateTo = langoptions;
  SCPersistantSave('languageTo',SCtranslateTo);
}

// SCtranslate = langoptions;

//SCdonthide = false;
//SChidespan(SCcurrentLink);

} function SCwikipedialanguageChange(obj) {

var langoptions = 
obj.options[
    obj.selectedIndex
    ].value;
SCWikipedialanguage = langoptions;
SCPersistantSave('Wikipedialanguage',SCWikipedialanguage);
var objWiki = document.getElementById(SCcurrentLink);
if(!objWiki)
{
  alert("objWiki failed: " + SCcurrentLink);

}

var arrLinks = objWiki.getElementsByTagName("A");
var i;
for(i=0;i<arrLinks.length;i++)
{
  var theonclick = arrLinks[i].getAttribute("onclick");

  if(!theonclick)
     continue;

  if(theonclick.toString().match(/^(.*)..\.wikipedia\.org\?(.*)$/mi))
  {

     var newonclick =
      RegExp.$1 +
         SCWikipedialanguage +".wikipedia.org" + '?' + RegExp.$2;


     if(SCbIsIE)
     {


        if(newonclick.match(/\{(\s*javascript:)?(.*)\}/mi) ||
           newonclick.match(/(javascript:)?(.*)/mi))
        {

          var newfunction = new Function("return " + RegExp.$2 + ";");
          arrLinks[i].onclick = newfunction; 
        }
     }
     else
        arrLinks[i].setAttribute("onclick",newonclick);
  }
} 

} function SCSettings() {

 var linkname = "SC" + (++SCindex);
 SCcurrentLink = linkname;

 var str1 = document.createTextNode("");
 var a1 = document.createElement("DIV");
 a1.setAttribute("id",linkname); 
 if(!SCbIsIE) // firefox accepts these attributes
                   // ie does not
 {
   a1.setAttribute("onMousedown",'SChidespan("' + linkname + '")');
   a1.setAttribute("class","SCPopup");
   /*
   a1.setAttribute("style","text-decoration:none;background-color:" + SCPopupBackgroundColor + ";border: 1px solid blue;position:absolute;font-size:14pt;z-index:9999;overflow:visible;display:none;line-height:normal;");
 */
 }

 if(SCbIsIE) 
 {
      a1.className="SCPopupIE";
 //   a1.setAttribute("href",'javascript:SChidespan("' + linkname +  '")');
 }
 a1.setAttribute("onMouseover","SCdonthide=true;");
 a1.setAttribute("onMouseout","SCdonthide=false;");

 a1.appendChild(str1); 
  if(!SCbIsIE) 
  {
           a1.setAttribute("onMousedown","");
          // a1.firstChild.setAttribute("onMousedown","");
  }
 var thebody = document.getElementsByTagName("BODY");

// var html = document.getElementsByTagName("HTML");

     var item = document.getElementById("SCitem");
     if(item)
       item.appendChild(a1);
     SCPositionPopup(linkname,false);

     SCmakevisiblelanguagechange();    

} function SCHideSettings() {

 SCdonthide=false;
 SCPersistantSave('GoogleTrans',(SCGoogleTrans)?"1":"0");
 var popsid = document.getElementById("ca-TransPopsId");
 if(!popsid)
     return;

 var anchor1 = popsid.getElementsByTagName( "a" )[0];
 if(!anchor1)
    return;

 var text1; 
 if(SCGoogleTrans)
   text1 = document.createTextNode(SCstrGoogleTrans  + SCstrOn);
 else 
   text1 = document.createTextNode(SCstrGoogleTrans + SCstrOff);


// delete text from anchor in tab
while(anchor1.childNodes.length>0)
{
  anchor1.removeChild(anchor1.childNodes[0]);
}
// replace text in anchor in tab
anchor1.appendChild(text1);

 SChidespan(SCcurrentLink);

} function SCSaveShiftKeyNeeded() {

  if(SCbNoShiftKeyBrowser)
  {
    SCShiftKeyNeeded = false;
    return;
  }
  SCsetInterval();

  SCPersistantSave("shiftkeyneeded",SCShiftKeyNeeded?"1":"0");

} var SCnewhtml = ""; function SCmakevisiblelanguagechange() {

var obj = document.getElementById(SCcurrentLink); if(!obj) {

 alert("unable to get object: SCmakevisiblelanguagechange " + SCcurrentLink);
 return;

} SCdonthide = true;


var persistantlanguageFrom = SCPersistantLoad('languageFrom'); if(persistantlanguageFrom == "")

  persistantlanguageFrom = SCLanguageDefaultFrom;

var persistantlanguageTo = SCPersistantLoad('languageTo'); if(persistantlanguageTo == "")

  persistantlanguageTo = SCLanguageDefaultTo;


var newhtml =

'<a class="SCxWindow" href="javascript:SCdonthide=false;SChidespan(\ + SCcurrentLink + '\')">X</a>' +

'<a href="javascript:;"' + ' style="text-decoration:underline;" ' + ' onclick="javascript:SCGoogleTrans=' + ((SCGoogleTrans)?"false":"true") + ';SCHideSettings();">' + ((SCGoogleTrans)?SCstrTurnOffPopups: SCstrTurnOnPopups) + '</a>' + '  <a style="text-decoration:underline" href="javascript:;" onclick="javascript:window.open(\+SCstrHelpUrl+'\');">' + SCstrPopupHelp+'</a>'+ '
<a href="javascript:;"' + ' style="text-decoration:underline;" ' + ' onclick="javascript:SCShiftKeyNeeded=' + ((SCShiftKeyNeeded)?"false":"true") + ';SCSaveShiftKeyNeeded()' + ';SCHideSettings();">' + SCstrShiftKeyNeeded + ((SCShiftKeyNeeded)?SCstrOff:SCstrOn) + '</a>
' + '
  ' + '' + SCstrSelectLanguage + '
' + '<SELECT name="languageoptionsFrom" id="languageoptionsFrom"' +

'onchange="SClanguageChange(this,\'languageFrom\')"' +

' onMouseover="javascript:SCdonthide=true;" class="SChidestuff">' + //' onMouseout="javascript:SCdonthide=false;">' +

SCnewoptions + 

'</select>' + '<SELECT name="languageoptionsTo" id="languageoptionsTo"' +

'onchange="SClanguageChange(this,\'languageTo\')">' +

' onMouseover="javascript:SCdonthide=true;">' + //' onMouseout="javascript:SCdonthide=false;">' +

SCnewoptions + 

'</select>  
' + SCstrSingleWord + SCstrOn + '
' + SCstrSelectedText + ((SCbIsIE || (SCbIsMozilla&&!(SCbIsOpera||SCbIsKonqueror||SCbIsPre4Safari )))?SCstrOn:SCstrOff) + ((SCbIsKonqueror)?SCstrKonqueror + '
' : ) + '
'

var newspan = document.createElement("DIV");


 newspan.setAttribute("onMouseover","javascript:SCdonthide=true;");
 if(SCbIsIE)
 {
    newspan.onmouseover = new Function("SCdonthide=true;");
 }
newspan.innerHTML = newhtml;

var i;

while(obj.childNodes.length>0)
{
  obj.removeChild(obj.childNodes[0]);
}


// obj.replaceChild(newspan,obj.firstChild);

 obj.appendChild(newspan);

if(SCbIsSafari)

  obj.setAttribute("onMousedown","");

// obj.innerHTML = newhtml;

  SCsetLanguageDefault("languageoptionsFrom","languageFrom");
  SCsetLanguageDefault("languageoptionsTo","languageTo");



}

function SCsetLanguageDefault(id,key) {

 var i;
 var obj = document.getElementById(id);
 if(!obj)
     return;

 var defaultlanguage = SCPersistantLoad(key);
 if(defaultlanguage == "")
 {

// if(typeof SCtranslate == "undefined")

    {
       if(key.match(/From/))
         defaultlanguage = SCLanguageDefaultFrom;
       else if(key.match(/To/))
         defaultlanguage = SCLanguageDefaultTo;

    }

// else defaultlanguage = SCtranslate;

 }

if(!obj || (obj && !obj.options))
 {
    alert("setLanguageDefault: bad object passed!" +
     id + ":" +
     key);
 }
 for(i=0;i<obj.options.length;i++)
 {
    obj.options[i].selected = false;
    if(obj.options[i].value.match(defaultlanguage,"i"))
    {
       obj.options[i].selected = true; 

    }
 }

 if(SCbIsSafari)
 {
    obj.setAttribute("style","text-decoration:underline");
 }

}


function SCPersistantSave(key,value) { /*

  if(SCbIsIE)
      SCUserDataSave(key,value);
  else if(typeof globalStorage != "undefined") 
      SCGlobalStorageSave(key,value);
  else */
  SCsetcookieVal(key,value);

} function SCPersistantLoad(key) {

  /*
  if(SCbIsIE)
      return SCUserDataLoad(key);
  else if(typeof globalStorage != "undefined") 
      return SCGlobalStorageLoad(key);
  else */
   return SCgetcookieVal(key);

}

function SCUserDataSave(key,value) {

 SCPersistElement.setAttribute(key,value);
 SCPersistElement.save("oXMLStore");

} function SCUserDataLoad(key) {

 SCPersistElement.load("oXMLStore");
 var retc = SCPersistElement.getAttribute(key);
 if(retc == null)
   retc = "";
 return retc;

} function SCGlobalStorageSave(key,value) {

 if(globalStorage)
 {
   eval ("globalStorage['www.securecottage.com']." +
         key + "='" + value + "'");
 }

} function SCGlobalStorageLoad(key) {

 var retc = "";
 if(globalStorage)
 {
    retc =     eval ("globalStorage['wikipedia.org']." +
         key );
    if (retc == null)
       return "";

    return retc.value;

 }
 return "";

} function SCsetcookieVal(cookieKey,cookieValue) {

  var ExpireDate = new Date();

// ExpireDate.setYear(ExpireDate.getYear() + 1910);

  ExpireDate.setYear(ExpireDate.getUTCFullYear() + 10);

  document.cookie =  cookieKey + '=' + cookieValue + 
         "; path=/; " 
       //  + "domain=" + document.domain + "; " 
         + "expires=" + ExpireDate.toGMTString();


} function SCgetcookieVal(cookieName) {

 var aCookie = "";
 var thisCookie;


 aCookie = document.cookie;
 thisCookie = aCookie.split("; ");

 var i;
 var retCookie = "";
 for(i=0;i<thisCookie.length;i++)
 {
    if(cookieName == thisCookie[i].split("=")[0].substring(0,cookieName.length))
    {
          retCookie = thisCookie[i].split("=")[1];
          break;
    }
 }

 return retCookie;

} function SCwikichange(obj) {

 var parobj = document.getElementById(SCcurrentLink);
 if(parobj && SCWikihtml != "")
 {
    SCalttranslateFrom = SCtranslateFrom;
    SCalttranslateTo = SCtranslateTo;
    SCtranslateFrom = "Wikipedia";
    SCDicthtml = parobj.innerHTML;
    if(SCbIsIE||SCbIsSafari)
       SCreplaceHtml(parobj,SCWikihtml);
    else
       parobj.innerHTML = SCWikihtml;
   SCsetLanguageDefault("wikipedialanguageoptions",
                      "Wikipedialanguage")

  if(SCbIsSafari) 
           parobj.setAttribute("onMousedown","");

 //  SChidewradd();
 }

} function SCdictionarychange(obj) {

 var parobj = document.getElementById(SCcurrentLink);

 if(parobj && SCDicthtml != "")
 {
    SCtranslateFrom = SCalttranslateFrom;
    SCtranslateTo = SCalttranslateTo;
    SCtranslateFrom = SCPersistantLoad('languageFrom');
    SCtranslateTo = SCPersistantLoad('languageTo');

    if(SCtranslateFrom == "")
        SCtranslateFrom = SCLanguageDefaultFrom;
    if(SCtranslateTo == "")
        SCtranslateTo = SCLanguageDefaultTo;

    SCWikihtml = parobj.innerHTML;
    if(SCbIsIE||SCbIsSafari)
      SCreplaceHtml(parobj,SCDicthtml);
    else
      parobj.innerHTML = SCDicthtml;

    if(parobj && !SCbIsIE)
    {
       parobj.setAttribute("onMousedown",'SChidespan("' + SCcurrentLink + '")');
    }

    if(!SCbInsertSpan)
    {

      SCasyncGet(SCcurrentLink,SCDicthtml);
      SCbInsertSpan=true;

    }

 }

}

function SCWikipediaLink(wordindex,delimiter,words) {

 var i,j;

 var newhtml = 

'' + '<a href="javascript:SCmakevisiblelanguagechange()"' + ' style="text-decoration:underline"' + ' onMouseover="javascript:SCdonthide=true;"' + '>' + 'Wikipedia</a>';

 newhtml +=
 '  <a href="javascript:SCdictionarychange(this)"' +

' style="text-decoration:underline"' + ' onMouseover="javascript:SCdonthide=true;"' + '>' + 'Dictionary?</a>';

 newhtml += 

'
Wikipedia language
' + '<SELECT name="Wikipedialanguageoptions" id="wikipedialanguageoptions"' +

'onchange="SCwikipedialanguageChange(this)">' +

' onMouseover="javascript:SCdonthide=true;"' + '>' + SCnewoptions + '</select>
';

 var numwords=4;
 if((SCtranslateFrom.match("zh","i") 
     || SCtranslateFrom == "ja"
    )
      ||
    (SCtranslateFrom == "Wikipedia" && 
     (SCalttranslateFrom.match("zh","i") ||
      SCalttranslateFrom == "ja"
       ))
    )
     numwords = 10;

 var wordindexend = wordindex + numwords;
 if(wordindexend > words.length-1)
    wordindexend = words.length-1;
 for(i=wordindex;i<=wordindexend;i++)
 {
    var linktext = "";
    for(j=wordindex;j<=i;j++)
    {
       var theword="";
       if(words[j].match(/^(.*)\'s$/))
          theword = RegExp.$1;
       else theword = words[j];
       theword = theword.replace(/\'/g,"\u0026#39;");
       theword = theword.replace(/\"/g,"\u0026quot;");
       linktext += theword;
       if(j<i)
         linktext += delimiter;
    }
    newhtml += '
<a ' +

'onMouseover="javascript:SCdonthide=true;" ' +

' href="javascript:SCdonthide=false;SChidespan(\ + SCcurrentLink + '\');" ' +
'onclick=\'javascript:window.open(\"http://' +

SCWikipedialanguage.substring(0,2) + '.wikipedia.org?go=Go&search=' +

    encodeURIComponent(linktext) + '\")\'>' + linktext + '</a>';

 }
 newhtml +='
<a href="javascript:SCdonthide=false;SChidespan(' + '\ + SCcurrentLink +'\')">Close Window</a>'; var newspan = document.createElement("SPAN"); newspan.innerHTML = newhtml; return newspan;

} //http://www.howtocreate.co.uk/tutorials/javascript/browserwindow function SCScreenSize() {

 if( typeof( window.innerWidth ) == 'number' ) {
   //Non-IE
   SCscreenWidth = window.innerWidth;
   SCscreenHeight = window.innerHeight;

 } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
   //IE 6+ in 'standards compliant mode'
   SCscreenWidth = document.documentElement.clientWidth;
   SCscreenHeight = document.documentElement.clientHeight;
 } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
   //IE 4 compatible
   SCscreenWidth = document.body.clientWidth;
   SCscreenHeight = document.body.clientHeight;
 }

} //http://www.howtocreate.co.uk/tutorials/javascript/browserwindow function SCgetScrollXY() {

 if( typeof( window.pageYOffset ) == 'number' ) {
   //Netscape compliant
   SCscrOfY = window.pageYOffset;
   SCscrOfX = window.pageXOffset;
 } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
   //DOM compliant
   SCscrOfY = document.body.scrollTop;
   SCscrOfX = document.body.scrollLeft;
 } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
   //IE6 standards compliant mode
   SCscrOfY = document.documentElement.scrollTop;
   SCscrOfX = document.documentElement.scrollLeft;
 }

} // is the current cursor position inside // a selected area. The selected area must be // within one node and be on the same line // only works for IE

function SCNotTooBigAndInSelection(theparent,thechild) {

 if(!SCbIsIE)
     return "";
 var retWordList = SCIsInSelection(theparent,thechild);
 if(retWordList == "" || retWordList == null)
     return "";
 var retWordArray = retWordList.split(/[\r\n\s]+/);
 return retWordList;

} // only works for IE function SCIsInSelection(theparent,thechild) { var returnSelectedText=""; var debug = false; var userSelection;


if(SCbIsIE) // IE {

 var debug=false;

 if(SCSelectionType.match(/none/i)||SCSelectionType == ""||SCSelectionType == null)
   return "";

 if(SCSelectionType.match(/text/i))
 {
   var range = SCIeRange;
   var s = range.text;
   var collection = range.getClientRects();
   var i;
   for(i=0;i<collection.length;i++)
   {
      var leftt = collection[i].left +
          document.body.scrollLeft+
          document.documentElement.scrollLeft;
      var rightt =  collection[i].right +
          document.body.scrollLeft+
          document.documentElement.scrollLeft;
      var topp = collection[i].top +
          document.body.scrollTop+
          document.documentElement.scrollTop;
      var bottomm = collection[i].bottom +
          document.body.scrollTop+
          document.documentElement.scrollTop;

   if(SCposx >= leftt
   && SCposx <= rightt
   && SCposy >= topp
   && SCposy <= bottomm
   )
      return s;
 if(debug)
      alert(collection[i].left+"-"+collection[i].right+"_"+
            collection[i].top +"_"+collection[i].bottom+"__"+
       leftt+":"
      + ":" + rightt + ":" +
      ":"+document.body.scrollLeft +":"+
          document.documentElement.scrollLeft +
      ":"+topp+":"
       +bottomm+":"+":"
        +document.body.scrollTop+":" +
       document.documentElement.scrollTop +":"
      +SCposx+":"+SCposy);
   }


   return "";
  }

 }

} function SCgetRangeObject(selectionObject) { // Safari! var range = document.createRange(); range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset); range.setEnd(selectionObject.focusNode,selectionObject.focusOffset); return range;

} function SCAreWordsInSelection(userSelection,SCselectionarray,child,childindex) {

 var range = SCgetRangeObject(userSelection);
 var str = child.nodeValue;
 if(str == "" || str == null)
   return;
 var delimiter = /^[\r\n ]+/
 var delimiter1 = /^\S+/;
 var bNoSpace = false;
 if(SCtranslateFrom.match("zh","i") ||
    SCtranslateFrom == "ja")
 {
   bNoSpace = true;
 }
 SCselectionstart[childindex] = -1;
 SCselectionend[childindex] = -1;
 var range1 = document.createRange();

 var i,j,k;
 for(i=0,j=0;i<str.length;i++)
 {

   if(SCisPointInRange(range,range1,child,i))
   {
      if(SCselectionstart[childindex] == -1)
        SCselectionstart[childindex] = i;
      SCselectionend[childindex] = i;
   }

   if(str.substring(i).match(delimiter))
     continue;
   else
   { 
     if(SCisPointInRange(range,range1,child,i))
     {
       SCselectionarray[j] = true;
     }
     else SCselectionarray[j] = false;
     j++;
   }
   k=i;
   for(k=i;!bNoSpace && k<str.length;k++)
   {
     if(SCisPointInRange(range,range1,child,k))
       SCselectionend[childindex] = k;
     if(!str.substring(k).match(delimiter1))
       break;
   }
   i=k;


 }

} function SCOldAreWordsInSelection(userSelection,SCselectionarray,child,childindex) {

 var range = SCgetRangeObject(userSelection);
 var str = child.nodeValue;
 if(str == "" || str == null)
   return;
 var delimiter = /^[\r\n ]+/
 var delimiter1 = /^\S+/;
 var bNoSpace = false;
 if(SCtranslateFrom.match("zh","i") ||
    SCtranslateFrom == "ja")
 {
   bNoSpace = true;
 }
 SCselectionstart[childindex] = -1;
 SCselectionend[childindex] = -1;
 var range1 = document.createRange();

 var i,j,k;
 for(i=0,j=0;i<str.length;i++)
 {

   if(range.isPointInRange(child,i))
   {
      if(SCselectionstart[childindex] == -1)
        SCselectionstart[childindex] = i;
      SCselectionend[childindex] = i;
   }

   if(str.substring(i).match(delimiter))
     continue;
   else
   { 
     if(range.isPointInRange(child,i))
     {
       SCselectionarray[j] = true;
     }
     else SCselectionarray[j] = false;
     j++;
   }
   k=i;
   for(k=i;!bNoSpace && k<str.length-1;k++)
   {
     if(!str.substring(k+1).match(delimiter1))
       break;
   }
   i=k;
   if(range.isPointInRange(child,i))
     SCselectionend[childindex] = i;

 }

}

function SCisPointInRange(range,range1,child,i) {

   if(SCbIsMozilla)
      return range.isPointInRange(child,i);

   range1.setStart(child,i);
   range1.setEnd(child,i);
   if(range.compareBoundaryPoints(Range.START_TO_START,range1) <= 0 &&
      range.compareBoundaryPoints(Range.END_TO_END,range1) >= 0)
      return true;
   else return false;

}

function SCgoogleLookup(text,label) {

     SClabel = label;
     SCtext = text;

     SCgoogletranslate =
      "http://translate.google.com/translate?hl=en" + "&tl=" + SCtranslateTo +"&u=" +
         encodeURIComponent(document.location.href);


         google.language.translate(text, "", SCtranslateTo, 
           function(result) {  

           if (result.translation||result.error) 
      { 
      var nbspstr = "";
      var i;
      var SClanguagestrlength = SCGetGoogleLanguage(SCtranslateTo).length + 10;
      if(SClanguagestrlength < SCSourcestrlength)
         SClanguagestrlength = SCSourcestrlength+10;
      if(result.translation)
      {
      for(i=result.translation.length;i<SClanguagestrlength;i++)
      {  
           nbspstr+=" ";
      }
      }

      var pretranslatedword = "";
      if(SClinkword.split(/\s/).length == 1)
          pretranslatedword = SClinkword + ": ";

      var insertstring = SClabel + '|' + SCtext + '|' +
     SClanguageprompt1 +  '→ ' +   SCGetGoogleLanguage(SCtranslateTo) + 	    SClanguageprompt2 + 
 SCgooglereference2 +
     SClabel + SCgooglereference3 + 

'

' +
     ((result.translation)?
           (pretranslatedword + result.translation)
:result.error.message) + nbspstr +'

' + '


' +
     SCgooglereference1 + 
     SCgoogletranslate +
     SCgooglereference1a
+ '

'

     ;

            SCInsertTranslation(insertstring); 

      }

         }); 

}


SCScreenSize(); function SCGetGoogleLanguage(lcode) {

 var l;
 for (l in google.language.Languages)
 {
    if(lcode == google.language.Languages[l])
      return (l.substring(0,1).toUpperCase() + l.substring(1).toLowerCase()); 
 }
 return "undefined";

}


SCScreenSize();

addOnloadHook(

   function () {
       SCcreateEvents();
   }

); addOnloadHook(

   function () {
       var bPops = SCPersistantLoad('GoogleTrans');

       var title = SCstrGoogleTrans;
       if(bPops == "1" || bPops == "")
       {
          SCGoogleTrans = true;
          title += SCstrOn;
          if(bPops == "")
             SCPersistantSave('GoogleTrans','1');
       }
       else
       {
          SCGoogleTrans = false;
          title += SCstrOff;
       }
   //    SCMakeGoogleLanguages();
       mw.util.addPortletLink('p-cactions',"javascript:SCSettings()", title, "ca-TransPopsId", SCstrChangeOptions, "");
   }

); // Temporary necessity of evil, shouldn't really be here, // but as it is at the moment the only required file for all modules // of twinkle, it's here. // Should perhaps be moved into a "twinklebase.js" file. var twinkleConfigExists = false;

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

if( typeof( TwinkleConfig ) == 'undefined' ) TwinkleConfig = {}; switch (skin) { case 'vector': if( typeof( TwinkleConfig.portletArea ) == 'undefined' ) TwinkleConfig.portletArea = 'right-navigation'; if( typeof( TwinkleConfig.portletId ) == 'undefined' ) TwinkleConfig.portletId = 'p-twinkle'; if( typeof( TwinkleConfig.portletName ) == 'undefined' ) TwinkleConfig.portletName = 'TW'; if( typeof( TwinkleConfig.portletType ) == 'undefined' ) TwinkleConfig.portletType = 'menu'; if( typeof( TwinkleConfig.portletNext ) == 'undefined' ) TwinkleConfig.portletNext = 'p-search'; break; default: if( typeof( TwinkleConfig.portletId ) == 'undefined' ) TwinkleConfig.portletId = 'p-cactions'; break; }

/**

* Add a portlet menu to one of the navigation areas on the page.
* This is necessarily quite a hack since skins, navigation areas, and
* portlet menu types all work slightly different.
*
* Available navigation areas depend on the script used.
* Monobook:
*  "column-one", outer div class "portlet", inner div class "pBody". Existing portlets: "p-cactions", "p-personal", "p-logo", "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*  Special layout of p-cactions and p-personal through specialized styles.
* Vector:
*  "panel", outer div class "portal", inner div class "body". Existing portlets/elements: "p-logo", "p-navigation", "p-interaction", "p-tb", "p-coll-print_export"
*  "left-navigation", outer div class "vectorTabs" or "vectorMenu", inner div class "" or "menu". Existing portlets: "p-namespaces", "p-variants" (menu)
*  "right-navigation", outer div class "vectorTabs" or "vectorMenu", inner div class "" or "menu". Existing portlets: "p-views", "p-cactions" (menu), "p-search"
*  Special layout of p-personal portlet (part of "head") through specialized styles.
* Modern:
*  "mw_contentwrapper" (top nav), outer div class "portlet", inner div class "pBody". Existing portlets or elements: "p-cactions", "mw_content"
*  "mw_portlets" (sidebar), outer div class "portlet", inner div class "pBody". Existing portlets: "p-navigation", "p-search", "p-interaction", "p-tb", "p-coll-print_export"
*
* NOTE: If anyone is brave enough to reuse this directly, please shoot
* me a note. Otherwise I might change the signature down the line and
* your script breaks. Amalthea.
*
* @param String navigation -- id of the target navigation area (skin dependant, on vector either of "left-navigation", "right-navigation", or "panel")
* @param String id -- id of the portlet menu to create, preferably start with "p-".
* @param String text -- name of the portlet menu to create. Visibility depends on the class used.
* @param String type -- type of portlet. Currently only used for the vector non-sidebar portlets, pass "menu" to make this portlet a drop down menu.
* @param Node nextnodeid -- the id of the node before which the new item should be added, should be another item in the same list, or undefined to place it at the end.
*
* @return Node -- the DOM node of the new item (a DIV element) or null
*/

function twAddPortlet( navigation, id, text, type, nextnodeid ) { //sanity checks, and get required DOM nodes var root = document.getElementById( navigation ); if ( !root ) return null;

var item = document.getElementById( id ); if (item) { if (item.parentNode && item.parentNode==root) return item; return null; }

var nextnode; if (nextnodeid) nextnode = document.getElementById(nextnodeid);

//Add styles we might need.

 if (!twAddPortlet.styleAdded)
 {
 	if (skin=="vector") appendCSS( "div div.extraMenu h5 span { background-position: 90% 50%;} div.extraMenu h5 a { padding-left: 0.4em; padding-right: 0.4em; width:auto; } div.extraMenu h5 a span {display:inline-block; font-size:0.8em; height:2.5em; font-weight: normal; padding-top: 1.25em; margin-right:14px; }" );
 	else if (skin=="modern") appendCSS("#mw_contentwrapper div.portlet { overflow:hidden; height:1.5em; margin:0 0 0 14em; padding:0; } #mw_contentwrapper div.portlet h5 {display:none;} #mw_contentwrapper div.portlet div.pBody {margin:0; padding:0;} #mw_contentwrapper div.portlet div.pBody ul { display:inline; margin:0; } #mw_contentwrapper div.portlet div.pBody ul li { display:block; float:left; height:1.5em; margin:0 0.5em; padding:0 0.2em; text-transform:lowercase; } #mw_contentwrapper div.portlet div.pBody ul li a { text-decoration:underline;} #mw_contentwrapper div.portlet div.pBody ul li.selected a { text-decoration:none;}");
 	twAddPortlet.styleAdded = true;
 }

//verify/normalize input type = skin=="vector" && type=="menu" && (navigation=="left-navigation" || navigation=="right-navigation")?"menu":""; var outerDivClass; var innerDivClass; switch (skin) { case "vector": if (navigation!="portal" && navigation!="left-navigation" && navigation!="right-navigation") navigation="panel"; outerDivClass = navigation=="panel"?"portal":(type=="menu"?"vectorMenu extraMenu":"vectorTabs extraMenu"); innerDivClass = navigation=="panel"?'body':(type=='menu'?'menu':); break; case "modern": if (navigation!="mw_portlets" && navigation!="mw_contentwrapper") navigation="mw_portlets"; outerDivClass = "portlet"; innerDivClass = "pBody"; break; default: navigation="column-one"; outerDivClass = "portlet"; innerDivClass = "pBody"; break; }

//Build the DOM elements. var outerDiv = document.createElement( 'div' ); outerDiv.className = outerDivClass+" emptyPortlet"; outerDiv.id = id; var nextnode; if ( nextnode && nextnode.parentNode==root ) root.insertBefore( outerDiv, nextnode ); else root.appendChild( outerDiv );

var h5 = document.createElement( 'h5' ); if (type=='menu') { var span = document.createElement( 'span' ); span.appendChild( document.createTextNode( text ) ); h5.appendChild( span );

var a = document.createElement( 'a' ); a.href = "#"; var span = document.createElement( 'span' ); span.appendChild( document.createTextNode( text ) ); a.appendChild( span ); h5.appendChild( a ); } else h5.appendChild( document.createTextNode( text ) ); outerDiv.appendChild( h5 );

var innerDiv = document.createElement( 'div' ); //not strictly necessary with type vectorTabs, or other skins. innerDiv.className = innerDivClass; outerDiv.appendChild(innerDiv);

var ul = document.createElement( 'ul' ); innerDiv.appendChild( ul );

return outerDiv; }

//Build a portlet menu if it doesn't exist yet, and add the portlet link. function twAddPortletLink( href, text, id, tooltip, accesskey, nextnode ) { if (TwinkleConfig.portletArea) twAddPortlet(TwinkleConfig.portletArea, TwinkleConfig.portletId, TwinkleConfig.portletName, TwinkleConfig.portletType, TwinkleConfig.portletNext); mw.util.addPortletLink( TwinkleConfig.portletId, href, text, id, tooltip, accesskey, nextnode ); }

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

/**

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

QuickForm = function QuickForm( event, eventType ) {

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

var cssNode = document.createElement('style'); cssNode.type = 'text/css'; cssNode.rel = 'stylesheet'; cssNode.appendChild( document.createTextNode("")); // Safari bugfix document.getElementsByTagName("head")[0].appendChild(cssNode); var styles = cssNode.sheet ? cssNode.sheet : cssNode.stylesSheet; styles.insertRule("form.quickform { width: 96%; margin:auto; padding: .5em; vertical-align: middle}", 0); styles.insertRule("form.quickform * { font-family: sans-serif; vertical-align: middle}", 0); styles.insertRule("form.quickform select { width: 30em; border: 1px solid gray; font-size: 1.1em}", 0); styles.insertRule("form.quickform h5 { border-top: 1px solid gray;}", 0); styles.insertRule("form.quickform textarea { width: 100%; height: 6em }", 0); styles.insertRule("form.quickform .tooltipButtonContainer { position: relative; width: 100%; }", 0); styles.insertRule("form.quickform .tooltipButton { padding: .2em; color: blue; font-weight: bold; cursor:help;}", 0); styles.insertRule(".quickformtooltip { z-index: 200; position: absolute; padding: .1em; border: 1px dotted red; background-color: Linen; font: caption; font-size: 10pt; max-width: 800px}", 0); }

QuickForm.prototype.render = function QuickFormRender() { var ret = this.root.render(); ret.names = {}; return ret;

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

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

QuickForm.element.id = 0;

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

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

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

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

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

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

var current = data.list[i];

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

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

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

var current = data.list[i];

current.type = 'option'; //must be options here

var res = this.compute( current ); node.appendChild( res[0] ); } } break; case 'field': node = document.createElement( 'fieldset' ); label = node.appendChild( document.createElement( 'legend' ) ); label.appendChild( document.createTextNode( data.label ) ); if( data.name ) { node.setAttribute( 'name', data.name ); } break; case 'checkbox': case 'radio': node = document.createElement( 'div' ); if( data.list ) { for( var i = 0; i < data.list.length; ++i ) { var cur_id = id + '_' + i; var current = data.list[i]; if( current.type == 'header' ) { // inline hack cur_node = node.appendChild( document.createElement( 'h6' ) ); cur_node.appendChild( document.createTextNode( current.label ) ); if( current.tooltip ) { QuickForm.element.generateTooltip( cur_node , current ); } continue; } cur_node = node.appendChild( document.createElement( 'div' ) ); var input = cur_node.appendChild( document.createElement( 'input' ) ); input.values = current.value; input.setAttribute( 'value', current.value ); input.setAttribute( 'name', current.name || data.name ); input.setAttribute( 'type', data.type ); input.setAttribute( 'id', cur_id );


if( current.checked ) { input.setAttribute( 'checked', 'checked' ); } if( current.disabled ) { input.setAttribute( 'disabled', 'disabled' ); } if( data.event ) { input.addEventListener( 'change', data.event, false ); } else if ( current.event ) { input.addEventListener( 'change', current.event, true ); } var label = cur_node.appendChild( document.createElement( 'label' ) ); label.appendChild( document.createTextNode( current.label ) ); label.setAttribute( 'for', cur_id ); if( current.tooltip ) { QuickForm.element.generateTooltip( label, current ); } if( current.subgroup ) { var tmpgroup = current.subgroup; if( ! tmpgroup.type ) { tmpgroup.type = data.type; } tmpgroup.name = (current.name || data.name) + '.' + tmpgroup.name;

var subgroup =this.compute( current.subgroup, cur_id )[0]; subgroup.style.marginLeft = '3em'; input.subgroup = subgroup; input.shown = false;

var event = function(e) { if( e.target.checked ) { e.target.parentNode.appendChild( e.target.subgroup ); if( e.target.type == 'radio' ) { var name = e.target.name; if( typeof( e.target.form.names[name] ) != 'undefined' ) { e.target.form.names[name].parentNode.removeChild( e.target.form.names[name].subgroup ); } e.target.form.names[name] = e.target; } } else { e.target.parentNode.removeChild( e.target.subgroup ); } } input.addEventListener( 'change', event, true ); if( current.checked ) { input.parentNode.appendChild( subgroup ); } } else if( data.type == 'radio' ) { var event = function(e) { if( e.target.checked ) { var name = e.target.name; if( typeof( e.target.form.names[name] ) != 'undefined' ) { e.target.form.names[name].parentNode.removeChild( e.target.form.names[name].subgroup ); } delete e.target.form.names[name]; } } input.addEventListener( 'change', event, true ); } } } break; case 'input': node = document.createElement( 'div' );

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

var input = node.appendChild( document.createElement( 'input' ) ); if( data.value ) { input.setAttribute( 'value', data.value ); } input.setAttribute( 'name', data.name ); input.setAttribute( 'type', 'text' ); if( data.size ) { input.setAttribute( 'size', data.size ); } if( data.disabled ) { input.setAttribute( 'disabled', 'disabled' ); } if( data.readonly ) { input.setAttribute( 'readonly', 'readonly' ); } if( data.maxlength ) { input.setAttribute( 'maxlength', data.maxlength ); } if( data.event ) { input.addEventListener( 'keyup', data.event, false ); } break; case 'dyninput': var min = data.min || 1; var max = data.max || Infinity;

node = document.createElement( 'div' );

label = node.appendChild( document.createElement( 'h5' ) ); label.appendChild( document.createTextNode( data.label ) );

var listNode = node.appendChild( document.createElement( 'div' ) );

var more = this.compute( { type: 'button', label: 'more', disabled: min >= max, event: function(e) { var area = e.target.area; var new_node = new QuickForm.element( e.target.sublist ); e.target.area.appendChild( new_node.render() );

if( ++e.target.counter >= e.target.max ) { e.target.setAttribute( 'disabled', 'disabled' ); } e.stopPropagation(); } } );

node.appendChild( more[0] ); moreButton = more[1];


var sublist = { type: '_dyninput_element', label: data.sublabel || data.label, name: data.name, value: data.value, size: data.size, remove: false, maxlength: data.maxlength, event: data.event }


for( var i = 0; i < min; ++i ) { var elem = new QuickForm.element( sublist ); listNode.appendChild( elem.render() ); } sublist.remove = true; sublist.morebutton = moreButton; sublist.listnode = listNode;

moreButton.sublist = sublist; moreButton.area = listNode; moreButton.max = max - min; moreButton.counter = 0; break; case '_dyninput_element': // Private, similar to normal input node = document.createElement( 'div' );

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

var input = node.appendChild( document.createElement( 'input' ) ); if( data.value ) { input.setAttribute( 'value', data.value ); } input.setAttribute( 'name', data.name ); input.setAttribute( 'type', 'text' ); if( data.size ) { input.setAttribute( 'size', data.size ); } if( data.maxlength ) { input.setAttribute( 'maxlength', data.maxlength ); } if( data.event ) { input.addEventListener( 'keyup', data.event, false ); } if( data.remove ) { var remove = this.compute( { type: 'button', label: 'remove', event: function(e) { var list = e.target.listnode; var node = e.target.inputnode; var more = e.target.morebutton;

list.removeChild( node ); --more.counter; more.removeAttribute( 'disabled' ); e.stopPropagation(); } } ); node.appendChild( remove[0] ); removeButton = remove[1]; removeButton.inputnode = node; removeButton.listnode = data.listnode; removeButton.morebutton = data.morebutton; } break; case 'hidden': var node = document.createElement( 'input' ); node.setAttribute( 'type', 'hidden' ); node.values = data.value; node.setAttribute( 'value', data.value ); node.setAttribute( 'name', data.name ); break; case 'header': node = document.createElement( 'h5' ); node.appendChild( document.createTextNode( data.label ) ); break; case 'div': node = document.createElement( 'div' ); break; case 'submit': node = document.createElement( 'span' ); childContainder = node.appendChild(document.createElement( 'input' )); childContainder.setAttribute( 'type', 'submit' ); if( data.label ) { childContainder.setAttribute( 'value', data.label ); } childContainder.setAttribute( 'name', data.name || 'submit' ); if( data.disabled ) { childContainder.setAttribute( 'disabled', 'disabled' ); } break; case 'button': node = document.createElement( 'span' ); childContainder = node.appendChild(document.createElement( 'input' )); childContainder.setAttribute( 'type', 'button' ); if( data.label ) { childContainder.setAttribute( 'value', data.label ); } childContainder.setAttribute( 'name', data.name ); if( data.disabled ) { childContainder.setAttribute( 'disabled', 'disabled' ); } if( data.event ) { childContainder.addEventListener( 'click', data.event, false ); } break; case 'textarea': node = document.createElement( 'div' ); if( data.label ) { label = node.appendChild( document.createElement( 'h5' ) ); label.appendChild( document.createTextNode( data.label ) ); label.setAttribute( 'for', id ); } node.appendChild( document.createElement( 'br' ) ); textarea = node.appendChild( document.createElement( 'textarea' ) ); textarea.setAttribute( 'name', data.name ); if( data.cols ) { textarea.setAttribute( 'cols', data.cols ); } if( data.rows ) { textarea.setAttribute( 'rows', data.rows ); } if( data.disabled ) { textarea.setAttribute( 'disabled', 'disabled' ); } if( data.readonly ) { textarea.setAttribute( 'readonly', 'readonly' ); } if( data.value ) { textarea.value = data.value; } break;

}

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

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

return [ node, childContainder ]; }

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

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

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

/*

* returns an array containing the values of elements with the given name, that has it's
* checked property set to true. (i.e. a checkbox or a radiobutton is checked), or select options
* that have selected set to true. (don't try to mix selects with radio/checkboxes, please)
* Type is optional and can specify if either radio or checkbox (for the event
* that both checkboxes and radiobuttons have the same name.
*/

HTMLFormElement.prototype.getChecked = function( name, type ) { var elements = this.elements[name]; if( !elements ) { // if the element doesn't exists, return null. return null; } var return_array = []; if( elements instanceof HTMLSelectElement ) { var options = elements.options; for( var i = 0; i < options.length; ++i ) { if( options[i].selected ) { if( options[i].values ) { return_array.push( options[i].values ); } else { return_array.push( options[i].value ); }

} } } else if( elements instanceof HTMLInputElement ) { if( type != null && elements.type != type ) { return []; } else if( elements.checked ) { return [ elements.value ]; } } else { for( var i = 0; i < elements.length; ++i ) { if( elements[i].checked ) { if( type != null && elements[i].type != type ) { continue; } if( elements[i].values ) { return_array.push( elements[i].values ); } else { return_array.push( elements[i].value ); } } } } return return_array; }

/*

* returns an array containing the values of elements with the given name, that has non-empty strings
* type is "text" or given.
*/

HTMLFormElement.prototype.getTexts = function( name, type ) { type == type || 'text'; var elements = this.elements[name]; if( !elements ) { // if the element doesn't exists, return null. return null; } var return_array = []; for( var i = 0; i < elements.length; ++i ) { if( elements[i].value != ) { return_array.push( elements[i].value ); } } return return_array; } /**

  • Will escape a string to be used in a RegExp
  • /

RegExp.escape = function( text, space_fix ) {

if ( !arguments.callee.sRE ) { arguments.callee.sRE = /(\/|\.|\*|\+|\?|\||\(|\)|\[|\]|\{|\}|\\|\$|\^)/g; }

text = text.replace( arguments.callee.sRE , '\\$1' );

// Special Mediawiki escape, underscore/space is the same, often at lest:

if( space_fix ) { text = text.replace( / |_/g, '[_ ]' ); }

return text;

}

// Sprintf implementation based on perl similar function sprintf() { if( arguments.length == 0 ) { throw "Not enough arguments for sprintf"; } var result = ""; var format = arguments[0];

var index = 1; var current_index = 1; var flags = {}; var in_operator = false; var relative = false; var precision = false; var fixed = false; var vector = false; var vector_delimiter = '.';


for( var i = 0; i < format.length; ++i ) { var current_char = format.charAt(i); if( in_operator ) { switch( current_char ) { case 'i': current_char = 'd'; break; case 'F': current_char = 'f'; break; case '%': case 'c': case 's': case 'd': case 'u': case 'o': case 'x': case 'e': case 'f': case 'g': case 'X': case 'E': case 'G': case 'b': var value = arguments[current_index]; if( vector ) { r = value.toString().split( ); result += value.toString().split().map( function( value ) { return sprintf.format( current_char, value.charCodeAt(), flags ); }).join( vector_delimiter ); } else { result += sprintf.format( current_char, value, flags ); } if( !fixed ) { ++index; } current_index = index; flags = {}; relative = false; in_operator = false; precision = false; fixed = false; vector = false; vector_delimiter = '.'; break; case 'v': vector = true; break; case ' ': case '0': case '-': case '+': case '#': flags[current_char] = true; break; case '*': relative = true; break; case '.': precision = true; break; } if( /\d/.test( current_char ) ) { var num = parseInt( format.substr( i ) ); var len = num.toString().length; i += len - 1; var next = format.charAt( i + 1 ); if( next == '$' ) { if( num <= 0 || num >= arguments.length ) { throw "out of bound"; } if( relative ) { if( precision ) { flags['precision'] = arguments[num]; precision = false; } else if( format.charAt( i + 2 ) == 'v' ) { vector_delimiter = arguments[num]; }else { flags['width'] = arguments[num]; } relative = false; } else { fixed = true; current_index = num; } ++i; } else if( precision ) { flags['precision'] = num; precision = false; } else { flags['width'] = num; } } else if ( relative && !/\d/.test( format.charAt( i + 1 ) ) ) { if( precision ) { flags['precision'] = arguments[current_index]; precision = false; } else if( format.charAt( i + 1 ) == 'v' ) { vector_delimiter = arguments[current_index]; } else { flags['width'] = arguments[current_index]; } ++index; if( !fixed ) { current_index++; } relative = false; } } else { if( current_char == '%' ) { in_operator = true; continue; } else { result += current_char; continue; } } } return result; }

sprintf.format = function sprintfFormat( type, value, flags ) {

// Similar to how perl printf works if( value == undefined ) { if( type == 's' ) { return ; } else { return '0'; } }

var result; var prefix = ; var fill = ; var fillchar = ' '; switch( type ) { case '%': result = '%'; break; case 'c': result = String.fromCharCode( parseInt( value ) ); break; case 's': result = value.toString(); break; case 'd': result = parseInt( value ).toString(); break; case 'u': result = Math.abs( parseInt( value ) ).toString(); // it's not correct, but JS lacks unsigned ints break; case 'o': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(8); break; case 'x': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16); break; case 'b': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(2); break; case 'e': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toExponential( digits ).toString(); break; case 'f': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toFixed( digits ).toString(); case 'g': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toPrecision( digits ).toString(); break; case 'X': result = (new Number( Math.abs( parseInt( value ) ) ) ).toString(16).toUpperCase(); break; case 'E': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toExponential( digits ).toString().toUpperCase(); break; case 'G': var digits = flags['precision'] ? flags['precision'] : 6; result = (new Number( value ) ).toPrecision( digits ).toString().toUpperCase(); break; }

if(flags['+'] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) { prefix = '+'; }

if(flags[' '] && parseFloat( value ) > 0 && ['d','e','f','g','E','G'].indexOf(type) != -1 ) { prefix = ' '; }

if( flags['#'] && parseInt( value ) != 0 ) { switch(type) { case 'o': prefix = '0'; break; case 'x': case 'X': prefix = '0x'; break; case 'b': prefix = '0b'; break; } }

if( flags['0'] && !flags['-'] ) { fillchar = '0'; }

if( flags['width'] && flags['width'] > ( result.length + prefix.length ) ) { var tofill = flags['width'] - result.length - prefix.length; for( var i = 0; i < tofill; ++i ) { fill += fillchar; } }

if( flags['-'] && !flags['0'] ) { result += fill; } else { result = fill + result; }

return prefix + result; }

Bytes = function ( value ) { if( typeof(value) == 'string' ) { var res = /(\d+) ?(\w?)(i?)B?/.exec( value ); var number = res[1]; var mag = res[2]; var si = res[3];

if( ! number ) { this.number = 0; return; }

if( !si ) { this.value = number * Math.pow( 10, Bytes.magnitudes[mag] * 3 ); } else { this.value = number * Math.pow( 2, Bytes.magnitudes[mag] * 10 ); } } else { this.value = value; } }

Bytes.magnitudes = { : 0, 'K': 1, 'M': 2, 'G': 3, 'T': 4, 'P': 5, 'E': 6, 'Z': 7, 'Y': 8 } Bytes.rmagnitudes = { 0: , 1: 'K', 2: 'M', 3: 'G', 4: 'T', 5: 'P', 6: 'E', 7: 'Z', 8: 'Y' }

Bytes.prototype.valueOf = function() { return this.value; }

Bytes.prototype.toString = function( magnitude ) { var tmp = this.value; if( magnitude ) { var si = /i/.test(magnitude); var mag = magnitude.replace( /.*?(\w)i?B?.*/g, '$1' ); if( si ) { tmp /= Math.pow( 2, Bytes.magnitudes[mag] * 10 ); } else { tmp /= Math.pow( 10, Bytes.magnitudes[mag] * 3 ); } if( parseInt( tmp ) != tmp ) { tmp = (new Number( tmp ) ).toPrecision( 4 ); } return tmp + ' ' + mag + (si?'i':) + 'B'; } else { // si per default var current = 0; while( tmp >= 1024 ) { tmp /= 1024; ++current; } tmp = this.value / Math.pow( 2, current * 10 ); if( parseInt( tmp ) != tmp ) { tmp = (new Number( tmp ) ).toPrecision( 4 ); } return tmp + ' ' + Bytes.rmagnitudes[current] + ( current > 0 ? 'iB' : 'B' ); }

} String.prototype.ltrim = function stringPrototypeLtrim( chars ) { chars = chars || "\\s*"; return this.replace( new RegExp("^[" + chars + "]+", "g"), "" ); }

String.prototype.rtrim = function stringPrototypeRtrim( chars ) { chars = chars || "\\s*"; return this.replace( new RegExp("[" + chars + "]+$", "g"), "" ); } String.prototype.trim = function stringPrototypeTrim( chars ) { return this.rtrim(chars).ltrim(chars); }

String.prototype.splitWeightedByKeys = function stringPrototypeSplitWeightedByKeys( start, end, skip ) { if( start.length != end.length ) { throw 'start marker and end marker must be of the same length'; } var level = 0; var initial = null; var result = []; if( !( skip instanceof Array ) ) { if( typeof( skip ) == 'undefined' ) { skip = []; } else if( typeof( skip ) == 'string' ) { skip = [ skip ]; } else { throw "non-applicable skip parameter"; } } for( var i = 0; i < this.length; ++i ) { for( var j = 0; j < skip.length; ++j ) { if( this.substr( i, skip[j].length ) == skip[j] ) { i += skip[j].length - 1; continue; } } if( this.substr( i, start.length ) == start ) { if( initial == null ) { initial = i; } ++level; i += start.length - 1; } else if( this.substr( i, end.length ) == end ) { --level; i += end.length - 1; } if( level == 0 && initial != null ) { result.push( this.substring( initial, i + 1 ) ); initial = null; } }

return result; }

Array.prototype.uniq = function arrayPrototypeUniq() { var result = []; for( var i = 0; i < this.length; ++i ) { var current = this[i]; if( result.indexOf( current ) == -1 ) { result.push( current ); } } return result; }

Array.prototype.dups = function arrayPrototypeUniq() { var uniques = []; var result = []; for( var i = 0; i < this.length; ++i ) { var current = this[i]; if( uniques.indexOf( current ) == -1 ) { uniques.push( current ); } else { result.push( current ); } } return result; }

Array.prototype.chunk = function arrayChunk( size ) { if( typeof( size ) != 'number' || size <= 0 ) { // pretty impossible to do anything :) return [ this ]; // we return an array consisting of this array. } var result = []; var current; for(var i = 0; i < this.length; ++i ) { if( i % size == 0 ) { // when 'i' is 0, this is always true, so we start by creating one. current = []; result.push( current ); } current.push( this[i] ); }

   return result;

}

Unbinder = function unbinder( string ) { if( typeof( string ) != 'string' ) { throw "not a string"; } this.content = string; this.counter = 0; this.history = {}; this.prefix = '%UNIQ::' + Math.random() + '::'; this.postfix = '::UNIQ%'; }

Unbinder.prototype = {

 unbind: function UnbinderUnbind( prefix, postfix ) {
   var re = new RegExp( prefix + '(.*?)' + postfix, 'g' );
   this.content = this.content.replace( re, Unbinder.getCallback( this ) );
 },
 rebind: function UnbinderRebind() {
   var content = this.content;
   content.self = this;
   for( var current in this.history )
     if( this.history.hasOwnProperty( current ) )
       content = content.replace( current, this.history[current] );
   return content;
 },
 prefix: null, // %UNIQ::0.5955981644938324::
 postfix: null, // ::UNIQ%
 content: null, // string
 counter: null, // 0++
 history: null // {}

};

Unbinder.getCallback = function UnbinderGetCallback(self) {

 return function UnbinderCallback( match , a , b ) {
   var current = self.prefix + self.counter + self.postfix;
   self.history[current] = match;
   ++self.counter;
   return current;
 };

};

function clone( obj, deep ) {

 var objectClone = new obj.constructor();
 for ( var property in obj )
   if ( !deep ) {

objectClone[property] = obj[property]; }

   else if ( typeof obj[property] == 'object' ) {

objectClone[property] = clone( obj[property], deep ); }

   else {

objectClone[property] = obj[property]; }

 return objectClone;

}

namespaces = { '-2': 'Media', '-1': 'Special', '0' : , '1' : 'Talk', '2' : 'User', '3' : 'User_talk', '4' : 'Project', '5' : 'Project talk', '6' : 'Image', '7' : 'Image talk', '8' : 'MediaWiki', '9' : 'MediaWiki talk', '10': 'Template', '11': 'Template talk', '12': 'Help', '13': 'Help talk', '14': 'Category', '15': 'Category talk', '100': 'Portal', '101': 'Portal talk' }; function ln( ns, title ) { var ns2ln = { '0' : 'la', '1' : 'lat', '2' : 'lu', '3' : 'lut', '4' : 'lw', '5' : 'lwt', '6' : 'li', '7' : 'lit', '8' : 'lm', '9' : 'lmt', '10': 'lt', '11': 'ltt', '12': 'lh', '13': 'lht', '14': 'lc', '15': 'lct', '100': 'lp', '101': 'lpt' }; return "\{\{" + ns2ln[ns] + "|" + title + "\}\}"; } Namespace = { MAIN: 0, TALK: 1, USER: 2, USER_TALK: 3, PROJECT: 4, PROJECT_TALK: 5, IMAGE: 6, IMAGE_TALK: 7, FILE: 6, FILE_TALK: 7, MEDIAWIKI: 8, MEDIAWIKI_TALK: 9, TEMPLATE: 10, TEMPLATE_TALK: 11, HELP: 12, HELP_TALK: 13, CATEGORY: 14, CATEGORY_TALK: 15, PORTAL: 100, PORTAL_TALK: 101, MEDIA: -2, SPECIAL: -1,

"": 0, WIKIPEDIA: 4, WIKIPEDIA_TALK: 5, WP: 4, WT: 5 };


// Helper functions to change case of a string String.prototype.toUpperCaseFirstChar = function() { return this.substr( 0, 1 ).toUpperCase() + this.substr( 1 ); }

String.prototype.toLowerCaseFirstChar = function() { return this.substr( 0, 1 ).toLowerCase() + this.substr( 1 ); }

String.prototype.toUpperCaseEachWord = function( delim ) { delim = delim ? delim : ' '; return this.split( delim ).map( function(v) { return v.toUpperCaseFirstChar() } ).join( delim ); }

String.prototype.toLowerCaseEachWord = function( delim ) { delim = delim ? delim : ' '; return this.split( delim ).map( function(v) { return v.toLowerCaseFirstChar() } ).join( delim ); }

/**

  • Helper functions to get the month as a string instead of a number
  • /

Date.monthNames = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; Date.monthNamesAbbrev = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];

Date.prototype.getMonthName = function() { return Date.monthNames[ this.getMonth() ]; }

Date.prototype.getMonthNameAbbrev = function() { return Date.monthNamesAbbrev[ this.getMonth() ]; } Date.prototype.getUTCMonthName = function() { return Date.monthNames[ this.getUTCMonth() ]; }

Date.prototype.getUTCMonthNameAbbrev = function() { return Date.monthNamesAbbrev[ this.getUTCMonth() ]; }

// Accessor functions for wikiediting and api-access Wikipedia = {};

// we dump all XHR here so they won't loose props Wikipedia.dump = [];

Wikipedia.numberOfActionsLeft = 0; Wikipedia.nbrOfCheckpointsLeft = 0;

Wikipedia.actionCompleted = function( self ) { if( --Wikipedia.numberOfActionsLeft <= 0 && Wikipedia.nbrOfCheckpointsLeft <= 0 ) { Wikipedia.actionCompleted.event( self ); } }

// Change per action wanted Wikipedia.actionCompleted.event = function() { new Status( Wikipedia.actionCompleted.notice, Wikipedia.actionCompleted.postfix, 'info' ); if( Wikipedia.actionCompleted.redirect != null ) { // if it isn't an url, make it an relative to self (probably this is the case) if( !/^\w+\:\/\//.test( Wikipedia.actionCompleted.redirect ) ) { Wikipedia.actionCompleted.redirect = mw.config.get('wgServer') + mw.config.get('wgArticlePath').replace( '$1', encodeURIComponent( Wikipedia.actionCompleted.redirect ).replace( /\%2F/g, '/' ) ); } window.setTimeout( function() { window.location = Wikipedia.actionCompleted.redirect } , Wikipedia.actionCompleted.timeOut ); } } wpActionCompletedTimeOut = typeof(wpActionCompletedTimeOut) == 'undefined'  ? 5000 : wpActionCompletedTimeOut; wpMaxLag = typeof(wpMaxLag) == 'undefined' ? 10 : wpMaxLag; // Maximum lag allowed, 5-10 is a good value, the higher value, the more agressive.

Wikipedia.editCount = 10; Wikipedia.actionCompleted.timeOut = wpActionCompletedTimeOut; Wikipedia.actionCompleted.redirect = null; Wikipedia.actionCompleted.notice = 'Action'; Wikipedia.actionCompleted.postfix = 'completed';

Wikipedia.addCheckpoint = function() { ++Wikipedia.nbrOfCheckpointsLeft; }

Wikipedia.removeCheckpoint = function() { if( --Wikipedia.nbrOfCheckpointsLeft <= 0 && Wikipedia.numberOfActionsLeft <= 0 ) { Wikipedia.actionCompleted.event(); } }

/*

currentAction: text, the current action (required)
query: Object, the query (required)
oninit: function, the function to call when page gotten
*/

Wikipedia.api = function( currentAction, query, oninit, statelem ) { this.currentAction = currentAction; this.query = query; this.query['format'] = 'xml'; //LET THE FORCE BE WITH YOU!!! this.oninit = oninit; if( statelem ) { statelem.status( currentAction ) } else { this.statelem = new Status( currentAction ); } ++Wikipedia.numberOfActionsLeft; } Wikipedia.api.prototype = { currentAction: , oninit: null, query: null, responseXML: null, statelem: null, counter: 0, post: function() { var xmlhttp = sajax_init_object(); Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'POST' , mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php', true); xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xmlhttp.onerror = function() { this.obj.statelem.error( "Error " + this.status + " occurred while quering the api." ); } xmlhttp.onload = function() { this.obj.responseXML = this.responseXML; if( this.obj.oninit ) { this.obj.oninit( this.obj ); } Wikipedia.actionCompleted(); }; xmlhttp.send( QueryString.create( this.query ) ); } }

/*

currentAction: text, the current action (required)
query: Object, the query (required)
oninit: function, the function to call when page gotten (required)
onsuccess: function, a function to call when post succeeded
onerror: function, a function to call when we abort failed posts
onretry: function, a function to call when we try to retry a post
*/

Wikipedia.wiki = function( currentAction, query, oninit, onsuccess, onerror, onretry ) { this.currentAction = currentAction; this.query = query; this.oninit = oninit; this.onsuccess = onsuccess; this.onerror = onerror; this.onretry = onretry; this.statelem = new Status( currentAction ); ++Wikipedia.numberOfActionsLeft; }

Wikipedia.wiki.prototype = { currentAction: , onsuccess: null, onerror: null, onretry: null, oninit: null, query: null, postData: null, responseXML: null, statelem: null, counter: 0, post: function( data ) { this.postData = data; if( Wikipedia.editCount <= 0 ) { this.query['maxlag'] = wpMaxLag; // are we a bot? } else { --Wikipedia.editCount; }

var xmlhttp = sajax_init_object(); Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'POST' , mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?useskin=monobook&' + QueryString.create( this.query ), true); xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xmlhttp.onerror = function(e) { var self = this.obj; self.statelem.error( "Error " + this.status + " occurred while posting the document." ); } xmlhttp.onload = function(e) { var self = this.obj; var status = this.status; if( status != 200 ) { if( status == 503 ) { var retry = this.getResponseHeader( 'Retry-After' ); var lag = this.getResponseHeader( 'X-Database-Lag' ); if( lag ) { self.statelem.warn( "current lag of " + lag + " seconds is more than our defined maximum lag of " + wpMaxLag + " seconds, will retry in " + retry + " seconds" ); window.setTimeout( function( self ) { self.post( self.postData ); }, retry * 1000, self ); return; } else { self.statelem.error( "Error " + status + " occurred while posting the document." ); } } return; } var xmlDoc; xmlDoc = self.responseXML = this.responseXML; var xpathExpr = 'boolean(//div[@class=\'previewnote\']/p/strong[contains(.,\'Sorry! We could not process your edit due to a loss of session data\')])'; var nosession = xmlDoc.evaluate( xpathExpr, xmlDoc, null, XPathResult.BOOLEAN_TYPE, null ).booleanValue; if( nosession ) { // Grabbing the shipping token, and repost var new_token = xmlDoc.evaluate( '//input[@name="wfEditToken"]/@value', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue; self.postData['wfEditToken'] = new_token; self.post( self.postData ); } else { if( self.onsuccess ) { self.onsuccess( self ); } else { var link = document.createElement( 'a' ); link.setAttribute( 'href', wgArticlePath.replace( '$1', self.query['title'] ) ); link.setAttribute( 'title', self.query['title'] ); link.appendChild( document.createTextNode( self.query['title'] ) );

self.statelem.info( [ 'completed (' , link , ')' ] ); } Wikipedia.actionCompleted(); } }; xmlhttp.send( QueryString.create( this.postData ) ); }, get: function() { this.onloading( this ); var redirect_query = { 'action': 'query', 'titles': this.query['title'], 'redirects': }

var wikipedia_api = new Wikipedia.api( "resolving eventual redirect", redirect_query, this.postget, this.statelem ); wikipedia_api.parent = this; wikipedia_api.post(); }, postget: function() { var xmlDoc = self.responseXML = this.responseXML; var to = xmlDoc.evaluate( '//redirects/r/@to', xmlDoc, null, XPathResult.STRING_TYPE, null ).stringValue; if( !this.parent.followRedirect ) { this.parent.statelem.info('ignoring eventual redirect'); } else if( to ) { this.parent.query['title'] = to; } this.parent.onloading( this ); var xmlhttp = sajax_init_object(); Wikipedia.dump.push( xmlhttp ); xmlhttp.obj = this.parent; xmlhttp.overrideMimeType('text/xml'); xmlhttp.open( 'GET' , mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/index.php?useskin=monobook&' + QueryString.create( this.parent.query ), true); xmlhttp.onerror = function() { var self = this.obj; self.statelem.error( "Error " + this.status + " occurred while receiving the document." ); } xmlhttp.onload = function() { this.obj.onloaded( this.obj ); this.obj.responseXML = this.responseXML; this.obj.responseText = this.responseText; this.obj.oninit( this.obj ); }; xmlhttp.send( null ); }, onloading: function() { this.statelem.status( 'loading data...' ); }, onloaded: function() { this.statelem.status( 'data loaded...' ); } }

Number.prototype.zeroFill = function( length ) { var str = this.toFixed(); if( !length ) { return str; } while( str.length < length ) { str = '0' + str; } return str; }

Mediawiki = {};

Mediawiki.Template = { parse: function( text, start ) { var count = -1; var level = -1; var equals = -1; var current = ; var result = { name: , parameters: {} };

for( var i = start; i < text.length; ++i ) { var test3 = text.substr( i, 3 ); if( test3 == '\{\{\{' ) { current += '\{\{\{'; i += 2; ++level; continue; } if( test3 == '\}\}\}' ) { current += '\}\}\}'; i += 2; --level; continue; } var test2 = text.substr( i, 2 ); if( test2 == '\{\{' || test2 == '\[\[' ) { current += test2; ++i; ++level; continue; } if( test2 == '\]\]' ) { current += test2; ++i; --level; continue; } if( test2 == '\}\}' ) { current += test2; ++i; --level;

if( level <= 0 ) { if( count == -1 ) { result.name = current.substring(2).trim(); ++count; } else { if( equals != -1 ) { var key = current.substring( 0, equals ).trim(); var value = current.substring( equals ).trim(); result.parameters[key] = value; equals = -1; } else { result.parameters[count] = current; ++count; } } break; } continue; }

if( text.charAt(i) == '|' && level <= 0 ) { if( count == -1 ) { result.name = current.substring(2).trim(); ++count; } else { if( equals != -1 ) { var key = current.substring( 0, equals ).trim(); var value = current.substring( equals + 1 ).trim(); result.parameters[key] = value; equals = -1; } else { result.parameters[count] = current; ++count; } } current = ; } else if( equals == -1 && text.charAt(i) == '=' && level <= 0 ) { equals = current.length; current += text.charAt(i); } else { current += text.charAt(i); } }

return result; } }

Mediawiki.Page = function mediawikiPage( text ) { this.text = text; }


Mediawiki.Page.prototype = { text: , removeLink: function( link_target ) { var first_char = link_target.substr( 0, 1 ); var link_re_string = "[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( link_target.substr( 1 ), true ); var link_simple_re = new RegExp( "\\[\\[(" + link_re_string + ")\\|?\\]\\]", 'g' ); var link_named_re = new RegExp( "\\[\\[" + link_re_string + "\\|(.+?)\\]\\]", 'g' ); if( link_simple_re.test(this.text) ) { this.text = this.text.replace( link_simple_re, "$1" ); } else { this.text = this.text.replace( link_named_re, "$1" ); } }, commentOutImage: function( image, reason ) { var unbinder = new Unbinder( this.text ); unbinder.unbind( );

reason = reason ? ' ' + reason + ': ' : ; var first_char = image.substr( 0, 1 ); var image_re_string = "[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( image.substr( 1 ), true );

/* * Check for normal image links, i.e. ... * Will eat the whole link */ var links_re = new RegExp( "\\[\\[(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string ); var allLinks = unbinder.content.splitWeightedByKeys( '', '' ).uniq(); for( var i = 0; i < allLinks.length; ++i ) { if( links_re.test( allLinks[i] ) ) { var replacement = ; unbinder.content = unbinder.content.replace( allLinks[i], replacement, 'g' ); } } // unbind the newly created comments unbinder.unbind( );

/* * Check for gallery images, i.e. instances that must start on a new line, eventually preceded with some space, and must include Image: prefix * Will eat the whole line. */ var gallery_image_re = new RegExp( "(^\\s*(?:[Ii]mage|[Ff]ile):\\s*" + image_re_string + ".*?$)", 'mg' ); unbinder.content.replace( gallery_image_re, "" );

// unbind the newly created comments unbinder.unbind( ); /* * Check free image usages, for example as template arguments, might have the Image: prefix excluded, but must be preceeded by an | * Will only eat the image name and the preceeding bar and an eventual named parameter */ var free_image_re = new RegExp( "(\\|\\s*(?:[\\w\\s]+\\=)?\\s*(?:(?:[Ii]mage|[Ff]ile):\\s*)?" + image_re_string + ")", 'mg' ); unbinder.content.replace( free_image_re, "" );

// Rebind the content now, we are done! this.text = unbinder.rebind(); }, addToImageComment: function( image, data ) { var first_char = image.substr( 0, 1 ); var image_re_string = "(?:[Ii]mage|[Ff]ile):\\s*[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( image.substr( 1 ), true ); var links_re = new RegExp( "\\[\\[" + image_re_string ); var allLinks = this.text.splitWeightedByKeys( '', '' ).uniq(); for( var i = 0; i < allLinks.length; ++i ) { if( links_re.test( allLinks[i] ) ) { var replacement = allLinks[i]; // just put it at the end? replacement = replacement.replace( /\]\]$/, '|' + data + ']]' ); this.text = this.text.replace( allLinks[i], replacement, 'g' ); } } var gallery_re = new RegExp( "^(\\s*" + image_re_string + '.*?)\\|?(.*?)$', 'mg' ); var replacement = "$1|$2 " + data; this.text = this.text.replace( gallery_re, replacement ); }, removeTemplate: function( template ) { var first_char = template.substr( 0, 1 ); var template_re_string = "(?:[Tt]emplate:)?\\s*[" + first_char.toUpperCase() + first_char.toLowerCase() + ']' + RegExp.escape( template.substr( 1 ), true ); var links_re = new RegExp( "\\\{\\\{" + template_re_string ); var allTemplates = this.text.splitWeightedByKeys( '{\{', '}}', [ '{{{', '}}}' ] ).uniq(); for( var i = 0; i < allTemplates.length; ++i ) { if( links_re.test( allTemplates[i] ) ) { this.text = this.text.replace( allTemplates[i], , 'g' ); } }

}, getText: function() { return this.text; } }

// Simple helper functions to see what groups a user might belong

function userIsInGroup( group ) {

return ( wgUserGroups != null && wgUserGroups.indexOf( group ) != -1 ) || ( wgUserGroups == null && group == 'anon' ); }

function userIsAnon() { return wgUserGroups == null; }

// AOL Proxy IP Addresses (2007-02-03) var AOLNetworks = [ '64.12.96.0/19', '149.174.160.0/20', '152.163.240.0/21', '152.163.248.0/22', '152.163.252.0/23', '152.163.96.0/22', '152.163.100.0/23', '195.93.32.0/22', '195.93.48.0/22', '195.93.64.0/19', '195.93.96.0/19', '195.93.16.0/20', '198.81.0.0/22', '198.81.16.0/20', '198.81.8.0/23', '202.67.64.128/25', '205.188.192.0/20', '205.188.208.0/23', '205.188.112.0/20', '205.188.146.144/30', '207.200.112.0/21', ];

// AOL Client IP Addresses (2007-02-03) var AOLClients = [ '172.128.0.0/10', '172.192.0.0/12', '172.208.0.0/14', '202.67.66.0/23', '172.200.0.0/15', '172.202.0.0/15', '172.212.0.0/14', '172.216.0.0/16', '202.67.68.0/22', '202.67.72.0/21', '202.67.80.0/20', '202.67.96.0/19', ];

/**

  • ipadress is in the format 1.2.3.4 and network is in the format 1.2.3.4/5
  • /

function isInNetwork( ipaddress, network ) { var iparr = ipaddress.split('.'); var ip = (parseInt(iparr[0]) << 24) + (parseInt(iparr[1]) << 16) + (parseInt(iparr[2]) << 8) + (parseInt(iparr[3]));

var netmask = 0xffffffff << network.split('/')[1];

var netarr = network.split('/')[0].split('.'); var net = (parseInt(netarr[0]) << 24) + (parseInt(netarr[1]) << 16) + (parseInt(netarr[2]) << 8) + (parseInt(netarr[3]));

return (ip & netmask) == net; }

/* Returns true if given string contains a valid IP-address, that is, from 0.0.0.0 to 255.255.255.255*/ function isIPAddress( string ){ var res = /(\d{1,4})\.(\d{1,3})\.(\d{1,3})\.(\d{1,4})/.exec( string ); return res != null && res.slice( 1, 5 ).every( function( e ) { return e < 256; } ); }

/**

  • Maps the querystring to an object
  • Functions:
  • QueryString.exists(key)
  • returns true if the particular key is set
  • QueryString.get(key)
  • returns the value associated to the key
  • QueryString.equals(key, value)
  • returns true if the value associated with given key equals given value
  • QueryString.toString()
  • returns the query string as a string
  • QueryString.create( hash )
  • creates an querystring and encodes strings via encodeURIComponent and joins arrays with |
  • In static context, the value of location.search.substring(1), else the value given to the constructor is going to be used. The mapped hash is saved in the object.
  • Example:
  • var value = QueryString.get('key');
  • var obj = new QueryString('foo=bar&baz=quux');
  • value = obj.get('foo');
  • /

function QueryString(qString) { this.string = qString; this.params = {};

if( qString.length == 0 ) { return; }

qString.replace(/\+/, ' '); var args = qString.split('&');

for( var i = 0; i < args.length; ++i ) { var pair = args[i].split( '=' ); var key = decodeURIComponent( pair[0] ), value = key;

if( pair.length == 2 ) { value = decodeURIComponent( pair[1] ); }

this.params[key] = value; } }

QueryString.static = null;

QueryString.staticInit = function() { if( QueryString.static == null ) { QueryString.static = new QueryString(location.search.substring(1)); } }

QueryString.get = function(key) { QueryString.staticInit(); return QueryString.static.get(key); };

QueryString.prototype.get = function(key) { return this.params[key] ? this.params[key] : null; };

QueryString.exists = function(key) { QueryString.staticInit(); return QueryString.static.exists(key); }

QueryString.prototype.exists = function(key) { return this.params[key] ? true : false; }

QueryString.equals = function(key, value) { QueryString.staticInit(); return QueryString.static.equals(key, value); }

QueryString.prototype.equals = function(key, value) { return this.params[key] == value ? true : false; }

QueryString.toString = function() { QueryString.staticInit(); return QueryString.static.toString(); }

QueryString.prototype.toString = function() { return this.string ? this.string : null; }


QueryString.create = function( arr ) { var resarr = Array(); var editToken; // KLUGE: this should always be the last item in the query string (bug TW-B-0013) for( var i in arr ) { if( typeof arr[i] == 'undefined' ) { continue; } var res; if( arr[i] instanceof Array ){ var v = Array(); for(var j = 0; j < arr[i].length; ++j ) { v[j] = encodeURIComponent( arr[i][j] ); } res = v.join('|'); } else { res = encodeURIComponent( arr[i] ); }

               if( i == 'wpEditToken' ) {
                       editToken = res;

} else { resarr.push( encodeURIComponent( i ) + '=' + res ); } } if( typeof editToken != 'undefined' ) { resarr.push( 'wpEditToken=' + editToken ); } return resarr.join('&'); } QueryString.prototype.create = QueryString.create;

/**

  • Simple exception handling
  • /

Exception = function( message ) { this.message = message || ; this.name = "Exception"; }

Exception.prototype.what = function() { return this.message; }

function Status( text, stat, type ) { this.text = this.codify(text); this.stat = this.codify(stat); this.type = type || 'status'; this.generate(); if( stat ) { this.render(); } } Status.init = function( root ) { if( !( root instanceof Element ) ) { throw new Exception( 'object not an instance of Element' ); } while( root.hasChildNodes() ) { root.removeChild( root.firstChild ); } Status.root = root;

var cssNode = document.createElement('style'); cssNode.type = 'text/css'; cssNode.rel = 'stylesheet'; cssNode.appendChild( document.createTextNode("")); // Safari bugfix document.getElementsByTagName("head")[0].appendChild(cssNode); var styles = cssNode.sheet ? cssNode.sheet : cssNode.stylesSheet; styles.insertRule(".tw_status_status { color: SteelBlue; }", 0); styles.insertRule(".tw_status_info { color: ForestGreen; }", 0); styles.insertRule(".tw_status_warn { color: OrangeRed; }", 0); styles.insertRule(".tw_status_error { color: OrangeRed; font-weight: 900; }", 0); } Status.root = null;

Status.prototype = { stat: null, text: null, type: 'status', target: null, node: null, linked: false, link: function() { if( ! this.linked && Status.root ) { Status.root.appendChild( this.node ); this.linked = true; } }, unlink: function() { if( this.linked ) { Status.root.removeChild( this.node ); this.linked = false; } }, codify: function( obj ) { if ( ! ( obj instanceof Array ) ) { obj = [ obj ]; } var result; result = document.createDocumentFragment(); for( var i = 0; i < obj.length; ++i ) { if( typeof obj[i] == 'string' ) { result.appendChild( document.createTextNode( obj[i] ) ); } else if( obj[i] instanceof Element ) { result.appendChild( obj[i] ); } // Else cosmic radiation made something shit } return result;

}, update: function( status, type ) { this.stat = this.codify( status ); if( type ) { this.type = type; } this.render(); }, generate: function() { this.node = document.createElement( 'div' ); this.node.appendChild( document.createElement('span') ).appendChild( this.text ); this.node.appendChild( document.createElement('span') ).appendChild( document.createTextNode( ': ' ) ); this.target = this.node.appendChild( document.createElement( 'span' ) ); this.target.appendChild( document.createTextNode( ) ); // dummy node }, render: function() { this.node.className = 'tw_status_' + this.type; while( this.target.hasChildNodes() ) { this.target.removeChild( this.target.firstChild ); } this.target.appendChild( this.stat ); this.link(); }, status: function( status ) { this.update( status, 'status'); }, info: function( status ) { this.update( status, 'info'); }, warn: function( status ) { this.update( status, 'warn'); }, error: function( status ) { this.update( status, 'error'); } }

Status.status = function( text, status ) { return new Status( text, status, 'status' ); } Status.info = function( text, status ) { return new Status( text, status, 'info' ); } Status.warn = function( text, status ) { return new Status( text, status, 'error' ); } Status.error = function( text, status ) { return new Status( text, status, 'error' ); }


// Simple helper function to create a simple node function htmlNode( type, content, color ) { var node = document.createElement( type ); if( color ) { node.style.color = color; } node.appendChild( document.createTextNode( content ) ); return node; }

// A simple dragable window

function SimpleWindow( width, height ) { var stylesheet = document.createElement('style'); stylesheet.type = 'text/css'; stylesheet.rel = 'stylesheet'; stylesheet.appendChild( document.createTextNode("") ); // Safari bugfix document.getElementsByTagName("head")[0].appendChild(stylesheet); var styles = stylesheet.sheet ? stylesheet.sheet : stylesheet.styleSheet; styles.insertRule( ".simplewindow { "+ "font: x-small sans-serif;"+ "position: fixed; "+ "background-color: AliceBlue; "+ "border: 2px ridge Black; "+ "z-index: 100; "+ "}", 0 );

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

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

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

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

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

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

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

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

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

frame.style.width = Math.min(parseInt(window.innerWidth), parseInt(width)) + 'px'; frame.style.height = Math.min(parseInt(window.innerHeight), parseInt(height)) + 'px'; frame.style.top = Math.max(0, parseInt( window.innerHeight - this.height )/2 ) + 'px' ; frame.style.left = Math.max(0, parseInt( window.innerWidth - this.width )/2 ) + 'px'; var img = document.createElement( 'img' ); img.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/6/65/Crystal_button_cancel.svg/18px-Crystal_button_cancel.svg.png"; closeButton.appendChild( img );

var self = this;

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

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

   this.currentState = this.initialState;    

}

SimpleWindow.prototype = { focusLayer: 100, width: 800, height: 600,

   initialState: "Inactive",

currentState: null, // current state of finite state machine (one of 'actionTransitionFunctions' properties) focus: function(event) { this.frame.style.zIndex = ++this.focusLayer; }, close: function(event) { event.preventDefault(); document.body.removeChild( this.frame ); }, initMove: function(event) { event.preventDefault(); this.initialX = parseInt( event.clientX - this.frame.offsetLeft ); this.initialY = parseInt( event.clientY - this.frame.offsetTop ); this.frame.style.opacity = '0.5'; this.currentState = 'Move'; }, initResize: function(event) { event.preventDefault(); this.frame.style.opacity = '0.5'; this.currentState = 'Resize'; }, handleEvent: function(event) { event.preventDefault(); var actionTransitionFunction = this.actionTransitionFunctions[this.currentState][event.type]; if( !actionTransitionFunction ) { actionTransitionFunction = this.unexpectedEvent; } var nextState = actionTransitionFunction.call(this, event); if( !nextState ){ nextState = this.currentState; }

       if( !this.actionTransitionFunctions[nextState] ){

nextState = this.undefinedState(event, nextState); }

       this.currentState = nextState;

event.stopPropagation();

   },
   unexpectedEvent: function(event) { 

throw ("Handled unexpected event '" + event.type + "' in state '" + this.currentState);

       return this.initialState; 
   },  

   undefinedState: function(event, state) {
       throw ("Transitioned to undefined state '" + state + "' from state '" + this.currentState + "' due to event '" + event.type);
       return this.initialState; 
   },  

actionTransitionFunctions: {

       Inactive: {
           mouseover: function(event) { 
               return this.currentState;
           },
           mousemove: function(event) { 
               return this.currentState;
           },
           mouseup: function(event) { 
               return this.currentState;
           }
       }, 
       Move: {
           mouseover: function(event) { 

this.moveWindow( event.clientX, event.clientY );

               return this.currentState;
           },
           mousemove: function(event) { 

return this.doActionTransition("Move", "mouseover", event);

           },
           mouseup: function(event) { 

this.frame.style.opacity = '1';

               return 'Inactive';
           }
       }, 

Resize: { mouseover: function(event) { this.resizeWindow( event.clientX, event.clientY ); return this.currentState; }, mousemove: function(event) { return this.doActionTransition("Resize", "mouseover", event); }, mouseup: function(event) { this.frame.style.opacity = '1'; return 'Inactive'; } } }, doActionTransition: function(anotherState, anotherEventType, event) {

        return this.actionTransitionFunctions[anotherState][anotherEventType].call(this,event);
   },

display: function() { document.body.appendChild( this.frame ); }, setTitle: function( title ) { this.title.textContent = title; }, setWidth: function( width ) { this.frame.style.width = width; }, setHeight: function( height ) { this.frame.style.height = height; }, setContent: function( content ) { this.purgeContent(); this.addContent( content ); }, addContent: function( content ) { this.content.appendChild( content ); }, purgeContent: function( content ) { while( this.content.hasChildNodes() ) { this.content.removeChild( this.content.firstChild ); } }, moveWindow: function( x, y ) { this.frame.style.left = x - this.initialX + 'px'; this.frame.style.top = y - this.initialY + 'px'; }, resizeWindow: function( x, y ) { this.frame.style.height = Math.max( parseInt( y - this.frame.offsetTop ), 200 ) + 'px'; this.frame.style.width = Math.max( parseInt( x - this.frame.offsetLeft ), 200 ) + 'px'; } }

/*

  • Things to note:
  • - The users listed in the twinkleblacklist array will *not* be able to use Twinkle, even if they have it enabled as a
  • gadget. *However*, since javascript files are usually cached in the client's browser cache, it can take a while for
  • the blacklisting to come into effect - theoretically for up to 30 days, although usually with the next browser restart.
  • - The search method used the detect the usernames in this array is case-sensitive, so make sure that you get the
  • capitalization right! Always capitalize the first letter of a username; this is how the software formats usernames.
  • - The users on this blacklist will remain so until they are removed. The only way to restore one of these users' access to
  • Twinkle is to remove his/her name from the list. Even then, the user might need to WP:BYPASS his browser cache.
  • - Make sure that every username is wrapped in straight quotation marks ("" or ), that quotation marks or apostrophes
  • within the usernames are preceded by a backward-slash (\), and that every name EXCEPT THE LAST ONE is followed by a
  • comma. Not following these directions can cause the script to fail.
  • - Correct: http://en.wikipedia.org/w/index.php?title=User%3AAzaToth%2Fmorebits.js&diff=298609098&oldid=298609007
  • /

var twinkleBlacklistedUsers = ["Dilip rajeev", "Jackmantas", "Flaming Grunt", "Catterick", "44 sweet", "Sarangsaras", "WebHamster", "Radiopathy", "Nezzadar", "Darrenhusted", "Notpietru", "Arthur Rubin", "Wuhwuzdat", "MikeWazowski", "Lefty101"];

if(twinkleBlacklistedUsers.indexOf(wgUserName) != -1 && twinkleConfigExists) twinkleConfigExists = false;

// to check of morebits had loaded morebits_js_loaded = true;

// When Twinkle modules are imported, we can't be sure that this base module // has been loaded yet. For that reason, modules using them need // to initialize themselves using // window.TwinkleInit = (window.TwinkleInit || []).concat( someInitializationFunction ); // for maximal robustness. Looks weird, works well. $(function() { var funcs = window.TwinkleInit; window.TwinkleInit = { concat : function(func){ func(); return window.TwinkleInit;} }; //redefine the concat method used to enqueue initializers: From now on, they just execute immediately. if (funcs) for (var i=0; i<funcs.length; i++) funcs[i](); }); /**

Twinklefluff revert and antivandalism utillity
*/

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

/**

TwinkleConfig.summaryAd (string)
If ad should be added or not to summary, default TWINKLE
*/

if( typeof( TwinkleConfig.summaryAd ) == 'undefined' ) { TwinkleConfig.summaryAd = " (TW)"; }

/**

TwinkleConfig.revertMaxRevisions (int)
defines how many revision to query maximum, maximum possible is 50, default is 50
*/

if( typeof( TwinkleConfig.revertMaxRevisions ) == 'undefined' ) { TwinkleConfig.revertMaxRevisions = 50; }

/**

TwinkleConfig.userTalkPageMode may take arguments:
'window': open a new window, remember the opened window
'tab': opens in a new tab, if possible.
'blank': force open in a new window, even if a such window exist
*/

if( typeof( TwinkleConfig.userTalkPageMode ) == 'undefined' ) { TwinkleConfig.userTalkPageMode = 'window'; }

/**

TwinkleConfig.openTalkPage (array)
What types of actions that should result in opening of talk page
*/

if( typeof( TwinkleConfig.openTalkPage ) == 'undefined' ) { TwinkleConfig.openTalkPage = [ 'agf', 'norm', 'vand' ]; }

/**

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

if( typeof( TwinkleConfig.openTalkPageOnAutoRevert ) == 'undefined' ) { TwinkleConfig.openTalkPageOnAutoRevert = false; }

/**

TwinkleConfig.markRevertedPagesAsMinor (array)
What types of actions that should result in marking edit as minor
*/

if( typeof( TwinkleConfig.markRevertedPagesAsMinor ) == 'undefined' ) { TwinkleConfig.markRevertedPagesAsMinor = [ 'vand' ]; }

/**

TwinkleConfig.watchRevertedPages (array)
What types of actions that should result in forced addition to watchlist
*/

if( typeof( TwinkleConfig.watchRevertedPages ) == 'undefined' ) { TwinkleConfig.watchRevertedPages = [ 'agf', 'norm', 'vand', 'torev' ]; }

/**

TwinkleConfig.offerReasonOnNormalRevert (boolean)
If to offer a promt for extra summary reason for normal reverts, default to true
*/

if( typeof( TwinkleConfig.offerReasonOnNormalRevert ) == 'undefined' ) { TwinkleConfig.offerReasonOnNormalRevert = true; }

/**

TwinkleConfig.showRollbackLinks (array)
Where Twinkle should show rollback links (diff, others, mine, contribs)
*/

if( typeof( TwinkleConfig.showRollbackLinks ) == 'undefined' ) { TwinkleConfig.showRollbackLinks = [ 'diff', 'others' ]; }

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

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

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

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

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

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

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

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

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

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

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

var href = document.evaluate( 'a[2]/@href', current, null, XPathResult.STRING_TYPE, null ).stringValue; var tmpNode = revNode.cloneNode( true ); tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'norm' } ) ); current.appendChild( tmpNode ); var tmpNode = revVandNode.cloneNode( true ); tmpNode.firstChild.setAttribute( 'href', href + '&' + QueryString.create( { 'twinklerevert': 'vand' } ) ); current.appendChild( tmpNode ); } } } else {

if( wgCanonicalSpecialPageName == "Special:Undelete" ) { //You can't rollback deleted pages! return; }


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

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

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

var old_rev_url = document.evaluate( '//div[@id="mw-diff-otitle1"]//strong/a/@href', document, null, XPathResult.STRING_TYPE, null ).stringValue;

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

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

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

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

otitle.insertBefore( revertToRevision, otitle.firstChild );

if( document.getElementById('differences-nextlink') ) { // Not latest revision curVersion = false;

var new_rev_url = document.evaluate( '//div[@id="mw-diff-ntitle1"]//strong/a/@href', document, null, XPathResult.STRING_TYPE, null ).stringValue; var query = new QueryString( new_rev_url.split( '?', 2 )[1] ); var newrev = query.get( 'oldid' ); var revertToRevision = document.createElement('div'); revertToRevision.setAttribute( 'id', 'tw-revert-to-nrevision' ); revertToRevision.style.fontWeight = 'bold'; var revertToRevisionLink = revertToRevision.appendChild( document.createElement('a') ); revertToRevisionLink.href = "javascript:twinklefluff.revertToRevision('" + newrev + "')"; revertToRevisionLink.appendChild( spanTag( 'Black', '[' ) ); revertToRevisionLink.appendChild( spanTag( 'SaddleBrown', 'restore this version' ) ); revertToRevisionLink.appendChild( spanTag( 'Black', ']' ) ); ntitle.insertBefore( revertToRevision, ntitle.firstChild );

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

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

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

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

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

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

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

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

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

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

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

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

wgPageName = page || wgPageName; wgCurRevisionId = rev || wgCurRevisionId;

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

twinklefluff.revertToRevision = function revertToRevision( oldrev ) {

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

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

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

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

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

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

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

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

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

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

}

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

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

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


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

}

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

var good_revision = revs.snapshotItem( found );

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

self.params.count = count;

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


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

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

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

xmlDoc = self.responseXML;

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

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

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

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

var summary;

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

if( TwinkleConfig.openTalkPage.indexOf( self.params.type ) != -1 ) { Status.info( 'Info', [ 'Opening user talk page edit form for user ', htmlNode( 'strong', self.params.user ) ] );

var query = { 'title': 'User talk:' + self.params.user, 'action': 'edit', 'preview': 'yes', 'vanarticle': wgPageName.replace(/_/g, ' '), 'vanarticlerevid': wgCurRevisionId, 'vanarticlegoodrevid': self.params.goodid, 'type': self.params.type, 'count': self.params.count }

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

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

Wikipedia.actionCompleted.redirect = wgPageName; Wikipedia.actionCompleted.notice = "Reversion completed"

self.post( postData ); } }

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