User:Mrcool1122/monobook.js

Source: Wikipedia, the free encyclopedia.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// [[User:Lupin/popups.js]]

mw.loader.load(
             'https://en.wikipedia.org/w/index.php?title=User:Lupin/popups.js'
             + '&action=raw&ctype=text/javascript&dontcountme=s');

popupMaxWidth=150;

/*<pre><nowiki> */

//
// comfortable javascript editor
// by: [[User:Cacycle]]
// from: [[User:Cacycle/Editor.js]]
// decription: [[User:Cacycle/Editor]]

//
// configuration variables
//

// levels of undo (each level holds the whole text)
var undoBufferMax = undoBufferMax || 20;

// button text and summary text of summary buttons
var summaryButtons =  summaryButtons || [
  ['Copyedit',  'Copyedit'],
  ['Linkfix',   'Linkfix'],
  ['Vandal',    'Reverting vandalism'],
  ['Format',    'Formatting source text'],
  ['Cap lists', 'Capitalizing (link) lists as per [[Wikipedia:List]] and [[Wikipedia:Manual_of_Style]]']
];

// background color of preview box
var previewBackground = previewBackground || '#ffffdd';

//
// to be done:
// regexp internationalization
//

//document.getElementById('PreviewBox').innerHTML = '';
//document.getElementById('PreviewBox').innerHTML += ': "' +  + '"<br>';

//
// find and replace functions
//
function Edit(what) {

// add focus to textbox
  document.editform.wpTextbox1.focus();

// get the textarea object
  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

// get the scroll position
  var scrollTopPx = textarea.scrollTop;
  var scrollHeightPx = textarea.scrollHeight;

// convert strange spaces, remove non-\n linebreak characters
  convertStrangeSpaces();

// get the text from the textbox
  var text = textarea.value;
  var textNew;

// get the find text
  var find = document.getElementById('findText');
  if (find == null) { return; }
  var findText = find.value;

// get the replace text
  var replace = document.getElementById('replaceText');
  if (replace == null) { return; }
  var replaceText = replace.value;

// get checkboxes
  var caseSensitive = document.getElementById('caseSensitive');
  if (caseSensitive == null) { return; }
  var regExp = document.getElementById('regExp');
  if (regExp == null) { return; }

// changed flags
  var textChanged = false;
  var posChanged = false;

// get the text selection info
  var startPos = textarea.selectionStart;
  var endPos = textarea.selectionEnd;
  var selected = text.substring(startPos, endPos);
  var startPosNew;
  var endPosNew;

// set cursor to position 0 if cursor has not already been set (better solution???)
  if ( (startPos == endPos) && (startPos == text.length) ) {
    textarea.selectionStart = 0;
    textarea.selectionEnd = 0;
    startPos = 0;
    endPos = 0;
  }

// manipulate selected text
  if (selected != '') {

// lowercase selection
    if ('lowercase'.indexOf(what) >= 0) {
      var selectedNew = selected.toLowerCase();
      textNew = text.substring(0, startPos) + selectedNew + text.substring(endPos);
      startPosNew = startPos;
      endPosNew = endPos;
      textChanged = true;
    }

// bold selection
    if ('bold'.indexOf(what) >= 0) {
      var selectedNew;
      if ( /^\'\'\'.*\'\'\'$/.test(selected) ) {
        selectedNew = selected.replace(/^\'\'\'(.*)\'\'\'$/, '$1');
        startPosNew = startPos;
        endPosNew = endPos - 6 ;
      }
      else {
        selectedNew = "'''" + selected + "'''";
        startPosNew = startPos;
        endPosNew = endPos + 6;
      }
      textNew = text.substring(0, startPos) + selectedNew + text.substring(endPos);
      textChanged = true;
    }

// italic selection
    if ('italic'.indexOf(what) >= 0) {
      var selectedNew;
      if ( /^\'\'.*\'\'$/.test(selected) ) {
        selectedNew = selected.replace(/^\'\'(.*)\'\'$/, '$1');
        startPosNew = startPos;
        endPosNew = endPos - 4 ;
      }
      else {
        selectedNew = "''" + selected + "''";
        startPosNew = startPos;
        endPosNew = endPos + 4;
      }
      textNew = text.substring(0, startPos) + selectedNew + text.substring(endPos);
      textChanged = true;
    }
  }

// increase heading level
  if ('headingmore'.indexOf(what) >= 0) {
    var selectedNew = '';

// nothing selected, get current line
    if (selected == '') {
      var lineStart = text.lastIndexOf('\n', startPos - 1) + 1;
      var lineEnd = text.indexOf('\n', startPos);
      if (lineEnd < 0) {
        lineEnd = text.length;
      }
      selectedNew = text.substring(lineStart, lineEnd);

// increase heading level
      if ( /^\=\=.*\=\= *$/.test(selectedNew) ) {
        selectedNew = selectedNew.replace(/^(\=\=+) *(.*?) *(\=\=+) *$/, '=$1 $2 $3=');
      }

// make the line a heading
      else {
        selectedNew = selectedNew.replace(/(^ +| +$)/g, '');
        if (selectedNew.length < 80) {
          selectedNew = '== ' + selectedNew + ' ==';
        }
        else {
          lineStart = startPos;
          lineEnd = endPos;
          selectedNew = selected;
        }
      }
      startPosNew = lineStart;
      endPosNew = lineStart;
      textNew = text.substring(0, lineStart) + selectedNew + text.substring(lineEnd);
    }

// increase all headings in selected text
    else {
      var lines = selected.split('\n');

// cycle trough the lines
      for (var i = 0; i < lines.length; i++) {
        var line = lines[i];

// increase heading level in selected text
        if ( /^==.*== *$/.test(line) ) {
          line = line.replace(/^(==+) *(.*?) *(==+) *$/, '$1= $2 =$3');
        }
        selectedNew += line;
        if (i < lines.length - 1) {
          selectedNew += '\n';
        }
      }
      startPosNew = startPos;
      endPosNew = startPos + selectedNew.length;
      textNew = text.substring(0, startPos) + selectedNew + text.substring(endPos);
    }
    textChanged = true;
  }

// decrease heading level
  if ('headingless'.indexOf(what) >= 0) {
    var selectedNew = '';

// nothing selected, get current line
    if (selected == '') {
      var lineStart = text.lastIndexOf('\n', startPos - 1) + 1;
      var lineEnd = text.indexOf('\n', startPos);
      if (lineEnd < 0) {
        lineEnd = text.length;
      }
      selectedNew = text.substring(lineStart, lineEnd);

// decrease heading level
      if ( /^===.*=== *$/.test(selectedNew) ) {
        selectedNew = selectedNew.replace(/^=(==.*==)= *$/, '$1');
      }
      else if ( /^==.*==$/.test(selectedNew) ) {
        selectedNew = selectedNew.replace(/^== *(.*) *== *$/, '$1');
      }
      startPosNew = lineStart;
      endPosNew = lineStart;
      textNew = text.substring(0, lineStart) + selectedNew + text.substring(lineEnd);
    }

// increase all headings in selected text
    else {
      var lines = selected.split('\n');

// cycle trough the lines
      for (var i = 0; i < lines.length; i++) {
        var line = lines[i];

// decrease heading level in selected text
        if ( /^===.*=== *$/.test(line) ) {
          line = line.replace(/^=(==.*==)= *$/, '$1');
        }
        selectedNew += line;
        if (i < lines.length - 1) {
          selectedNew += '\n';
        }
      }
      startPosNew = startPos;
      endPosNew = startPos + selectedNew.length;
      textNew = text.substring(0, startPos) + selectedNew + text.substring(endPos);
    }
    textChanged = true;
  }

// replacements and text fixes
  if ('spaces html punct caps dashes units math'.indexOf(what) >= 0) {

    var startPosFix;
    var endPosFix;
    var selectedFix;

// apply to whole text if nothing is selected
    if (startPos == endPos) {
      startPosFix = 0;
      endPosFix = text.length;
      selectedFix = text;
    }
    else {
      startPosFix = startPos;
      endPosFix = endPos;
      selectedFix = selected;
    }

// apply fixes to selected text
         if ('spaces'.indexOf   (what) >= 0) { selectedFix = FixSpaces(selectedFix); }
    else if ('html'.indexOf     (what) >= 0) { selectedFix = FixHTML  (selectedFix); }
    else if ('punct'.indexOf    (what) >= 0) { selectedFix = FixPunct (selectedFix); }
    else if ('caps'.indexOf     (what) >= 0) { selectedFix = FixCaps  (selectedFix); }
    else if ('dashes'.indexOf   (what) >= 0) { selectedFix = FixDashes(selectedFix); }
    else if ('units'.indexOf    (what) >= 0) { selectedFix = FixUnits (selectedFix); }
    else if ('math'.indexOf     (what) >= 0) { selectedFix = FixMath  (selectedFix); }

// remove newlines and spaces
    selectedFix = selectedFix.replace(/\n{3,}/g, '\n\n');
    selectedFix = selectedFix.replace(/^\n+/, '');
    selectedFix = selectedFix.replace(/\n{2,}$/, '\n');

// set selection
    if (startPos == endPos) {
      startPosNew = startPos;
      endPosNew = startPos;
    }
    else {
      startPosNew = startPos;
      endPosNew = startPos + selectedFix.length;
    }

// insert selected into unchanged text
    textNew = text.substring(0, startPosFix) + selectedFix + text.substring(endPosFix);
    textChanged = true;
    posChanged = true;
  }

// prepare find regexp for find and replace
  var regExpFlags = '';
  if ('findprev findnext replaceprev replacenext replaceall'.indexOf(what) >= 0) {

// format the find text as regexp or plain text
    if (regExp.checked) {

// replace \n with newline character, other characters have already been converted
      replaceText = replaceText.replace(/((^|[^\\])(\\\\)*)\\n/g, '$1\n');
    }
    else {
      findText = findText.replace(/([\\^\$\*\+\?\.\(\)\[\]\{\}\:\=\!\|\,\-])/g, '\\$1');
    }

// set regexp flag i
    if ( ! caseSensitive.checked ) {
      regExpFlags = 'i';
    }
  }

// find / replace
  if ('findnext replacenext findprev replaceprev'.indexOf(what) >= 0) {
    if (find.value != '') {

// create regexp
      var regExpFind = new RegExp(findText, regExpFlags + 'g');

// set start position for search to right
      var indexStart;
      var result;
      if ('findnext replacenext'.indexOf(what) >= 0) {
        indexStart = startPos;
        if ( (selected.length > 0) && ('findnext'.indexOf(what) >= 0) ) {
          indexStart = startPos + 1;
        }

// execute the regexp search to the right
        regExpFind.lastIndex = indexStart;
        result = regExpFind.exec(text);
      }

// prepare search to the left
      else {

// set start position for search to left
        indexStart = startPos - 1;
        if ( (selected.length > 0) && ('replaceprev'.indexOf(what) >= 0) ) {
          indexStart = startPos;
        }

// cycle through the matches to the left
        var resultNext;
        do {
          result = resultNext;
          resultNext = regExpFind.exec(text);
          if (resultNext == null) {
            break;
          }
        } while (resultNext.index <= indexStart);
      }

// get the matched string
      var matched;
      var matchedStart;
      var matchedLength;
      if (result != null) {
        matched = result[0];
        matchedStart = result.index;
        matchedLength = matched.length;

// replace only if the next match was already selected
        if ('replacenext replaceprev'.indexOf(what) >= 0) {
          if (selected == matched) {
            var replace = selected.replace(regExpFind, replaceText);
            textNew = text.substr(0, matchedStart) + replace + text.substr(matchedStart + matched.length);
            matchedLength = replace.length;
            textChanged = true;
          }
        }

// select the found match in the textarea
        startPosNew = matchedStart;
        endPosNew = matchedStart + matchedLength;
      }
      else {
        if ('findprev replaceprev'.indexOf(what) >= 0) {
          indexStart = startPos;
        }
        startPosNew = indexStart;
        endPosNew = indexStart;
      }
      posChanged = true;
    }
  }

// replace all
  if ('replaceall'.indexOf(what) >= 0) {
    if (find.value != '') {

// create regexp
      var regExpFind = new RegExp(findText, regExpFlags + 'g');


// replace all in whole text
      if (selected == '') {

// get the new cursorposition
        textNew = text.replace(regExpFind, replaceText);
        var textbefore = textNew.substr(0, startPos);
        textbefore = textbefore.replace(regExpFind, replaceText);
        startPosNew = textbefore.length;
        endPosNew = startPosNew;
        posChanged = true;
      }

// replace all in selection
      else {
        var replace = selected.replace(regExpFind, replaceText);
        startPosNew = startPos;
        endPosNew = startPos + replace.length;
        textNew = text.substr(0, startPos) + replace + text.substr(endPos);
      }
      textChanged = true;
    }
  }

// get the find field from the selection
  if ('findnext findprev replacenext replaceprev getfind'.indexOf(what) >= 0) {
    if ( ('getfind'.indexOf(what) >= 0) || ( (find.value == '') && (selected != '') ) ) {
      if (regExp.checked) {
        find.value = selected.replace(/\n/g, '\\n');
      }
      else {
        find.value = selected.replace(/\n.*/, '');
        startPosNew = startPos;
        endPosNew = endPos;
      }
    }
  }

// jump to top / bottom
  if ('updown'.indexOf(what) >= 0) {
    if (scrollTopPx > scrollHeightPx / 2) {
      startPosNew = 0;
      endPosNew = 0
    }
    else {
      startPosNew = text.length;
      endPosNew = text.length;
    }
    posChanged = true;
  }

// change textarea, save undo info
  if (textChanged) {
    textarea.value = textNew;
    SaveUndo(text, startPos, endPos);
    SaveUndo(textNew, startPosNew, endPosNew);
  }

// set the selection range
  textarea.setSelectionRange(startPosNew, endPosNew);

// scroll the textarea to the selected text or cursor position
  if (posChanged) {
    ScrollToSelection();
  }
  else {
    textarea.scrollTop = scrollTopPx;
  }

  return;
}


//
// scroll the textarea to the selected text or cursor position
//
function ScrollToSelection() {

  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  var text = textarea.value;
  var rows = textarea.rows;
  var cols = textarea.cols;

// get total number of rows
  var totalRows = GetRows(text, rows, cols);

// determine scroll position
  if (totalRows >= rows) {
    var startPos = textarea.selectionStart;

// breaking of the cursor row depends on trailing characters
    var nextspace = text.indexOf(' ', startPos);
    var nextreturn = text.indexOf('\n', startPos);
    var tostart = text.substr(0, startPos);
    var fromstart = text.substr(startPos);
    fromstart = fromstart.replace(/\s.*/g, '');

// get the rows number of the cursor position
    var currentRow = GetRows(tostart + fromstart, rows, cols);
    currentRow = currentRow - rows / 2;
    if (currentRow < 0) {
      currentRow = 0;
    }

// set scroll position
    var scrollHeightRel = (currentRow - 1) / (totalRows);
    var scrollHeightPx = textarea.scrollHeight;
    var scrollTopPxNew = parseInt(scrollHeightRel * scrollHeightPx);
    textarea.scrollTop = scrollTopPxNew;
  }
  return;
}


//
// get number of rows for a text in a textarea
//
function GetRows(text, rows, cols) {
  var totalRows = 0;
  var lines = text.split('\n');

// cycle trough the lines
  for (var i = 0; i < lines.length; i++) {
    totalRows ++;
    var line = lines[i];

// simulate breaking of long lines
    while (line.length > cols) {

// break line before border
      var spaceBefore = line.lastIndexOf(' ', cols);
      if (spaceBefore >= 0) {
        line = line.substr(spaceBefore + 1);
      }
      else {

// break line after border
        var spaceAfter = line.indexOf(' ', cols);
        if (spaceAfter >= 0) {
          line = line.substr(spaceAfter + 1);
          line.replace(/^ */, '');
        }

// no spaces, no break
        else {
          line = '';
        }
      }
      if (line.length > 0) {
        totalRows ++;
      }
    }
  }
  return(totalRows);
}


//
// fix characters, spaces, empty lines, certain headings
//
function FixSpaces(text) {

// remove trailing spaces from lines
  text = text.replace(/ +\n/g, '\n');

// empty line before and after headings, spaces around word (lookahead)
  text = text.replace(/(\n={2,}) *([^\n]*?) *(={2,})(?=\n)/g, '\n$1 $2 $3\n\n');

// uppercase important headings
  text = text.replace(/\n== external links? ==\n/ig, '\n== External links ==\n');
  text = text.replace(/\n== see also ==\n/ig, '\n== See also ==\n');
  text = text.replace(/\n== references? ==\n/ig, '\n== References ==\n');

// add space after * # : ; (list) and after {| |- | (table)
  text = text.replace(/(^|\n)([\*\#\:\;]+|\{\||\|\-|\|\}|\|) */g, '$1$2 ');

// empty line before and after tables
  text = text.replace(/\n+(\{\|)/g, '\n\n$1');
  text = text.replace(/(\n\|\}) *([^\n]*)[\n|$]+/g, '$1\n\n$2\n\n');

// empty line before and after lists
  text = text.replace(/(^|\n)([^\*\#\:\;].*?)\n+([\*\#\:\;])/g, '$1$2\n\n$3');
  text = text.replace(/(^|\n)([\*\#\:\;].*?)\n+([^\*\#\:\;])/g, '$1$2\n\n$3');

// split into lines and change single lines, used to handle tables
  var lines = text.split('\n');
  text = '';
  var tableflag = false;
  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];

// do not change lines starting with a blank
    if ( ! line.match(/^ /) ) {

// detect table
      if ( line.match(/^(\{\||\!|\|[^}])/) ) {
        tableflag = true;
      }
      else if ( line.match(/^\|\}/) ) {
        tableflag = false;
      }

// changes only to be done in tables
      if (tableflag) {

// add spaces around ||
        line = line.replace(/ *\|\| */g, ' || ');

// to do: add spaces around | in tables without changing image markup!
      }

// changes not to be done in tables
      if ( ! tableflag) {

// empty line before and after images
        line = line.replace(/^(\[\[image:.*?\]\])/ig, '\n$1');
        line = line.replace(/(\[\[image:.*?(\[\[.*?\]\].*?)*\]\])$/ig, '$1\n');

// empty line before and after includes
        line = line.replace(/^(\{\{.*?\}\})/g, '\n$1');
        line = line.replace(/(\{\{.*?\}\})$/g, '$1\n');

// to be done: convert single newlines into spaces
//      line = line.replace(/(\n[^\n \*\#\:\;\|\{].*?)\n([^\n \*\#\:\;\|\{])/g, '$1 $2');
      }
    }

// concatenate the lines
    text += line;
    if (i < lines.length - 1) {
      text += '\n';
    }
  }

// remove spaces in wikilinks
  text = text.replace(/\[\[ *([^\n]*?) *\]\]/g, '[[$1]]');

// remove spaces in external links
  text = text.replace(/\[ *([^\n]*?) *\]/g, '[$1]');

// space around pipes in wikilinks but not in images
  text = text.replace(/(\[\[(?!image:)[^\n]+?) *\| *(.*?\]\])/ig, '$1 | $2');

// space around pipes in templates
  text = text.replace(/(\{\{)([^\n]+?)(\}\})/g,
    function (p, p1, p2, p3) {
      p2 = p2.replace(/ *(\|) */g,
        function (p, p1) {
          return(' | ');
        }
      );
      return(p1 + p2 + p3);
    }
  );

// no space around pipes before brackets
  text = text.replace(/ +\| +\]\]/g, '|]]');

// no space around pipes before curly brackets
  text = text.replace(/ +\| +\}\}/g, '|}}');

// no empty line between headings and includes
  text = text.replace(/\n(==+ [^\n]*? ==+\n)\n+(\{\{.*?\}\})/g, '$1$2');

// empty line before and after categories
  text = text.replace(/(\[\[category:[^\n]*?\]\]) */gi, '\n\n$1\n\n');

// categories not separated by empty lines (lookahead)
  text = text.replace(/(\[\[category:[^\n]*?\]\])\n*(?=\[\[category:[^\n]*?\]\])/gi, '$1\n');

  return(text);
}


//
// fix html to wikicode
//
function FixHTML(text) {

// fix basic
  text = FixSpaces(text);

// convert italic
  text = text.replace(/<i>|<\/i>/g, '\'\'');

// convert bold
  text = text.replace(/<b>|<\/b>/g, '\'\'\'');

// to do: tables

  return(text);
}


//
// fix space before punctuation marks
//
function FixPunct(text) {

// fix basic
  text = FixSpaces(text);

// remove space before .,: (; could be a definition)
  text = text.replace(/([a-zA-Z\'\"\”\]\}\)]) +([\.\,\:])/g, '$1$2');

  return(text);
}

//
// fix capitalizing of lists, linklists, images, headings
//
function FixCaps(text) {

// fix basic
  text = FixSpaces(text);

// uppercase lists
  text = text.replace(/(^|\n)([\*\#\:\;]+ \W*)(\w+)/g,
    function (p, p1, p2, p3) {
      if ( ! p3.match(/^(http|ftp)/) ) {
        p3 = p3.substr(0, 1).toUpperCase() + p3.substr(1);
      }
      return(p1 + p2 + p3);
    }
  );

// uppercase link lists (link)
  text = text.replace(/(^|\n)([\*\#\:\;]+ \[\[)([^\n]*?)(\]\])/g,
    function (p, p1, p2, p3, p4) {

// uppercase link
      p3 = p3.replace(/^(\W*)(\w+)/g,
        function (p, p1, p2) {
          if ( ! p2.match(/^(http|ftp)/) ) {
            p2 = p2.substr(0, 1).toUpperCase() + p2.substr(1);
          }
          return(p1 + p2);
        }
      );

// uppercase comment
      p3 = p3.replace(/(\| \W*)(\w+)/g,
        function (p, p1, p2) {
          if ( ! p2.match(/^(http|ftp)/) ) {
            p2 = p2.substr(0, 1).toUpperCase() + p2.substr(1);
          }
          return(p1 + p2);
        }
      );
      return(p1 + p2 + p3 + p4);
    }
  );

// uppercase headings
  text = text.replace(/(^|\n)(==+ )(\W*\w)([^\n]* ==+(\n|$))/ig,
    function (p, p1, p2, p3, p4) {
      return(p1 + p2 + p3.toUpperCase() + p4);
    }
  );

// uppercase images
  text = text.replace(/(\[\[)image:(\w)([^\n]*\]\])/ig,
    function (p, p1, p2, p3) {
      return(p1 + 'Image:' + p2.toUpperCase() + p3);
    }
  );

  return(text);
}


//
// dash fixer - adds a tab that fixes several obvious en/em dash, minus sign, and such special characters.
// originally from User:Omegatron
//
function FixDashes(text) {

// fix basic
  text = FixSpaces(text);

// convert html entities into actual dash characters
  text = text.replace(/&mdash;/g, '—');
  text = text.replace(/&ndash;/g, '–');
  text = text.replace(/&minus;/g, '\u2212');

// convert -- and em dashes with or without spaces to em dash surrounded by spaces
  text = text.replace(/([a-zA-Z\'\"”\]\}\)]) *(--|—|&mdash;) *([a-zA-Z\'\"“\[\{\(])/g, '$1 — $3');

// convert - or en dashes with spaces to em dash character surrounded by spaces
  text = text.replace(/([a-zA-Z\'\"”\]\}])( |&nbsp;)+(\u2212|–|&ndash;) +([a-zA-Z\'\"“\[\{])/g, '$1$2— $4');

// convert hyphen next to lone number into a minus sign character
  text = text.replace(/([a-zA-Z\'\"”\]\>] )-(\d)/g, '$1\u2212$3');

// convert dashes to en dashes in dates
  text = text.replace(/([ \(][12]\d\d\d) ?(--?|—|&mdash;) ?([12]\d\d\d|\d\d)([ \),.;])/g, '$1–$3$4');

  return(text);
}


//
// unit formatter - new tab adds spaces between number and units, makes units consistent
// originally from User:Omegatron
//
function FixUnits(text) {

// fix basic
  text = FixSpaces(text);

// convert all &deg; into actual ° symbol
  text = text.replace(/&deg;/g, '°');

// convert the word ohm(s) or the html entity into the actual O symbol (Omega, not the actual ohm symbol &#8486;) and make sure it's spaced
  text = text.replace(/(\d) ?(Y|Z|E|P|T|G|M|k|K|h|da|d|c|m|µ|µ|µ|n|p|f|a|z|y)? ?(&Omega;|ohm|Ohm)s?([ ,.])/g, '$1 $2O$4');

// convert various micro symbols into the actual micro symbol, make sure it's spaced
  text = text.replace(/(\d) ?(&mu;|µ|&micro;)(g|s|m|A|K|mol|cd|rad|sr|Hz|N|J|W|Pa|lm|lx|C|V|O|F|Wb|T|H|S|Bq|Gy|Sv|kat|°C|M)([ ,.])/g, '$1 µ$3$4');

// convert capital K to lowercase k in units
  text = text.replace(/(\d) ?K(g|s|m|A|K|mol|cd|rad|sr|Hz|N|J|W|Pa|lm|lx|C|V|O|F|Wb|T|H|S|Bq|Gy|Sv|kat|°C|M)([ ,.])/g, '$1 k$2$3');

// capitalize units correctly
  text = text.replace(/(\d) ?(khz)([ ,.])/gi, '$1 kHz$3');
  text = text.replace(/(\d) ?(mhz)([ ,.])/gi, '$1 MHz$3');
  text = text.replace(/(\d) ?(ghz)([ ,.])/gi, '$1 GHz$3');
  text = text.replace(/(\d) ?(Y|Z|E|P|T|G|M|k|K|h|da|d|c|m|µ|µ|µ|n|p|f|a|z|y)?(hz|HZ)([ ,.])/g, '$1 $2Hz$4');
  text = text.replace(/(\d) ?(Y|Z|E|P|T|G|M|k|K|h|da|d|c|m|µ|µ|µ|n|p|f|a|z|y)?(pa|PA)([ ,.])/g, '$1 $2Pa$4');

// add a space before dB or B
  text = text.replace(/(\d) ?(dB|B)\b/g, '$1 $2');

// add a space before any units that were missed before
  text = text.replace(/(\d) ?(Y|Z|E|P|T|G|M|k|K|h|da|d|c|m|µ|n|p|f|a|z|y)?(g|m|A|K|mol|cd|rad|sr|Hz|N|J|W|Pa|lm|lx|C|V|O|F|Wb|T|H|S|Bq|Gy|Sv|kat|°C|M)([ ,.])/g, '$1 $2$3$4');

// separate one for seconds since they give a lot of false positives like "1970s". Only difference is mandatory prefix.
  text = text.replace(/(\d) ?(Y|Z|E|P|T|G|M|k|K|h|da|d|c|m|µ|n|p|f|a|z|y)(s)([ ,.])/g, '$1 $2$3$4');

// bps or b/s or bits/s --> bit/s
  text = text.replace(/([KkMmGgTtPpEeYyZz])(bps|bits?\/s|b\/s)/g, '$1bit/s');

// Bps or byte/s or bytes/s --> B/s
  text = text.replace(/([KkMmGgTtPpEeYyZz])(Bps|bytes?\/s)/g, '$1B/s');

// after that, make capitalization correct
  text = text.replace(/K(bit|B)\/s/g, 'k$1/s');
  text = text.replace(/m(bit|B)\/s/g, 'M$1/s');
  text = text.replace(/g(bit|B)\/s/g, 'G$1/s');
  text = text.replace(/t(bit|B)\/s/g, 'T$1/s');
  text = text.replace(/e(bit|B)\/s/g, 'E$1/s');
  text = text.replace(/y(bit|B)\/s/g, 'Y$1/s');
  text = text.replace(/z(bit|B)\/s/g, 'Z$1/s');

// fix a common error
  text = text.replace(/mibi(bit|byte)/g, 'mebi$1');

  return(text);
}


//
// math character fixer, originally from User:Omegatron
//
// DO NOT USE FOT <math> </math> WIKICODE!
//
function FixMath(text) {

// fix basic
  text = FixSpaces(text);

// convert html entities into actual dash characters
  text = text.replace(/&minus;/g, '\u2212');
  text = text.replace(/&middot;/g, '·');

// Convert dash next to a number into a minus sign character
  text = text.replace(/([^a-zA-Z0-9\,\_\{])-(\d)/g, '$1\u2212$2');

// changes 2x3 to 2×3
  text = text.replace(/(\d ?)x( ?\d)/g, '$1×$2');

// changes 10^3 to 10<sup>3</sup>
  text = text.replace(/(\d*\.?\d+)\^(\u2212?\d+\.?\d*)/g, '$1<sup>$2</sup>');

// change x^3 to x<sup>3</sup>
  text = text.replace(/([0-9a-zA-Z])\^(\u2212?\d+\.?\d*) /g, '$1<sup>$2</sup>');

// change +/- to ±
  text = text.replace(/( |\d)\+\/(-|\u2212)( |\d)/g, '$1±$3');

  return(text);
}


//
// add a tag to the summary box
//
function AddSummary(summary) {
  var text = document.editform.wpSummary;
  if (text != null) {
    if (text.value.match(/ \*\/ $/)) {
      text += ' ';
    }
    else if (text.value != '') {
      text.value += '; ';
    }
    text.value += summary;
  }
}


// 20 level undo / redo
var undoBuffer = new Array(undoBufferMax);
var undoBufferSelStart = new Array(undoBufferMax);
var undoBufferSelEnd = new Array(undoBufferMax);
var undoBufferFirst = 0;
var undoBufferLast = 0;
var undoBufferCurr = 0;


//
// save undo information
//
function SaveUndo(text, startPos, endPos) {

  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  if (undoBufferLast == 0) {
    undoBuffer[1] = textarea.value;
    undoBufferSelStart[1] = startPos;
    undoBufferSelEnd[1] = endPos;
    undoBufferCurr = 1;
    undoBufferLast = 1;
  }
  undoBufferLast++;
  undoBufferCurr = undoBufferLast;
  var slot = undoBufferLast % undoBufferMax;
  undoBuffer[slot] = text;
  undoBufferSelStart[slot] = startPos;
  undoBufferSelEnd[slot] = endPos;
}


//
//undo
//
function Undo() {

  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  if (undoBufferCurr - 1 > undoBufferLast - undoBufferMax) {
    if (undoBufferCurr - 1 >= 0) {
      undoBufferCurr--;
      var slot = undoBufferCurr % undoBufferMax;
      textarea.value = undoBuffer[slot];
      textarea.focus();
      textarea.selectionStart = undoBufferSelStart[slot];
      textarea.selectionEnd = undoBufferSelEnd[slot];
      ScrollToSelection();
    }
  }
}


//
// redo
//
function Redo() {

  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  if (undoBufferCurr + 1 <= undoBufferLast) {
    undoBufferCurr++;
    var slot = undoBufferCurr % undoBufferMax;
    var slot = undoBufferCurr % undoBufferMax;
    textarea.value = undoBuffer[slot];
    textarea.focus();
    textarea.selectionStart = undoBufferSelStart[slot];
    textarea.selectionEnd = undoBufferSelEnd[slot];
    ScrollToSelection();
  }
}

//
// resize textarea to ~100% by adapting cols
//
function ResizeTextarea() {

  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  textarea.style.width = '100%';
  var widthMax = textarea.offsetWidth;
  textarea.style.width = 'auto';

// provoke a scrollbar
  var textLength = textarea.value.length;
  var cols = textarea.cols;
  var fillstring = '\n\n';
  for (i = 0; i < cols; i++) {
    fillstring += '\n';
  }

// add the filling string
  var startPos = textarea.selectionStart;
  var endPos = textarea.selectionEnd;
  var scrollTopPx = textarea.scrollTop;
  textarea.value += fillstring;

// find optimal width
  textarea.cols = 20;
  for (var i = 64; i >= 1; i = i / 2) {
    while (textarea.offsetWidth < widthMax) {
      textarea.cols = textarea.cols + i;
    }
    textarea.cols = textarea.cols - i;
  }

// remove filling string
  textarea.value = textarea.value.substr(0, textLength);
  textarea.selectionStart = startPos;
  textarea.selectionEnd = endPos;
  textarea.scrollTop = scrollTopPx;

  return;
}


//
// convert strange spaces, remove non-\n linebreak characters
//
function convertStrangeSpaces() {
  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

  var startPos = textarea.selectionStart;
  var endPos = textarea.selectionEnd;

  var text = textarea.value;
  text = text.replace(/[\t\v\u00A0\u2028\u2029]+/g, ' ');
  text = text.replace(/[\r\f]/g, '');
  textarea.value = text;

  textarea.selectionStart = startPos;
  textarea.selectionEnd = endPos;

  return;
}


//
// setup routine for javascript editor
//
function SetupEditor() {

// at the moment this works only for mozilla browsers (Mozilla, Mozilla Firefox, Mozilla SeaMonkey)
  var name = navigator.appName.match(/Netscape/i)[0];
  if (name == null)  { return; }
  var version = navigator.appVersion.match(/\d+(\.\d+)/)[0];
  if (version == null)  { return; }
  if (version < 5.0)  { return; }

// get the textarea object
  var textarea = document.editform.wpTextbox1;
  if (textarea == null) { return; }

// setup the undo buffers and get the original text for instant change view
  undoBuffer[0] = textarea.value;
  editformOrig = textarea.value;

// set textarea size to maximal row number
  window.onresize = ResizeTextarea;
  ResizeTextarea();

// convert strange spaces, remove non-\n linebreak characters
  convertStrangeSpaces();

// add custom edit area stylesheet definition to head
  var insert = document.getElementsByTagName('head')[0];
  if (insert != null) {
    var html = '';
    html +=
      '<style type="text/css"> .customedit { font-size: smaller; padding-left: 0.1em; padding-right: 0.1em } </style>\n';
    insert.innerHTML += html;
  }

// add formatting buttons below the textarea
  var insert = document.getElementById('editpage-copywarn');
  if (insert != null) {
    var editpagecopywarn = insert.cloneNode(true);
    insert.setAttribute('id','customEditButtons');
    var old = document.getElementById('editpage-specialchars');
    old.parentNode.insertBefore(editpagecopywarn, old);
    var html = '';

// find, replace
    html += '<div style="margin-top: 0.2em; margin-left: 0;">\n';
    html += '<input class="customedit" type="button" value="Get" onclick="javascript:Edit(\'getfind\');" title="Get the find text from the selection"/>\n';
    html += '<input class="customedit" type="button" value="&larr;Find" onclick="javascript:Edit(\'findprev\');" title="Find previous"/>\n';
    html += '<input class="customedit" type="text" value="" onclick="javascript:();" id="findText" title=""/>\n';
    html += '<input class="customedit" type="button" value="Find&rarr;" onclick="javascript:Edit(\'findnext\');" title="Find next"/>\n';
    html += '<span style="margin-left: 0.5em;"></span>\n';
    html += '<input class="customedit" type="button" value="&uarr;&darr;" onclick="javascript:Edit(\'updown\');" title="Jump to the top / bottom"/>\n';
    html += '<span style="margin-left: 1em;"></span>\n';
    html += '<input class="customedit" type="button" value="&larr;" onclick="javascript:Undo();" title="Undo buttons"/>\n';
    html += '<input class="customedit" type="button" value="&rarr;" onclick="javascript:Redo();" title="Redo buttons"/>\n';
    html += '<span style="margin-left: 1em;"></span>\n';
    html += '<input class="customedit" type="button" style="font-weight: bold;" value="b" onclick="javascript:Edit(\'bold\');" title="Bold text"/>\n';
    html += '<input class="customedit" type="button" style="font-style: italic;" value="i" onclick="javascript:Edit(\'italic\');" title="Italic text"/>\n';
    html += '<input class="customedit" type="button" value="A&rarr;a" onclick="javascript:Edit(\'lowercase\');" title="Lowercase text"/>\n';
    html += '<span style="margin-left: 0.5em;"></span>\n';
    html += '<input class="customedit" type="button" value="=&larr;" onclick="javascript:Edit(\'headingless\');" title="Decrease heading level"/>\n';
    html += '<input class="customedit" type="button" value="&rarr;==" onclick="javascript:Edit(\'headingmore\');" title="Increase heading level"/>\n';
    html += '</div>\n';

// fixing functions
    html += '<div style="margin-top: 0.2em; margin-bottom: 0.5em; margin-left: 0;">\n';
    html += '<input class="customedit" type="button" value="All" onclick="javascript:Edit(\'replaceall\');" title="Replace all in whole text or selection"/>\n';
    html += '<input class="customedit" type="button" value="&larr;Repl." onclick="javascript:Edit(\'replaceprev\');" title="Replace previous"/>\n';
    html += '<input class="customedit" type="text" value="" onclick="javascript:();" id="replaceText" title=""/>\n';
    html += '<input class="customedit" type="button" value="Repl.&rarr;" onclick="javascript:Edit(\'replacenext\');" title="Replace"/>\n';
    html += '<span style="margin-left: 0.2em;"></span>\n';
    html += '<span title="Search should be case sensitive"><input class="customedit" style="margin: 0;" type="checkbox" value="1" onclick="javascript:();" id="caseSensitive"/> Case</span>\n';
    html += '<span style="margin-left: 0.2em;"></span>\n';
    html += '<span title="Search should be a regular expression"><input class="customedit" style="margin: 0;" type="checkbox" value="1" onclick="javascript:();" id="regExp"/> Regexp</span>\n';
    html += '<span style="margin-left: 1em;">Fix:</span>\n';
    html += '<input class="customedit" type="button" value="Basic" onclick="javascript:Edit(\'spaces\');" title="Fix blanks and empty lines"/>\n';
    html += '<input class="customedit" type="button" value="&mdash;" onclick="javascript:Edit(\'dashes\');" title="Fix dashes"/>\n';
    html += '<input class="customedit" type="button" value="k&Omega;" onclick="javascript:Edit(\'units\');" title="Fix units"/>\n';
    html += '<input class="customedit" type="button" value="&radic;" onclick="javascript:Edit(\'math\');" title="Fix math, DO NOT USE ON <math></math> WIKICODE!!!"/>\n';
    html += '<input class="customedit" type="button" value="html" onclick="javascript:Edit(\'html\');" title="Fix html to wikicode"/>\n';
    html += '<input class="customedit" type="button" value=".,:" onclick="javascript:Edit(\'punct\');" title="Fix spaces before puntuation"/>\n';
    html += '<input class="customedit" type="button" value="Aa" onclick="javascript:Edit(\'caps\');" title="Fix caps in headers and lists"/>\n';
    html += '</div>\n';
    insert.innerHTML = html;
  }

// add summary buttons
  var old = document.getElementById('wpSummary');
  var insert = document.createElement('span');
  insert.setAttribute('id','instantButtons');
  old.parentNode.insertBefore(insert, old.nextSibling);
  if (insert != null) {
    var html = '';
    html += '<span style="margin-left: 1em;">\n';
    var summary;
    for (var i = 0; i < summaryButtons.length; i++) {
      html += '<input type="button" class="customedit" value="' + summaryButtons[i][0] + '" onclick="javascript:AddSummary(\'' + summaryButtons[i][1] + '\');" title="Add to summary: ' + summaryButtons[i][1] + '"/>\n';
    }
    html += '</span>\n';
    insert.innerHTML = html;
  }

// add preview and changes buttons
  var old = document.getElementById('wpPreview');
  var insert = document.createElement('span');
  insert.setAttribute('id','customPreviewButtons');
  old.parentNode.insertBefore(insert, old);
  if (insert != null) {
    var html = '';
    html += '<span style="margin-left: 0.5em; margin-right: 0.5em">\n';
    html += 'Instant:\n';
    html += '<input type="button" class="customedit" title="Show a preview below" value="Preview" onclick="document.getElementById(\'PreviewBox\').innerHTML = wiki2html(editform.wpTextbox1.value);" />\n';
    html += '<input type="button" class="customedit" title="Show changes since your last preview below" value="Changes" onclick="document.getElementById(\'PreviewBox\').innerHTML = diffString(editformOrig, editform.wpTextbox1.value);" />\n';
    html += '<input type="button" class="customedit" title="Clear the preview box" value="Clear" onclick="document.getElementById(\'PreviewBox\').innerHTML = \'\';" />\n';
    html += '</span>\n';
    html += 'Server:\n';
    insert.innerHTML = html;
  }

// shorten button and tab texts
  document.getElementById('wpPreview').value = 'Preview';
  document.getElementById('wpDiff').value = 'Changes';

// add preview box
  var old = document.getElementById('editpage-copywarn');
  var insert = document.createElement('div');
  insert.setAttribute('id','customPreviewBox');
  old.parentNode.insertBefore(insert, old);
  if (insert != null) {
    var html = '';
    html += '<div style="margin-top: 1em; margin-bottom: 0.5em; border-width: 1px; border-style: solid; border-color: #808080 #d0d0d0 #d0d0d0 #808080; id="PreviewBoxOutline">\n';
    html += '<div style="padding: 5px; border-width: 1px; border-style: solid; border-color: #404040 #ffffff #ffffff #404040; background-color: ' + previewBackground + ';" id="PreviewBox">\n';
    html += '</div>\n';
    html += '</div>\n';
    insert.innerHTML = html;
  }
  return;
}


/*
 * Javascript Diff Algorithm
 *  By John Resig (http://ejohn.org/)
 *
 * More Info:
 *  http://ejohn.org/projects/javascript-diff-algorithm/
 */

function diffString( o, n ) {

// added by cacycle:
  o = o.replace(/\</g, '&lt;');
  n = n.replace(/\</g, '&lt;');
  o = o.replace(/\>/g, '&gt;');
  n = n.replace(/\>/g, '&gt;');
  o = o.replace(/(sort|watch)/, ''); // its funny, it doesn't work if the text contains 'sort', 'watch'
  n = n.replace(/sort/, '&#115;ort');

  var out = diff( o.split(/\s+/), n.split(/\s+/) );
  var str = "";

  for ( var i = 0; i < out.n.length - 1; i++ ) {
    if ( out.n[i].text == null ) {
      if ( out.n[i].indexOf('"') == -1 && out.n[i].indexOf('<') == -1 )
        str += "<ins style='background:#E6FFE6;'> " + out.n[i] +"</ins>";
      else
        str += " " + out.n[i];
    } else {
      var pre = "";
      if ( out.n[i].text.indexOf('"') == -1 && out.n[i].text.indexOf('<') == -1 ) {

        var n = out.n[i].row + 1;
        while ( n < out.o.length && out.o[n].text == null ) {
          if ( out.o[n].indexOf('"') == -1 && out.o[n].indexOf('<') == -1 && out.o[n].indexOf(':') == -1 && out.o[n].indexOf(';') == -1 )
            pre += " <del style='background:#FFE6E6;'>" + out.o[n] +" </del>";
          n++;
        }
      }
      str += " " + out.n[i].text + pre;
    }
  }

  return str;
}

function diff( o, n ) {
  var ns = new Array();
  var os = new Array();

  for ( var i = 0; i < n.length; i++ ) {
    if ( ns[ n[i] ] == null )
      ns[ n[i] ] = { rows: new Array(), o: null };
    ns[ n[i] ].rows.push( i );
  }

  for ( var i = 0; i < o.length; i++ ) {
    if ( os[ o[i] ] == null )
      os[ o[i] ] = { rows: new Array(), n: null };
    os[ o[i] ].rows.push( i );
  }

  for ( var i in ns ) {
    if ( ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1 ) {
      n[ ns[i].rows[0] ] = { text: n[ ns[i].rows[0] ], row: os[i].rows[0] };
      o[ os[i].rows[0] ] = { text: o[ os[i].rows[0] ], row: ns[i].rows[0] };
    }
  }

  for ( var i = 0; i < n.length - 1; i++ ) {
    if ( n[i].text != null && n[i+1].text == null && o[ n[i].row + 1 ].text == null &&
         n[i+1] == o[ n[i].row + 1 ] ) {
      n[i+1] = { text: n[i+1], row: n[i].row + 1 };
      o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1 };
    }
  }

  for ( var i = n.length - 1; i > 0; i-- ) {
    if ( n[i].text != null && n[i-1].text == null && o[ n[i].row - 1 ].text == null &&
         n[i-1] == o[ n[i].row - 1 ] ) {
      n[i-1] = { text: n[i-1], row: n[i].row - 1 };
      o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1 };
    }
  }

  return { o: o, n: n };
}


/* </nowiki></pre> */