Участник:AS/EditTools.js

Материал из Википедии — свободной энциклопедии
/*!
 * jQuery Color Utilities
 *
 * Released under the MIT and GPL licenses.
 * Copied from MediaWiki 1.40. Removed in MediaWiki 1.41 (https://phabricator.wikimedia.org/T335723).
 */
EditToolsColorUtil = {
  /**
   * Parse CSS color strings looking for color tuples
   *
   * Based on highlightFade by Blair Mitchelmore
   * <http://jquery.offput.ca/highlightFade/>
   *
   * @param {Array|string} color
   * @return {Array}
   */
  getRGB: function ( color ) {
    var result;

    // Check if we're already dealing with an array of colors
    if ( color && Array.isArray( color ) && color.length === 3 ) {
      return color;
    }
    if ( typeof color !== 'string' ) {
      return undefined;
    }

    // Look for rgb(num,num,num)
    // eslint-disable-next-line no-cond-assign
    if ( result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec( color ) ) {
      return [
        parseInt( result[ 1 ], 10 ),
        parseInt( result[ 2 ], 10 ),
        parseInt( result[ 3 ], 10 )
      ];
    }

    // Look for rgb(num%,num%,num%)
    // eslint-disable-next-line no-cond-assign, security/detect-unsafe-regex
    if ( result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec( color ) ) {
      return [
        parseFloat( result[ 1 ] ) * 2.55,
        parseFloat( result[ 2 ] ) * 2.55,
        parseFloat( result[ 3 ] ) * 2.55
      ];
    }

    // Look for #a0b1c2
    // eslint-disable-next-line no-cond-assign
    if ( result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec( color ) ) {
      return [
        parseInt( result[ 1 ], 16 ),
        parseInt( result[ 2 ], 16 ),
        parseInt( result[ 3 ], 16 )
      ];
    }

    // Look for #fff
    // eslint-disable-next-line no-cond-assign
    if ( result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec( color ) ) {
      return [
        parseInt( result[ 1 ] + result[ 1 ], 16 ),
        parseInt( result[ 2 ] + result[ 2 ], 16 ),
        parseInt( result[ 3 ] + result[ 3 ], 16 )
      ];
    }

    // Look for rgba(0, 0, 0, 0) == transparent in Safari 3
    // eslint-disable-next-line no-cond-assign
    if ( result = /rgba\(0, 0, 0, 0\)/.exec( color ) ) {
      return EditToolsColorUtil.colors.transparent;
    }

    // Otherwise, we're most likely dealing with a named color
    return EditToolsColorUtil.colors[ color.trim().toLowerCase() ];
  },

  /**
   * Named color map
   *
   * Based on Interface by Stefan Petre
   * <http://interface.eyecon.ro/>
   *
   * @property {Object}
   */
  colors: {
    aqua: [ 0, 255, 255 ],
    azure: [ 240, 255, 255 ],
    beige: [ 245, 245, 220 ],
    black: [ 0, 0, 0 ],
    blue: [ 0, 0, 255 ],
    brown: [ 165, 42, 42 ],
    cyan: [ 0, 255, 255 ],
    darkblue: [ 0, 0, 139 ],
    darkcyan: [ 0, 139, 139 ],
    darkgrey: [ 169, 169, 169 ],
    darkgreen: [ 0, 100, 0 ],
    darkkhaki: [ 189, 183, 107 ],
    darkmagenta: [ 139, 0, 139 ],
    darkolivegreen: [ 85, 107, 47 ],
    darkorange: [ 255, 140, 0 ],
    darkorchid: [ 153, 50, 204 ],
    darkred: [ 139, 0, 0 ],
    darksalmon: [ 233, 150, 122 ],
    darkviolet: [ 148, 0, 211 ],
    fuchsia: [ 255, 0, 255 ],
    gold: [ 255, 215, 0 ],
    green: [ 0, 128, 0 ],
    indigo: [ 75, 0, 130 ],
    khaki: [ 240, 230, 140 ],
    lightblue: [ 173, 216, 230 ],
    lightcyan: [ 224, 255, 255 ],
    lightgreen: [ 144, 238, 144 ],
    lightgrey: [ 211, 211, 211 ],
    lightpink: [ 255, 182, 193 ],
    lightyellow: [ 255, 255, 224 ],
    lime: [ 0, 255, 0 ],
    magenta: [ 255, 0, 255 ],
    maroon: [ 128, 0, 0 ],
    navy: [ 0, 0, 128 ],
    olive: [ 128, 128, 0 ],
    orange: [ 255, 165, 0 ],
    pink: [ 255, 192, 203 ],
    purple: [ 128, 0, 128 ],
    violet: [ 128, 0, 128 ],
    red: [ 255, 0, 0 ],
    silver: [ 192, 192, 192 ],
    white: [ 255, 255, 255 ],
    yellow: [ 255, 255, 0 ],
    transparent: [ 255, 255, 255 ]
  },

  /**
   * Convert an RGB color value to HSL.
   *
   * Conversion formula based on
   * <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
   *
   * Adapted from <https://en.wikipedia.org/wiki/HSL_color_space>.
   *
   * Assumes `r`, `g`, and `b` are contained in the set `[0, 255]` and
   * returns `h`, `s`, and `l` in the set `[0, 1]`.
   *
   * @param {number} r The red color value
   * @param {number} g The green color value
   * @param {number} b The blue color value
   * @return {number[]} The HSL representation
   */
  rgbToHsl: function ( r, g, b ) {
    var d, h, s, l, min, max;

    r = r / 255;
    g = g / 255;
    b = b / 255;

    max = Math.max( r, g, b );
    min = Math.min( r, g, b );
    l = ( max + min ) / 2;

    if ( max === min ) {
      // achromatic
      h = s = 0;
    } else {
      d = max - min;
      s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min );
      switch ( max ) {
        case r:
          h = ( g - b ) / d + ( g < b ? 6 : 0 );
          break;
        case g:
          h = ( b - r ) / d + 2;
          break;
        case b:
          h = ( r - g ) / d + 4;
          break;
      }
      h /= 6;
    }

    return [ h, s, l ];
  },

  /**
   * Convert an HSL color value to RGB.
   *
   * Conversion formula based on
   * <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>
   *
   * Adapted from <https://en.wikipedia.org/wiki/HSL_color_space>.
   *
   * Assumes `h`, `s`, and `l` are contained in the set `[0, 1]` and
   * returns `r`, `g`, and `b` in the set `[0, 255]`.
   *
   * @param {number} h The hue
   * @param {number} s The saturation
   * @param {number} l The lightness
   * @return {number[]} The RGB representation
   */
  hslToRgb: function ( h, s, l ) {
    var r, g, b, hue2rgb, q, p;

    if ( s === 0 ) {
      r = g = b = l; // achromatic
    } else {
      hue2rgb = function ( t ) {
        if ( t < 0 ) {
          t += 1;
        }
        if ( t > 1 ) {
          t -= 1;
        }
        if ( t < 1 / 6 ) {
          return p + ( q - p ) * 6 * t;
        }
        if ( t < 1 / 2 ) {
          return q;
        }
        if ( t < 2 / 3 ) {
          return p + ( q - p ) * ( 2 / 3 - t ) * 6;
        }
        return p;
      };

      q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s;
      p = 2 * l - q;
      r = hue2rgb( h + 1 / 3 );
      g = hue2rgb( h );
      b = hue2rgb( h - 1 / 3 );
    }

    return [ r * 255, g * 255, b * 255 ];
  },

  /**
   * Get a brighter or darker rgb() value string.
   *
   * Usage:
   *
   *     getColorBrightness( 'red', +0.1 );
   *     // > "rgb(255,50,50)"
   *     getColorBrightness( 'rgb(200,50,50)', -0.2 );
   *     // > "rgb(118,29,29)"
   *
   * @param {Mixed} currentColor Current value in css
   * @param {number} mod Wanted brightness modification between -1 and 1
   * @return {string} Like `'rgb(r,g,b)'`
   */
  getColorBrightness: function ( currentColor, mod ) {
    var rgbArr = EditToolsColorUtil.getRGB( currentColor ),
      hslArr = EditToolsColorUtil.rgbToHsl( rgbArr[ 0 ], rgbArr[ 1 ], rgbArr[ 2 ] );
    rgbArr = EditToolsColorUtil.hslToRgb( hslArr[ 0 ], hslArr[ 1 ], hslArr[ 2 ] + mod );

    return 'rgb(' +
      [ parseInt( rgbArr[ 0 ], 10 ), parseInt( rgbArr[ 1 ], 10 ), parseInt( rgbArr[ 2 ], 10 ) ].join( ',' ) +
      ')';
  }
};

function spEscaped(s, i){ // /-escaped
  var escSymbols = 0;
  i--;
  while ((i > -1) && (s.charAt(i) == '/')){
    escSymbols++;
    i--;
  }
  return (escSymbols % 2 == 1) 
}
 
function spIndexOfUnescaped(s, symbol){
  var index = -1;
  for (var i=0; i < s.length; i++)
  if ((s.charAt(i) == symbol) && !spEscaped(s, i))
  {index = i; break};
  return index;
}
 
//replace $ with selection
function spReplaceSpecsymbols(s, symbols, toFunc){
  var res = '', c;
  for (var i = 0; i < s.length; i++){
    c = s.charAt(i);
    if (spEscaped(s, i))
    res += c;
    else
      if (symbols.indexOf(c) > -1)
      res += toFunc(c);
      else
      res += c;
  }
  return res;
}
 
jQuery.fn.extend({
  //handles $ symbol as selection
  insertTag: function(beginTag, endTag){ 
    return this.each(function(){
      var sel = $(this).textSelection('getSelection');
      beginTag = spSelReplace(beginTag);
      endTag = endTag ? spSelReplace(endTag) : '';
      $(this).textSelection( 'encapsulateSelection', {
        pre: beginTag || '',
        peri: '',
        post: endTag || '',
        replace: true
      });
      //fix caret position after wrapping
      if (endTag && (sel !== '')){ 
        var pos = $(this).textSelection('getCaretPosition');
        $(this).textSelection('setSelection', {start:pos - endTag.length});
      }  
 
      function spSelReplace(s){
        return spReplaceSpecsymbols(s, '/$', function(c){if (c=='/') return ''; else if (c=='$') return sel});
      };
    }); //this.each(function()
  }, // InsertTag
 
  setSelection: function(text){
    return this.textSelection('encapsulateSelection', {
     post: text,
     replace: true
    });
  },
 
  getSelection: function(text){
    return this.textSelection('getSelection');
  }
});
 
var EditTools =
{
functions: [], //"functional" insertion-links have attribute with index of it's function 

charinsertDivider : "\240",
 
extraCSS : '\
  .specialchars-table{\
    margin-bottom:7px; margin-top:5px;\
    border:1px solid #aaaaaa; border-spacing:0px;\
  }\
  .editpage-specialchars-selected{\
  }\
  table.specialchars {border: 1px solid #aaaaaa; border-collapse: collapse; width:100%;}\
  table.specialchars td {border: 2px solid #aaaaaa; padding-left: 0.2em; padding-right: 0.2em; vertical-align: top;}\
  .specialchars-table a {\
    cursor: pointer;\
  }\
  #editpage-specialchars a:hover {\
  }\
  #specialchars-tablist{font-size:80%; background-color: #f3f3f3; border:1px solid #bbbbbb;\
  position:absolute; right:99.9%; top:-1px; z-index:2}\
',
 
appendExtraCSS : function (){
   mw.util.addCSS(this.extraCSS);
},
 
chars : function(section){return document.getElementById('specialchars-subsets' + section)},
subset: function(index){return document.getElementById('spchars' + index)},
tablist : function(){return document.getElementById('specialchars-tablist')},
 
tableId : 'editpage-specialchars',
 
cookieName : 'edittoolscharsubset',
 
tabPause : 600,
 
clearPause : function(){
  clearTimeout(window.spTimeout);
},
 
tab : function(index){return $('#spTabDiv' + index)},
 
createEditTools : function ($placeholder){
  var table = document.createElement("table");
  table.setAttribute('class', 'specialchars-table');
  table.setAttribute('id', this.tableId);
  $placeholder.replaceWith(table);
 
  var cookieRe, m;
  for (var i=0; i < window.spOptions.sections.length; i++){
    cookieRe = new RegExp("(?:^|;)\\s*" + EditTools.cookieName + i + "=(\\d+)\\s*(?:;|$)");
    m = cookieRe.exec(document.cookie);
    if (m && m.length > 1)
      window.spOptions.sections[i].selected = m[1];
    else window.spOptions.sections[i].selected = window.spOptions.sections[i].defaultSubset;
  }
  $(table).on('click', 'a', function($e){
    var f = EditTools.functions[$(this).attr('data-func')];
    if (typeof f == 'function') {
      f.apply(this);
    };
  });
},
 
tablistUpdateSelection : function(){
  if (!window.spActiveTab) return;
  var tabs = this.tablist().getElementsByTagName('div');
  for (var i=0; i < tabs.length; i++)
  {
    $(tabs[i]).removeClass('editpage-specialchars-selected');
    $(tabs[i]).removeAttr('style');
  }
  for (var i=0; i < window.spOptions.sections.length; i++)
  {
    $(tabs[window.spOptions.sections[i].selected]).addClass('editpage-specialchars-selected');
    $(tabs[window.spOptions.sections[i].selected]).css('background-color', EditToolsColorUtil.getColorBrightness(window.spOptions.sections[i].tabColor, 0.15));
  }
},
 
//rebuild table
refresh : function(){
  if (!this.created) return;
  var table = document.getElementById(this.tableId);
  $(table).empty();
  var tablist = document.createElement('div');
  tablist.setAttribute('id', 'specialchars-tablist');
  tablist.setAttribute('onmouseout', 'var children = EditTools.tablist().getElementsByTagName("*"); if (((!event.relatedTarget) || (event.relatedTarget.id != "specialchars-tablist")) && ($.inArray(event.relatedTarget, children) == -1)) {EditTools.clearPause(); window.spTimeout = setTimeout(function(){$(EditTools.tablist()).hide();}, ' + this.tabPause + ');}');
  $(document).delegate('#specialchars-tablist', 'mouseenter', function(){EditTools.clearPause();});
 
  for (var i=0; i < window.spOptions.subsets.length; i++){
    var $a = $('<a>');
    $a.text(window.spOptions.subsets[i].caption);
    $a.attr('data-spindex', i);
    $a.attr('onclick', 'EditTools.selectSubset(window.spActiveTab, ' + i + '); return false;');    
    var $div = $('<div>');
    $div.attr('data-spindex', i);
    $div.append($a);
    $(tablist).append($div);
  }
 
  for (var i=0; i < window.spOptions.sections.length; i++){
    var row = table.insertRow(-1);
    var $tab = $(row.insertCell(-1));
    $tab.attr('style', 'background-color: ' + window.spOptions.sections[i].tabColor + '; vertical-align: top;');
    var $tabDiv = $('<div>');
    $tabDiv.attr('id', 'spTabDiv' + i).attr('style', 'min-height: 1em; position:relative; padding:2px;').appendTo($tab);
    $tab.attr('data-pane', i).attr('id', 'spTab' + i);
    $(document).delegate('#spTab' + i, 'mouseenter', function(event){
     if ($(event.target).attr('data-pane')) 
       window.spActiveTab = $(event.target).attr('data-pane');
     else 
       return;
     $(EditTools.tablist()).detach().appendTo(EditTools.tab(window.spActiveTab)).show();
     EditTools.clearPause();
     EditTools.tablistUpdateSelection();
    });
    $tab.attr('onmouseout', 'if ((event.relatedTarget != EditTools.tablist()) && ($(EditTools.tablist()).find($(event.relatedTarget)).length == 0)) {EditTools.clearPause(); window.spTimeout = setTimeout(function(){$(EditTools.tablist()).hide();}, ' + this.tabPause + ');};');
    var $chars = $(row.insertCell(-1));
    $chars.attr('title', 'Клацніть, щоб вставити символи у вікно редагування');
    $chars.attr('style', 'padding-left:10px; width:100%');
    $chars.attr('id', 'specialchars-subsets' + i);
  }
  $(tablist).detach().appendTo(this.tab(0)).hide();
  for (var i=0; i < window.spOptions.sections.length; i++) {
    this.selectSubset(i, window.spOptions.sections[i].selected);
  }
},
 
createSubset: function(index){
  var subset = $(document.createElement('div'));
  subset.attr('id', 'spchars' + index);
  subset.appendTo($('#' + this.tableId)).hide();
  var tokens = window.spOptions.subsets[index].symbols;
  this.parseTokens(subset, tokens);
  return subset;
},
 
selectSubset : function(pane, newCurSubset){
  if ((window.spOptions.subsets[newCurSubset] == undefined))
    return; 
  $(this.subset(window.spOptions.sections[pane].selected)).detach().appendTo($('#' + this.tableId)).hide();
  for (var i=0; i < window.spOptions.sections.length; i++)
    if (window.spOptions.sections[i].selected == newCurSubset) 
      window.spOptions.sections[i].selected = -1;
  window.spOptions.sections[pane].selected = newCurSubset;
  //save into cookies for persistence
  try {
    var expires = new Date ();
    expires.setTime( expires.getTime() + 30 * 24 * 60 * 60 * 1000 );  // + 30 days
    document.cookie = this.cookieName + pane +"="+ newCurSubset + ";path=/;expires=" + expires.toUTCString();
  } catch (err) { }
  var pp = document.getElementById(this.tableId).getElementsByTagName('div');
  this.tablistUpdateSelection();
  var index = newCurSubset;
  var $p = $('#spchars' + index);
  if (!$p.length)
    $p = this.createSubset(index);
  $p.show();  
  $(this.chars(pane)).append($p);
},
 
parseTokens : function (paragraph, arr){
  var i, len = arr.length;
  for (i = 0; i < len; i += 1){
    if (typeof arr[i] == 'string')
      this.parseStr(paragraph, arr[i])
    else
    if (Object.prototype.toString.call(arr[i]) == '[object Array]')
      this.parseArr(paragraph, arr[i]);
    else
    if (typeof arr[i] == 'object')
      this.parseObj(paragraph, arr[i]);
  }
},
 
parseArr: function(paragraph, arr){
  this.addLink(paragraph, arr[0], arr[1] || '', {});
},
 
parseObj: function(paragraph, obj){
  if (obj.plain)
    this.addText(paragraph, obj.cap || obj.caption, {bold: obj.b || obj.bold, italic: obj.i || obj.italic});
  else 
  if (obj.html)
    this.addHtml(paragraph, obj.html);
  else {
    var $a;
    if (obj.ins || obj.insert)
      $a = this.addLink(paragraph, 
       obj.ins || obj.insert, 
       obj.cap || obj.caption, 
       {bold: obj.b || obj.bold, italic: obj.i || obj.italic});
    else
    if (obj.func || obj.function){
      $a = this.addFunc(paragraph, 
       obj.func || obj.function, 
       obj.cap || obj.caption || obj.ins, 
       {bold: obj.b || obj.bold, italic: obj.i || obj.italic});
    }
    if (obj.key){
      if (typeof obj.key == 'string')
      $a.attr('data-key', obj.key[0].toUpperCase());
      else
      if (Object.prototype.toStribg.apply(obj.key) == '[object Array]')
      $a.attr('data-key', obj.key.join(' ').toUpperCase());
    }
  }
},
 
parseStr : function (paragraph, str){
  var tokens = str.split(' '), i;
  for (i = 0; i < tokens.length; i++)
  this.addToken(paragraph, tokens[i]);
},
 
lineReplace: function(c){
  if (c =='/') return '/';
  else if (c == '_') return ' '
},
 
addToken: function(paragraph, token){
  function readModifiers(){
    var res = {bold: false, plain: false}, i = token.length-1, c;
    while ((i > -1) && !spEscaped(token, i)){
      c = token.charAt(i).toLowerCase();
      if (c == 'ж')
        res.bold = true;
      else
      if (c == 'н')
        res.italic = true;
      else
      if (c == 'п')
        res.plain = true;
      else
      break;
      token = token.substring(0, i);
      i--;
    };
    return res; 
  };
 
  var modifiers = readModifiers();
  if (modifiers.plain)
    this.addText(paragraph, token, modifiers);
  else if (token == '' || token == '_')
    this.addText(paragraph, EditTools.charinsertDivider + ' ', modifiers);
  else
    this.addLink(paragraph, token, '', modifiers);
}, 
 
insertFunc: function(e){
  EditTools.it($(this).attr('data-open').replace("'", "\'"), $(this).attr('data-close').replace("'", "\'")); 
  return false;
},
 
addLink: function(paragraph, token, name, mods){
  var tagOpen = token, tagClose = '';
  var n = spIndexOfUnescaped(token, '+');
  if (n > -1) {
    tagOpen = token.substring(0, n);
    tagClose = token.substring(n+1);
  }
  tagOpen = spReplaceSpecsymbols(tagOpen, '/_', this.lineReplace);
  tagClose = spReplaceSpecsymbols(tagClose, '/_', this.lineReplace);
  if (!name){
    name = tagOpen + tagClose;
    name = spReplaceSpecsymbols(name, '/$', function(c){if (c == '$') return ''; else if (c =='/') return ''; });   
  }
  var $a = this.addFunc(paragraph, this.insertFunc, name, mods);
  $a.attr('data-open', tagOpen).attr('data-close', tagClose);
  return $a;
},
 
addText: function(paragraph, txt, mods){
  var elem = $(document.createTextNode(spReplaceSpecsymbols(txt, '/_', this.lineReplace)+' '));
  paragraph.append(elem);
  if (mods.bold)
  elem.wrap('<b></b>');
  if (mods.italic)
  elem.wrap('<i></i>');
},
 
addHtml: function(paragraph, elem){
  paragraph.append(elem);
},
 
addFunc: function(paragraph, func, caption, mods){
  var $a = $('<a>');
  var funcIndex = $.inArray(func, EditTools.functions);
  if (funcIndex < 0)
    funcIndex = EditTools.functions.push(func) - 1;
 
  $a.attr('data-func', funcIndex);
  $a.text(caption).appendTo(paragraph);
 
  if (mods.bold)
  $a.wrap('<b></b>');
  if (mods.italic)
  $a.wrap('<i></i>');
 
  this.addText(paragraph, ' ', mods);
  return $a;
},
 
enableForAllFields : function (){
  if (typeof (insertTags) != 'function' || window.WikEdInsertTags)
    return;
  var texts = document.getElementsByTagName ('textarea');    
  for (var i = 0; i < texts.length; i++) {
    addHandler (texts[i], 'focus', EditTools.registerTextField);
  }
   // While we're at it, also enable it for input fields
  texts = document.getElementsByTagName ('input');
  for (var i = 0; i < texts.length; i++) 
    if (texts[i].type == 'text') {
      addHandler(texts[i], 'focus', EditTools.registerTextField);
    }
},
 
last_active_textfield : null,
 
registerTextField : function (evt){
  var e = evt || window.event;
  var node = e.target || e.srcElement;
  if (!node) 
    return;
  EditTools.last_active_textfield = node.id;
  return true;
},
 
getTextArea : function (){
  var txtarea = null;
  if (EditTools.last_active_textfield && EditTools.last_active_textfield != "")
    txtarea = document.getElementById (EditTools.last_active_textfield);
  if (!txtarea) {
  // Fallback option: old behaviour
    if (document.editform) 
      txtarea = document.editform.wpTextbox1;
    else {
    // Some alternate form? Take the first one we can find
      txtarea = document.getElementsByTagName ('textarea');
      if (txtarea.length > 0) txtarea = txtarea[0]; else txtarea = null;
    }
  }
  return txtarea;
},
 
it : function (beginTag, endTag){
  var textarea = EditTools.getTextArea();
  if (textarea != undefined){
    $(textarea).insertTag(beginTag, endTag);
  }
},
 
setup : function (){
  var $placeholder = $('#' + EditTools.tableId);
  if (!$placeholder.length) return;
  EditTools.appendExtraCSS();
  $('.editOptions').before($placeholder);
  $('input#wpSummary').attr('style', 'margin-bottom:3px;'); //fix margins after moving placeholder
  EditTools.createEditTools($placeholder);
  EditTools.enableForAllFields();
  EditTools.created = true;
  EditTools.refresh();
}
}; // end EditTools
 
function spTextArea(){
  return $(EditTools.getTextArea());
}
 
$(function(){
 if ((mw.config.get('wgAction') == 'edit') || (mw.config.get('wgAction') == 'submit'))
   EditTools.setup();
});