Difference between revisions of "MediaWiki:Common.js"

From Offene Naturführer BiolFlor
Jump to: navigation, search
m (Aktualisierungen von ON)
m
Line 1,220: Line 1,220:
 
function initImageZooming() {
 
function initImageZooming() {
 
   $("a[href].image img:not(.no-image-popup,.no-popup-image,.no-image-zoom,.no-zoom-image)").each(function() {
 
   $("a[href].image img:not(.no-image-popup,.no-popup-image,.no-image-zoom,.no-zoom-image)").each(function() {
     // check existing click events extension:MuletimediaViewer
+
     // check existing click events extension:MultimediaViewer
 
     var thisRawDOMelement = $(this).get(0)
 
     var thisRawDOMelement = $(this).get(0)
 
       , thisEventObject= $._data(thisRawDOMelement, 'events');
 
       , thisEventObject= $._data(thisRawDOMelement, 'events');

Revision as of 16:06, 19 February 2016

// <source lang="javascript">
// This JavaScript will be loaded for all users on every page load.
// The first version was imported from en.wikipedia.org, for authors see http://en.wikipedia.org/w/index.php?title=MediaWiki:Common.js&action=history
// Strongly modified (remove, add) since.
/*
 * dependencies:
    MediaWiki:Edittools.js
    MediaWiki:Common.js/edit.js
    MediaWiki:JKey.js
    MediaWiki:Mw-customcollapsible.js
    MediaWiki:JKeyWikiEditorHelp.js
    MediaWiki:SearchTools.js
    MediaWiki:JKeyRenumberingTool.js
    MediaWiki:Jquery.zoomImage.js not used when click event already assigned (in favour of extension:MultiMediaViewer)
    MediaWiki:JKeyTextToLeadTemplateTool.js
*/

/*global jQuery, document, screen, window, location, navigator, unescape, Image, clearTimeout, addOnloadHook, importScript, setTimeout, appendCSS, mw */ 
/* = settings for JSLint */
/* should go into mw.config: wgPageName, wgServer, wgScript, wgAction, wgCanonicalNamespace */
"use strict"; // set ECMAScript 5 Strict Mode

/**
 * @description Social media integration; using async script inserted here: 
 */
if ((mw.config.get('wgAction') === 'view')
        && (mw.config.get('wgCanonicalNamespace') !== 'Special')) {
  $("h1#firstHeading:first").prepend(
    "<!-- SOCIAL MEDIA START REMOVED, GET AGAIN FROM OLD VERSION IF IE8-9 ARE WORKING AGAIN!!!-->"
      + "<!-- SOCIAL MEDIA END -->"
  );
}

/**
 * @description  Scripts specific to Internet Explorer 
 * 
 * THIS SCRIPT IS PROBABLY WORKING, BUT NOT CLEAN JS, see JSLint
 * */
if (navigator.appName === "Microsoft Internet Explorer") {
  /* Internet Explorer ***bug fix***  Fixes horizontal scrollbar bug */
  var oldWidth, docEl = document.documentElement;
  var fixIEScroll2 = function() {
    docEl.style.overflowX = (docEl.scrollWidth - docEl.clientWidth < 4) ? "hidden" : "";
  };
  var fixIEScroll = function() {
    if (!oldWidth || docEl.clientWidth > oldWidth) {
      fixIEScroll2();
    } else {
      setTimeout(fixIEScroll2, 1);
    }
    oldWidth = docEl.clientWidth;
  };
  document.attachEvent("onreadystatechange", fixIEScroll);
  document.attachEvent("onresize", fixIEScroll);
  // In print IE (7?) does not like line-height
  appendCSS( '@media print { sup, sub, p, .documentDescription { line-height: normal; }}');
  //Import scripts specific to Internet Explorer 6
  // THIS IS A PNG transparency FIX, here commented out:
  // if (navigator.appVersion.substr(22, 1) == "6") {
  //    importScript("MediaWiki:Common.js/IE60Fixes.js");
  // }
} // END "Microsoft Internet Explorer"


/**
 * @description: helper function to escape jQuery IDs
 * @param {string} myid HTML ID
 * @returns {@exp;myid@call;replace|String}
 */ 
function jqueryEscapeId(myid) {
  if(myid.substr(0, 1) === "#"){
    return myid.replace(/(:|\.)/g,'\\$1');
  } else {
    return '#' + myid.replace(/(:|\.)/g,'\\$1');
  }
}

/**
 * Footenote tooltips from <references>
 * @description Footnotes as unformatted tooltip - from it.wikipedia.org under same license
 * Note (2015-12-21 14:19:20): Use of "addOnloadHook" is deprecated. Use jQuery instead.
 * @returns {undefined}
 */
function reference_footnote_tooltips () {
 var sups = document.getElementsByTagName("sup");
 for (var i=0; i < sups.length; i++) {
   var note_id = sups[i].childNodes[0].href;
   if (note_id && (note_id.indexOf("#") !== -1)) {
     note_id = document.getElementById(note_id.substr(note_id.indexOf("#")+1));
     if (note_id) {
       if (document.all) {
         sups[i].title = note_id.innerText;
         sups[i].childNodes[0].title = note_id.innerText;
       } else {
         sups[i].title = note_id.textContent;
      }
     }
   }
  }
}

/** @description Editing-page-specific: see also below JKeyWikiEditorHelp.js  */
if (mw.config.get('wgAction') === "edit" 
  || mw.config.get('wgAction') === "submit" 
  || mw.config.get('wgCanonicalSpecialPageName') === "Upload") {
  if (typeof EditTools === 'undefined') {
    importScript('MediaWiki:Edittools.js');
  }
  if (typeof $.wikiEditor === 'undefined') {
    importScript("MediaWiki:Common.js/edit.js");  // TODO remove or adjust? AP 2011-08-25
  }
}
/**
 * @namespace resource string dictionary
 * 
 * Note: Commons uses collapse/expand ▲/▼, but this looks better in strict box
 * layouts that in the free-wrapping key statements
 * 
 * Nomenclature proposal: if an extra plugin is used, strings can be designated as
 * “plugin_toolTipSomthing” otherwise just “toolTipSomthing” (global string). So it’s more clear if
 * somebody wants to deactivate a plugin and remove strings from the resource dictionary.
 * @augments $
 * @type object
 */
$.jI18n = {
  en: {
    ClueTip_newWindow :              "(New Window …)",
    ClueTip_toolTipClose  :          "Click to close",
    ClueTip_toolTipNewWindow :       "(click to open content in a new window or tab)",
    ClueTip_toolTipNoContentLoadable:"<i>No content could be loaded</i>",
    CollapseBox_captionCollapse :        "&nbsp;(show less)&nbsp;",
    CollapseBox_captionExpand :          "&nbsp;(more...)&nbsp;",
    CollapseBox_toolTipCollapse :        "(click to hide information below)",
    CollapseBox_toolTipExpand :          "(click to show more information below)",
    HeadingLink_toolTipHeadingLink:      "Click to show (permanent) link to this headline", // MediaWiki:Gadget-HeadingLink
    HeadingLink_toolTipHeadingLinkHelp:  "(1) Normal link to this head line or (2) the permanent link with version number:",// MediaWiki:Gadget-HeadingLink
    ImageZoom1st_iconCloseWindow :        "http://upload.wikimedia.org/wikipedia/commons/8/87/Close_icon_default.jpg",
    ImageZoom1st_iconCloseWindowHover :   "http://upload.wikimedia.org/wikipedia/commons/d/d0/Close_icon_hover.jpg",
    ImageZoom1st_imageMetadataLink :      "(Information about Creator, License and Copyright)",
    ImageZoom1st_toolTipImageZooming :    "Images can be enlarged by clicking on it",
    ImageZoom1st_zoomNotPossible :        "(This image can not be further enlarged)",
    // see MediaWiki:Jquery.zoomImage.js
    ImageZoom2nd_iconMagnifier: "http://species-id.net/o/media/f/f7/Iviewer.zoom_in.gif",
    ImageZoom2nd_iconMagnifierHover: "http://species-id.net/o/media/5/5c/Iviewer.zoom_out.gif",
    ImageZoom2nd_iconLoader:  "http://upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
    ImageZoom2nd_toolTipLoad :  "(click to load largest available image; this may take considerable time to load)",
    ImageZoom2nd_textZoomOrig:  "Zooming facility",
    MoveTOC_toolTipFloatleft : "floating on the left side",
    MoveTOC_toolTipFloatright: "floating on the right side",
    MoveTOC_toolTipNavigatePagetop : "Top of page",
    MoveTOC_toolTipUnfloat: "back to default position",
    // see MediaWiki:jKey.js
    jKey_expandAll :              "Show all extras",
    jKey_iconOverview  :          "http://upload.wikimedia.org/wikipedia/commons/thumb/2/22/View-pause_Gion_simple.svg/20px-View-pause_Gion_simple.svg.png",
    jKey_iconResume  :            "http://upload.wikimedia.org/wikipedia/commons/thumb/4/49/View-playback_Gion_simple.svg/20px-View-playback_Gion_simple.svg.png",
    jKey_iconStart1st  :          "http://upload.wikimedia.org/wikipedia/commons/thumb/4/49/View-playback_Gion_simple.svg/20px-View-playback_Gion_simple.svg.png",
    jKey_iconStartNew  :          "http://upload.wikimedia.org/wikipedia/commons/thumb/0/05/View-refresh_Gion_simple.svg/20px-View-refresh_Gion_simple.svg.png"
  },
  de: {
    ClueTip_newWindow :              "(Neues Fenster …)",
    ClueTip_toolTipClose  :          "Zum Schließen klicken",
    ClueTip_toolTipNewWindow :       "(klicken um Inhalt in neuem Fenster oder Reiter zu öffnen)",
    ClueTip_toolTipNoContentLoadable:"<i>Leider konnte der Inhalt nicht geladen werden.</i>",
    CollapseBox_captionCollapse :        "&nbsp;(weniger anzeigen)&nbsp;",
    CollapseBox_captionExpand :          "&nbsp;(mehr...)&nbsp;",
    CollapseBox_toolTipCollapse :        "(klicken um Zusatzinformationen zu verbergen)",
    CollapseBox_toolTipExpand :          "(klicken um Zusatzinformationen anzuzeigen)",
    HeadingLink_toolTipHeadingLink:      "Klicken um (permanenten) Link dieser Überschrift anzuzeigen",// MediaWiki:Gadget-HeadingLink
    HeadingLink_toolTipHeadingLinkHelp:  "(1) Link zu dieser Überschrift oder (2) Link mit Versionsnummer:",// MediaWiki:Gadget-HeadingLink
    ImageZoom1st_imageMetadataLink :      "(Informationen zu Autor, Lizenz und Copyright)",
    ImageZoom1st_toolTipImageZooming :    "Bilder können durch Anklicken vergrößert betrachtet werden",
    ImageZoom1st_zoomNotPossible :        "(Dieses Bild kann nicht weiter vergrößert werden)",
    // see MediaWiki:zoomImage.js
    ImageZoom2nd_toolTipLoad :  "(klicken um Originalbild nachzuladen; bei großen Bildern kann dies u. U. langsam sein)",
    ImageZoom2nd_textZoomOrig:  "Vergrößerungsfunktion",
    MoveTOC_toolTipFloatleft : "Links schwebend",
    MoveTOC_toolTipFloatright: "Rechts schwebend",
    MoveTOC_toolTipNavigatePagetop : "Zum Seitenanfang",
    MoveTOC_toolTipUnfloat: "Zurück zur Normalposition",
    jKey_expandAll :              "Alle Zusatzinformationen zeigen"
  },
  it: {
    ClueTip_toolTipClose  :          "Clicca per chiudere",
    CollapseBox_captionCollapse :        "&nbsp;(mostra di meno)&nbsp;",
    CollapseBox_captionExpand :          "&nbsp;(più...)&nbsp;",
    ImageZoom1st_imageMetadataLink   :    "(Informazione sull'Autore, Licenza e Copyright)",
    ImageZoom1st_toolTipImageZooming :    "Le immagini possono essere ingrandite cliccandoci sopra",
    ImageZoom1st_zoomNotPossible :        "(Al momento non è possibilie ingrandire questa immagine)", // TODO translation see en version
    jKey_expandAll :              "Mostra tutti informazione" //REVISE
  }
};


/**
 * @description Get resource string (text, image URLs) for a given language, based on a string-key
 *  If no resource is defined in a given language for a resource key, the resource for "en" will be returned,
 *  if this is missing as well an error message.
 * @augments $
 * @requires mw.config for getting global variables
 * @param {string} resourceKey key for the resource
 * @returns {String}
 */
$.resource = function (resourceKey) {
    var lang = mw.config.get('wgUserLanguage').split("-")[0]; // language: "pt-BR", "de-formal", etc.
    return ($.jI18n[lang] && $.jI18n[lang][resourceKey] ?
      $.jI18n[lang][resourceKey] :
      ($.jI18n.en[resourceKey]) ? $.jI18n.en[resourceKey] : "MISSING RESOURCE: no $.jI18n.en." + resourceKey + " defined.");
};

/**
 * @description Create html string for link with image and/or text content
 * @requires $.resource()
 * @param {string} txtResourceKey resource keys (multilingual {@link $.resource()}
 * @param {html} txtContent displayed content of a link
 * @param {url} href
 * @param {string} attributes string of combined other attributes of link element; must use ' as inner quotes, and \" inside event functions
 * @returns {@exp;txtResourceKey@pro;length|String|@exp;txtContent@pro;length@exp;txtResourceKey@pro;length}
 */
$.linkBuilder = function (txtResourceKey, txtContent, href, attributes) {
  return (txtResourceKey.length ? "<a "
    + " href='" + href + "' " 
    + " " + (attributes.length ? attributes : "") + ">"
    + $.resource(txtResourceKey)
    + "</a>" : (txtContent.length ? "<a "
      + " href='" + href + "' "
      + " " + (attributes.length ? attributes : "") + ">"
      + txtContent + 
      "</a>" : "")
  );
};
/**
 * 
 * @param {string} imgResourceKey resource key {@link $.resource()}
 * @param {string} txtResourceKey resource key {@link $.resource()}
 * @param {string} attributes HTML
 * @requiers: $.linkBuilder
 * @returns {String}
 */
$.imglinkBuilder = function (imgResourceKey, txtResourceKey, attributes) {
  return (imgResourceKey.length ? "<a "
    + " href='#'" + (attributes.length ? " " + attributes : "") + "><img src='" + $.resource(imgResourceKey) + "' /></a>&nbsp;" : "")
    + $.linkBuilder(txtResourceKey, "", "#", attributes);
};

/**
 * @description return a random integer
 * @param {integer} min
 * @param {integer} max
 * @returns {@exp;@call;parseInt}
 */
$.random = function (min, max) { // NO CHECKS: if(min>max) {return -1;}  if(min==max) {return min;}
  return (min + parseInt(Math.random() * (max - min + 1), 10));
};


///////////////////////
// Highlight targets //
///////////////////////

/* Description: Highlight all targets of page-internal links; generic function but
 *  especially useful in long internally linked tables like identification keys (see Template:Key_Start)
 *  NOTE: background-color animation is not easliy done by jQuery it needs either UI or a colorplugin
 */

/**
 * @description Highlight a single element that is target of the link-object caller (e.g. <a href=...>)
 * 
 * @requires resetHighlight()
 * @requires jqueryEscapeId()
 * @param {type} caller
 * @returns {undefined}
 */
function highlightTarget(caller) {
  var target = $(jqueryEscapeId(caller.hash)); // hash could be 'a.34:', jquery needs 'a\.34\:'
  if (target.length) {
    var tStyle = target.get(0).style,
      resetString = "resetHighlight(\"" + caller.hash + "\",\"" + tStyle.backgroundColor + "\",\"" + tStyle.textDecoration + "\")";
    tStyle.backgroundColor = "#EAEAEA";
    window.setTimeout(resetString, 2000);
  }
}

/**
 * @description Stop highlighting
 * @requires highlightTarget()
 * @requires jqueryEscapeId()
 * @param {string} hash ID of the target, could be 'a.34:'
 * @param {string} backColor background color
 * @param {string} txtDeco CSS style text decoration
 * @returns {undefined}
 */
function resetHighlight(hash, backColor, txtDeco) {
  if (hash) { // reset
    var tStyle = $(jqueryEscapeId(hash)).get(0).style;
    tStyle.backgroundColor = backColor;
    tStyle.textDecoration = (txtDeco === "") ? "none" : txtDeco;
  }
}

/**
 * @description Add onclick events to all page-internal links
 * 
 * @requires resetHighlight()
 * @requires highlightTarget()
 * @requires jqueryEscapeId()
 * @returns {undefined}
 */
function initTargetHighlighting() {
  for (var i=0, max=document.links.length; i < max; i++) {
    var lnk = document.links[i];
    if ((lnk.pathname === location.pathname) && lnk.hash.length > 1) { // page internal link; exluding single "#"
      lnk.onclick = function() { highlightTarget(this); };
    }
  }
}


/**
 * @description collapse all collapsible key tables on wiki page
 * using the MediaWiki mw-customtoggle mechanism on all CSS class of
 * class="decisiontree"
 * @requires MediaWiki:JKey.js
 * @param {boolean} shallExpandThisKey
 * @param {selector} caller
 * @returns {undefined}
 */
function toggleAllCollapsible(shallExpandThisKey, caller) {
  var $pseudolinks = $(caller).closest(".decisiontree")
    .find("span.pseudolink");
  // debug log messages
  if ($pseudolinks.length) {
    $pseudolinks.each(function () {
      var thisClasses = $(this).attr('class'),
      // get last string position/index
        thisClassesStopPos = thisClasses.indexOf(" ", thisClasses.indexOf("mw-customtoggle")),
      // get mw-customtoggle-Key class
        thisClassMwCustomtoggle = thisClasses.substring(
          thisClasses.indexOf("mw-customtoggle"), // from index
          thisClassesStopPos === -1 ? thisClasses.length : thisClassesStopPos // to index
        ),
      // extract correspoding id
        thisIdToToggle = thisClassMwCustomtoggle.replace("mw-customtoggle", "mw-customcollapsible"),
      // check clicked status using class mw-collapsed
        thisIsCollapsed = $('#' + thisIdToToggle).hasClass('mw-collapsed'),
        doExpandThis;

      if (shallExpandThisKey) {
        doExpandThis = thisIsCollapsed ? true : false;
      } else {
        doExpandThis = thisIsCollapsed ? false : true;
      }
      if (doExpandThis) {
        $(this).trigger('click');
      }
    });
  }// pseudolink found
}// toggleAllCollapsible()


////////////////////////////////////
// Cluetip hover and click popups //
////////////////////////////////////

/**
 * @description Utility for Cluetip, Modal layer, Image Zoom:
 * Create appendable jquery object, fnAction = function bound to click.
 * NOTE All functions called within createButton() should return false
 * to prevent appending a # to the URL from clicking <a href='#'></a>
 * @param {string} kindOfButton type of button ('zoomImg', 'close')
 * @param {function} fnAction function to bind on that link
 * @returns {@exp;@exp;@call;$@pro;append@pro;h@call;over@call;@call;click|@exp;@call;$@pro;append@pro;h@call;over@call;@call;click}
 */
function createButton(kindOfButton, fnAction) {
  switch (kindOfButton) {
  case "zoomImg":
    return $("<a href='#' title='"+$.resource('ImageZoom2nd_toolTipLoad')+"' />")
      .append(
        "<img src='"+$.resource("ImageZoom2nd_iconMagnifier")+"' align='middle' style='border:1px solid gray;'>" +
        '<span style="position:absolute;left:20px;top:0px;white-space:nowrap;">'+$.resource("ImageZoom2nd_textZoomOrig")+'</span>'
      ) // text after img seems to be inline only with position:absolute
      .hover(
        function() { $(this).find("img:first").attr({src: $.resource("ImageZoom2nd_iconMagnifierHover"), style :'border:1px solid black;'}); },
        function() { $(this).find("img:first").attr({src: $.resource("ImageZoom2nd_iconMagnifier"), style :'border:1px solid gray;'}); })
      .click(fnAction);
    break;
  case "close":
  default:
    return $("<a href='#' title='"+$.resource('ClueTip_toolTipClose')+"'/>")
      .append("<img src='"+$.resource("ImageZoom1st_iconCloseWindow")+"' />")
      .hover(
        function() { $(this).find("img:first").attr("src", $.resource("ImageZoom1st_iconCloseWindowHover")); },
        function() { $(this).find("img:first").attr("src", $.resource("ImageZoom1st_iconCloseWindow")); })
      .click(fnAction);
  }// end switch case
}

///////////////////////////////
// HoverIntent START
/**
 * @description hoverIntent is a plug-in that attempts to determine the user's intent
 * see http://offene-naturfuehrer.de/wiki/MediaWiki:HoverIntent.js for docu, creators and license
 * @augments $.fn
 * @requires jQuery 1.1.2+
 * @param {handler} f handler in
 * @param {handler} g handler out
 * @returns {@exp;@call;@call;mouseout}
 */
$.fn.hoverIntent = function(f,g) {
  var cfg = { // default configuration options: Cluetip overrides!
    sensitivity: 4, // mouseover is called if mouse is moved less pixels; default 7. Cluetip overrides!
    interval: 250, // comparison interval, also influences initial delay until detected; default 100.
    timeout: 0
  };
  cfg = $.extend(cfg, g ? {over:f, out:g} : f ); // override options with user-supplied object
  // current and previous X/Y position of mouse
  var cX, cY, pX, pY;

  // private function for getting mouse position
  var track = function(ev) {
    cX = ev.pageX;
    cY = ev.pageY;
  };

  // private function comparing current and previous mouse position
  var compare = function(ev,ob) {
    ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
    // compare mouse positions to see if they've crossed the threshold
    if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
      $(ob).unbind("mousemove",track);
      // set hoverIntent state to true (so mouseOut can be called)
      ob.hoverIntent_s = 1;
      return cfg.over.apply(ob,[ev]);
    } else { // set previous coordinates for next time
      pX = cX; pY = cY;
      // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
      ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
    }
  };

  // private function delaying the mouseOut function
  var delay = function(ev,ob) {
    ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
    ob.hoverIntent_s = 0;
    return cfg.out.apply(ob,[ev]);
  };

  // private function handling mouseover AND mouseout
  var handleHover = function(e) {
    // next three lines from jQuery.hover: ignore children onMouseOver/onMouseOut
    var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
    while ( p && p != this ) { try { p = p.parentNode; } catch(err) { p = this; } }
    if ( p == this ) { return false; }
    // copy objects to be passed into t (required for event object to be passed in IE)
    var ev = $.extend({},e);
    var ob = this;
    // cancel hoverIntent timer if it exists
    if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
    // else e.type == "onmouseover"
    if (e.type == "mouseover") {
      pX = ev.pageX; pY = ev.pageY; // set previous X/Y pos based on initial entry point
      $(ob).bind("mousemove",track); // update current X/Y pos based on mousemove
      // start polling interval (self-calling timeout) to compare mouse coordinates over time
      if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
      // else e.type == "onmouseout"
    } else {
      // unbind expensive mousemove event
      $(ob).unbind("mousemove",track);
      // if hoverIntent state is true, then call the mouseOut function after the specified delay
      if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
    }
  };

  // bind functions to event listeners
  return this.mouseover(handleHover).mouseout(handleHover);
};
// HoverIntent END


//////////////////////////////////////////////////
// Modified Cluetip Plugin

/**
 * @description Cluetip Plugin (modified)
 * MIT and GPL licenses 
 * http://plugins.learningjquery.com/cluetip/ and http://www.offene-naturfuehrer.de/wiki/MediaWiki:Cluetip.js for details
 * @version 1.0.6 modified for Off.Naturführer. 
 * @requires jQuery
 * @requires jqueryEscapeId()
 * @requires createButton()
 * @augments object $.fn
 * @returns {undefined}
 */
(function($) {
  $.cluetip = {version: '1.0.6-ON'};
  var $cluetip, $cluetipInner, $cluetipOuter, $cluetipTitle, $cluetipArrows, $cluetipWait, $dropShadow, imgCount,
    insertionElement = 'body';
  $.fn.cluetip = function(js, options) {
    if (typeof js == 'object') {
      options = js;
      js = null;
    }
    if (js == 'destroy') {
      return this.removeData('thisInfo').unbind('.cluetip');
    }
    return this.each(function(index) {
      var link=this, $this=$(this);

      // support metadata plugin (v1.0 and 2.0)
      var opts = $.extend(
        true, 
        {}, 
        $.fn.cluetip.defaults,
        options || {}, 
        $.metadata ? $this.metadata() : ( $.meta ? $this.data() : {} )
      );

      // start out with no contents (for ajax activation)
      var cluetipContents = false;
      var cluezIndex = +opts.cluezIndex;
      $this.data('thisInfo', {title: link.title, zIndex: cluezIndex});
      var isActive = false, closeOnDelay = 0;

      // create the cluetip divs
      if (!$('#cluetip').length) {
        $(['<div id="cluetip">',
          '<div id="cluetip-outer">',
            '<h3 id="cluetip-title"></h3>',
            '<div id="cluetip-inner"></div>',
          '</div>',
          '<div id="cluetip-extra"></div>',
          '<div id="cluetip-arrows" class="cluetip-arrows"></div>',
        '</div>'].join(''))
        .appendTo(insertionElement).hide();

        $cluetip = $('#cluetip').css({position: 'absolute'});
        $cluetipOuter = $('#cluetip-outer').css({position: 'relative', zIndex: cluezIndex});
        $cluetipInner = $('#cluetip-inner');
        $cluetipTitle = $('#cluetip-title');
        $cluetipArrows = $('#cluetip-arrows');
        $cluetipWait = $('<div id="cluetip-waitimage"></div>')
          .css({position: 'absolute'}).insertBefore($cluetip).hide();
      }
      var dropShadowSteps = (opts.dropShadow) ? +opts.dropShadowSteps : 0;
      if (!$dropShadow) {
        $dropShadow = $([]);
        for (var i=0; i < dropShadowSteps; i++) {
          $dropShadow = $dropShadow.add($('<div></div>').css({zIndex: cluezIndex-1, opacity:0.1, top: 1+i, left: 1+i}));
        }
        $dropShadow.css({position: 'absolute', backgroundColor: '#000'})
        .prependTo($cluetip);
      }
      var tipAttribute = $this.attr(opts.attribute);
      if (!tipAttribute && !opts.splitTitle && !js) {
        return true;
      }
      // if hideLocal is set to true, on DOM ready hide the local content that will be displayed in the clueTip
      if (opts.local && opts.localPrefix) {tipAttribute = opts.localPrefix + tipAttribute;}
      if (opts.local && opts.hideLocal) { $(tipAttribute + ':first').hide(); }
      var tOffset = parseInt(opts.topOffset, 10), lOffset = parseInt(opts.leftOffset, 10);
      // vertical measurement variables
      var tipHeight, wHeight,
          sTop, linkTop, posY, tipY, mouseY, baseline,
          defHeight = isNaN(parseInt(opts.height, 10)) ? 'auto' : (/\D/g).test(opts.height) ? opts.height : opts.height + 'px';
      // horizontal measurement variables
      var tipInnerWidth = parseInt(opts.width, 10) || 275,
        tipWidth = tipInnerWidth + (parseInt($cluetip.css('paddingLeft'),10)||0) + (parseInt($cluetip.css('paddingRight'),10)||0) + dropShadowSteps,
        linkWidth = this.offsetWidth,
        linkLeft, posX, mouseX, winWidth;

      // parse the title
      var tipParts;
      var tipTitle = (opts.attribute != 'title') ? $this.attr(opts.titleAttribute) : '';
      if (opts.splitTitle) {
        if (tipTitle === undefined) {tipTitle = '';}
        tipParts = tipTitle.split(opts.splitTitle);
        tipTitle = tipParts.shift();
      }
      if (opts.escapeTitle) {
        tipTitle = tipTitle.replace(/&/g,'&amp;').replace(/>/g,'&gt;').replace(/</g,'&lt;');
      }

      function returnFalse() { return false; }

//activate clueTip
    var activate = function(event) {
      var pY;
      if (!opts.onActivate($this)) {
        return false;
      }
      isActive = true;
      $cluetip.removeClass().css({width: tipInnerWidth});
      if (tipAttribute == $this.attr('href')) {
        $this.css('cursor', opts.cursor);
      }
      if (opts.hoverClass) {
        $this.addClass(opts.hoverClass);
      }
      linkTop = posY = $this.offset().top;
      linkLeft = $this.offset().left;
      mouseX = event.pageX;
      mouseY = event.pageY;
      if (link.tagName.toLowerCase() != 'area') {
        sTop = $(document).scrollTop();
        winWidth = $(window).width();
      }
// position clueTip horizontally
      if (opts.positionBy == 'fixed') {
        posX = linkWidth + linkLeft + lOffset;
        $cluetip.css({left: posX});
      } else {
        posX = (linkWidth > linkLeft && linkLeft > tipWidth) || linkLeft + linkWidth + tipWidth + lOffset > winWidth ? linkLeft - tipWidth - lOffset : linkWidth + linkLeft + lOffset;
        if (link.tagName.toLowerCase() == 'area' || opts.positionBy == 'mouse' || linkWidth + tipWidth > winWidth) { // position by mouse
          if (mouseX + 20 + tipWidth > winWidth) {
            $cluetip.addClass(' cluetip-jtip');
            posX = (mouseX - tipWidth - lOffset) >= 0 ? mouseX - tipWidth - lOffset - parseInt($cluetip.css('marginLeft'),10) + parseInt($cluetipInner.css('marginRight'),10) : mouseX - (tipWidth/2);
          } else {
            posX = mouseX + lOffset;
          }
        }
        pY = posX < 0 ? event.pageY + tOffset : event.pageY;
        $cluetip.css({
          left: (posX > 0 && opts.positionBy != 'bottomTop') ? posX : (mouseX + (tipWidth/2) > winWidth) ? winWidth/2 - tipWidth/2 : Math.max(mouseX - (tipWidth/2),0),
          zIndex: $this.data('thisInfo').zIndex
        });
        $cluetipArrows.css({zIndex: $this.data('thisInfo').zIndex+1});
      }
      wHeight = $(window).height();

// load a string from cluetip method's first argument
      if (js) {
        if (typeof js == 'function') {
          js = js.call(link);
        }
        $cluetipInner.html(js);
        cluetipShow(pY);
      }

// load the title attribute only (or user-selected attribute).
// clueTip title is string before 1st delimiter, subsequent delim place clueTip body text on separate lines
      else if (tipParts) {
        var tpl = tipParts.length;
        $cluetipInner.html(tpl ? tipParts[0] : '');
        if (tpl > 1) {
          for (var i=1; i < tpl; i++){
            $cluetipInner.append('<div class="split-body">' + tipParts[i] + '</div>');
          }
        }
        cluetipShow(pY);
      }
// load external file via ajax
      else if (!opts.local && tipAttribute.indexOf('#') !== 0) {
        if (/\.(jpe?g|tiff?|gif|png)$/i.test(tipAttribute)) {
          $cluetipInner.html('<img src="' + tipAttribute + '" alt="' + tipTitle + '" />');
          cluetipShow(pY);
        } else if (cluetipContents && opts.ajaxCache) {
          $cluetipInner.html(cluetipContents);
          // highlight target having a <span id=".."> see also ajaxSettings success:
          var idTarget = link.toString().split('#')[1];
          if(idTarget!=undefined){
            $cluetipInner.find(jqueryEscapeId('#'+idTarget)).addClass('alert');
          }
          cluetipShow(pY);
        } else {
          var optionBeforeSend = opts.ajaxSettings.beforeSend,
              optionError      = opts.ajaxSettings.error,
              optionSuccess    = opts.ajaxSettings.success,
              optionComplete   = opts.ajaxSettings.complete;
          var ajaxSettings = {
            cache: false, // force requested page not to be cached by browser
            url: tipAttribute,
            beforeSend: function(xhr) {
              if (optionBeforeSend) {optionBeforeSend.call(link, xhr, $cluetip, $cluetipInner);}
              $cluetipOuter.children().empty();
              $cluetipWait.css({top: mouseY+20, left: mouseX+20, zIndex: $this.data('thisInfo').zIndex-1}).show();
            },
            error: function(xhr, textStatus) {
              if (isActive) {
                if (optionError) {
                  optionError.call(link, xhr, textStatus, $cluetip, $cluetipInner);
                } else {
                  $cluetipInner.html($.resource('ClueTip_toolTipNoContentLoadable'));
                }
              }
            },
            success: function(data, textStatus) {
              cluetipContents = opts.ajaxProcess.call(link, data);
              if (isActive) {
                if (optionSuccess) {optionSuccess.call(link, data, textStatus, $cluetip, $cluetipInner);}
                $cluetipInner.html(cluetipContents);
                // highlight target having a <span id="..">
                var idTarget = link.toString().split('#')[1];
                if(idTarget!=undefined){
                  $cluetipInner.find(jqueryEscapeId('#'+idTarget)).addClass('alert');
                }
              }
            },
            complete: function(xhr, textStatus) {
              if (optionComplete) {optionComplete.call(link, xhr, textStatus, $cluetip, $cluetipInner);}
              imgCount = $('#cluetip-inner img').length;
              if (imgCount && navigator.userAgent.toUpperCase().indexOf('OPERA') ===-1 ) {
                $('#cluetip-inner img').bind('load error', function() {
                  imgCount--;
                  if (imgCount<1) {
                    $cluetipWait.hide();
                    if (isActive) { cluetipShow(pY); }
                  }
                });
              } else {
                $cluetipWait.hide();
                if (isActive) { cluetipShow(pY); }
              }
            }
          };
          var ajaxMergedSettings = $.extend(true, {}, opts.ajaxSettings, ajaxSettings);
          $.ajax(ajaxMergedSettings);
        }

// load an element from the same page
      } else if (opts.local) {

        var $localContent = $(tipAttribute + (/#\S+$/.test(tipAttribute) ? '' : ':eq(' + index + ')')).clone(true).show();
        $cluetipInner.html($localContent);
        cluetipShow(pY);
      }
    };

// get dimensions and options for cluetip and prepare it to be shown
    var cluetipShow = function(bpY) {
      $cluetip.addClass('cluetip-jtip');
      if (opts.truncate) {
        var $truncloaded = $cluetipInner.text().slice(0,opts.truncate) + '...';
        $cluetipInner.html($truncloaded);
      }
      if (opts.showTitle) {
        $cluetipTitle.show().html(tipTitle ? tipTitle : ' ');
      } else {
        $cluetipTitle.hide();
      }
      // INSERTED CODE: get href (= real wiki page link) and add as link in title ("(New Window)"). Cluetip now on link itself!
      // Not found how to get access in onShow() to "this"; thus inserted here.
      $cluetipTitle.prepend("<a href='"+ $this.attr("href") + "' target='_blank' title='"+ $.resource('ClueTip_toolTipNewWindow') +"' >" + $.resource('ClueTip_newWindow') + "</a>").show();
      if (opts.sticky) { // Close text modified directly
        var $closeLink = $('<div id="cluetip-close"/>').append(createButton("close", function() {cluetipClose();return false;}));
        if(opts.closePosition == 'bottom') {
          $closeLink.appendTo($cluetipInner);
        } else if(opts.closePosition == 'title'){
          $closeLink.prependTo($cluetipTitle);
        } else {
          $closeLink.prependTo($cluetipInner);
        }
        if (opts.mouseOutClose) {
          $cluetip.bind('mouseleave.cluetip', function() {cluetipClose();});
        } else {
          $cluetip.unbind('mouseleave.cluetip');
        }
      }
// now that content is loaded, finish the positioning
      var direction = '';
      $cluetipOuter.css({zIndex: $this.data('thisInfo').zIndex, overflow: defHeight == 'auto' ? 'visible' : 'auto'});
      tipHeight = defHeight == 'auto' ? Math.max($cluetip.outerHeight(),$cluetip.height()) : parseInt(defHeight,10);
      $cluetipInner.css({
        overflow: defHeight == 'auto' ? 'visible' : 'auto', 
        height: tipHeight - 45 //minus title: should be set automatically but $cluetipTitle.height() and height variants always get 0 or 0px
      });
      tipY = posY;
      baseline = sTop + wHeight;
      if (opts.positionBy == 'fixed') {
        tipY = posY - opts.dropShadowSteps + tOffset;
      } else if ( (posX < mouseX && Math.max(posX, 0) + tipWidth > mouseX) || opts.positionBy == 'bottomTop') {
        if (posY + tipHeight + tOffset > baseline && mouseY - sTop > tipHeight + tOffset) {
          tipY = mouseY - tipHeight - tOffset;
          direction = 'top';
        } else {
          tipY = mouseY + tOffset;
          direction = 'bottom';
        }
      } else if ( posY + tipHeight + tOffset > baseline ) {
        tipY = (tipHeight >= wHeight) ? sTop : baseline - tipHeight - tOffset;
      } else if ($this.css('display') == 'block' || link.tagName.toLowerCase() == 'area' || opts.positionBy == "mouse") {
        tipY = bpY - tOffset;
      } else {
        tipY = posY - opts.dropShadowSteps;
      }
      if (direction == '') {
        direction = (posX < linkLeft) ? 'left' : 'right';
      }
      $cluetip.css({top: tipY + 'px'}).removeClass().addClass('clue-' + direction + '-jtip').addClass(' cluetip-jtip');
      if (opts.arrows) { // set up arrow positioning to align with element
        var bgY = (posY - tipY - opts.dropShadowSteps);
        $cluetipArrows.css({top: (/(left|right)/.test(direction) && posX >=0 && bgY > 0) ? bgY + 'px' : /(left|right)/.test(direction) ? 0 : ''}).show();
      } else {
        $cluetipArrows.hide();
      }

// (first hide, then) ***SHOW THE CLUETIP***
      $dropShadow.hide();
      $cluetip.hide()[opts.fx.open](opts.fx.openSpeed || 0);
      if (opts.dropShadow) { $dropShadow.css({height: tipHeight, width: tipInnerWidth, zIndex: $this.data('thisInfo').zIndex-1}).show(); }
      if ($.fn.bgiframe) { $cluetip.bgiframe(); }
      // trigger the optional onShow function
      opts.onShow.call(link, $cluetip, $cluetipInner);
    };

// INACTIVATION
    var inactivate = function(event) {
      isActive = false;
      $cluetipWait.hide();
      if (!opts.sticky || (/click|toggle/).test(opts.activation) ) {
        cluetipClose();
        clearTimeout(closeOnDelay);
      }
      if (opts.hoverClass) {
        $this.removeClass(opts.hoverClass);
      }
    };
// close cluetip and reset some things
    var cluetipClose = function() {
      $cluetipOuter
      .parent().hide().removeClass();
      opts.onHide.call(link, $cluetip, $cluetipInner);
      $this.removeClass('cluetip-clicked');
      if (tipTitle) {
        $this.attr(opts.titleAttribute, tipTitle);
      }
      $this.css('cursor','');
      if (opts.arrows) {
        $cluetipArrows.css({top: ''});
      }
    };
    $(document).bind('hideCluetip', function(e) {
      cluetipClose();
    });

// BIND EVENTS
  // activate by click
      if ( (/click|toggle/).test(opts.activation) ) {
        $this.bind('click.cluetip', function(event) {
          if ($cluetip.is(':hidden') || !$this.is('.cluetip-clicked')) {
            activate(event);
            $('.cluetip-clicked').removeClass('cluetip-clicked');
            $this.addClass('cluetip-clicked');
          } else {
            inactivate(event);
          }
          this.blur();
          return false;
        });
  // activate by focus; inactivate by blur
      } else if (opts.activation == 'focus') {
        $this.bind('focus.cluetip', function(event) {
          activate(event);
        });
        $this.bind('blur.cluetip', function(event) {
          inactivate(event);
        });
  // activate by hover
      } else {
        // clicking is returned false if clickThrough option is set to false
        $this[opts.clickThrough ? 'unbind' : 'bind']('click', returnFalse);
        $this.hoverIntent({ // hoverintent now REQUIRED!
          sensitivity: opts.hoverIntent.sensitivity,
          interval: opts.hoverIntent.interval,
          timeout: opts.hoverIntent.timeout,
          over: function(event) {activate(event);},
          out: function(event) {inactivate(event); $this.unbind('mousemove.cluetip');}
        });
        $this.bind('mouseover.cluetip', function(event) {
          $this.attr('title','');
        }).bind('mouseleave.cluetip', function(event) {
          $this.attr('title', $this.data('thisInfo').title);
        });
      }
    });
  };

/**
 * @description default settings of $.fn.cluetip
 * Each can be explicitly overridden by changing its value
 * $.fn.cluetip.defaults.width = 200;
 * Each can also be overridden by passing an options map to the cluetip method
 * $('a.example').cluetip({width: 200});
 * would change the default width to 200 for clueTips invoked by a link with class of "example"
 * 
 * @augments $.fn.cluetip
 * @requires jQuery
 */
$.fn.cluetip.defaults = { // set up default options
    width:            400,      // The width of the clueTip
    height:          'auto',   // The height of the clueTip
    cluezIndex:      97,       // Sets the z-index style property of the clueTip
    positionBy:      'auto',   // Sets the type of positioning: 'auto', 'mouse','bottomTop', 'fixed'
    topOffset:        24,      // Number of px to offset clueTip from top of invoking element
    leftOffset:      12,       // Number of px to offset clueTip from left of invoking element
    local:            false,    // Whether to use content from the same page for the clueTip's body
    localPrefix:      null,    // string to be prepended to the tip attribute if local is true
    hideLocal:        true,    // If local is true, this determines whether local content to be shown in clueTip should be hidden at its original location
    attribute:        'resource', // the attribute to be used for fetching the clueTip's body content -- rel is sanitized by wiki!!!
    titleAttribute:  'title', // the attribute to be used for fetching the clueTip's title
    splitTitle:      '',       // A char to split the title attribute into title and divs within clueTip body. Example: |
    escapeTitle:      false,    // whether to html escape the title attribute
    showTitle:        true,    // show title bar of the clueTip, even if title attribute not set
    hoverClass:      '',       // class applied to the invoking element onmouseover and removed onmouseout
    cursor:          'help',
    arrows:          false,   // if true, displays arrow on appropriate side of clueTip
    dropShadow:      true,     // set to false if you don't want the drop-shadow effect on the clueTip
    dropShadowSteps:  6,        // adjusts the size of the drop shadow
    sticky:          false,   // keep visible until manually closed
    mouseOutClose:    false,    // close when clueTip is moused out
    activation:      'hover', // set to 'click' to force user to click to show clueTip
                                // set to 'focus' to show on focus of a form element and hide on blur
    clickThrough:    false,   // if true, and activation is not 'click', then clicking on link will take user to the link's href,
                                // even if href and tipAttribute are equal
    closePosition:    'top',    // location of close text for sticky cluetips; can be 'top' or 'bottom' or 'title'
    closeText:        'X',      // text (or HTML) to to be clicked to close sticky clueTips
    truncate:        0,       // number of characters to truncate clueTip's contents. if 0, no truncation occurs

    // effect and speed for opening clueTips
    fx: {
      open:      'show', // can be 'show' or 'slideDown' or 'fadeIn'
      openSpeed:  ''
    },
    hoverIntent: { // settings for hoverIntent
      sensitivity:  4,
      interval:    300,
      timeout:      0
    },
    // short-circuit function to run just before clueTip is shown.
    onActivate: function(e) {return true;},
    // function to run just after clueTip is shown.
    onShow:     function(ct, ci){},
    // function to run just after clueTip is hidden.
    onHide:     function(ct, ci){},
    // whether to cache results of ajax request to avoid unnecessary hits to server
    ajaxCache:   true,
    // process data retrieved via xhr before it's displayed
    ajaxProcess: function(data) {
      data = data.replace(/<(script|style|title)[^<]+<\/(script|style|title)>/gm, '').replace(/<(link|meta)[^>]+>/g,'');
      return data;
    },
  // can pass in standard $.ajax() parameters. Callback functions, such as beforeSend,
    // will be queued first within the default callbacks.
    // The only exception is error, which overrides the default
    ajaxSettings: {
    // error: function(ct, ci) { /* override default error callback */ }
    // beforeSend: function(ct, ci) { /* called first within default beforeSend callback }
    dataType: 'html'
    }
  };
})($);
// END Cluetip Plugin Code
/////////////////////

function initCluetips() {
  var jPopup = $('span.cluetip a');
  if (jPopup.length) { // only if at least one popup exists
    var ctHover = {
      arrows: true,
      height: 275,
      width:  400,
      fx: {open:'fadeIn', openSpeed:'3'}, // open can be 'show' or 'slideDown' or 'fadeIn'
      titleAttribute: 'suppress-title-display',
      positionBy: 'bottomTop',
      sticky: true,
      mouseOutClose: true,
      closePosition: 'title'
    },
    ctClick = {
      activation: 'click',
      height: 275,
      width:  400,
      fx: {open:'fadeIn', openSpeed:'3'}, // open can be 'show' or 'slideDown' or 'fadeIn'
      titleAttribute: 'suppress-title-display',
      sticky: true,
      closePosition: 'title',
      onShow: function () {// make title draggable
        if (typeof mw != "undefined"){
          mw.loader.using("jquery.ui.draggable", function () {
            $( "#cluetip" ).draggable({handle: "#cluetip-title"});
            $("#cluetip-title").css({cursor:"move"});
          });
        }
      }
    };
    // Design: Without js, a normal link should exist. With js, normal click should only call cluetip, which in turn offers opening in new window.
    jPopup.each(function() {
      var jLink=$(this), jSpan=jLink.parent();
      jLink.attr("resource", jSpan.attr("resource"));
      if (jSpan.hasClass("cluetip-hover")) {
        jLink.cluetip(ctHover);
      } else if (jSpan.hasClass("cluetip-click")) {
        jLink.cluetip(ctClick);
      }
    });
  } // END if popup found
}


////////////////////////////////////
// Modal Layer base functionality //
////////////////////////////////////
// for program flow see [[MediaWiki:ModalLayer and image zoom docu‎]]

/**
 * @description: Hide (= close) modal layer (Note: cyclical dependency with next method unavoidable)
 * @requires modalLayer_KeyDown()
 * @returns {Boolean}
 */
function modalLayer_Hide() {
  $(document).unbind("keydown", modalLayer_KeyDown);
  $("#modal-fg").fadeOut(function() {
    $("#modal-bg").hide();
    $(this).empty().hide();
  });
  return false;
}

/**
 * @description: Close (hide) modal layer on escape, backspace and arrow left key
 * @requires modalLayer_Hide()
 * @param {event} e the keyboard event object
 * @returns {undefined}
 */
function modalLayer_KeyDown(e) {
  if ((e.keyCode === 8) || (e.keyCode === 27) || (e.keyCode === 37)) { modalLayer_Hide(); }
}

/**
 * @description: Create modal layer and execute fnRender
 * @param {function} fnRender custom function to display something
 * @param {object} paramsObj generic parameters passed to "fnRender"
 * @returns {undefined}
 */
function modalLayer_Create(fnRender, paramsObj) {
  // find existing or create background & layer
  var modalBG = $("#modal-bg"),
    modalFG = $("#modal-fg");
  if (modalBG.length === 0) { // first time init
    if (typeof(document.body.style.maxHeight) === "undefined") { // if IE 6
      $("body","html").css({height: "100%", width: "100%"});
    }
    // add styles (IE6 hack not possible as element style!)
    $("head").append("<style type=\"text/css\">#modal-bg {position:fixed; z-index:100; top:0px;left:0px; height:100%;width:100%; background:black; opacity:0.8; filter:alpha(opacity=80); display:none;}\n" +
    "#modal-fg {position:fixed; z-index:101; top:50%;left:50%; padding:3px; border:2px solid #E0E0E0; background-color:white; display:none;}\n" + // IE6 hack: add (very!) slow IE-CSS-expression only for IE < 7
    ( ( navigator.userAgent.toUpperCase().indexOf('MSIE ') > 0 && parseFloat(navigator.appVersion) < 7 ) ? "* html #modal-bg {position: absolute; height:expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px');}\n* html #modal-fg {position: absolute; margin-top: expression(0 - parseInt(this.offsetHeight / 2) + (TBWindowMargin = document.documentElement && document.documentElement.scrollTop || document.body.scrollTop) + 'px');}\n" : "") +
    "#modal-fg img {display:block;}\n</style>");
    modalBG = $("<div id='modal-bg'/>"); // #### in old code version: HEIGHT was changed to: $(document).height()-- necessary for some browsers??? Trying without!
    modalFG = $("<div id='modal-fg'/>");
    modalBG.click(function() { modalLayer_Hide(); });
    $("body").append(modalBG).append(modalFG);
  } // END first time init
  $(document).keydown(modalLayer_KeyDown);
  // generic view port functionality: close icon and background click
  modalFG
    .append( $('<div style="position:absolute;right:4px;"/>')
    .append(createButton("close", modalLayer_Hide)) );
  modalBG
    .append('<img src="' + $.resource('ImageZoom2nd_iconLoader') + '"  id="loaderIcon"  style="position:absolute;top:50%;left:50%;"/>')
    .show();
  fnRender(paramsObj); // Execute custom logic, example: modalLayer_ZoomImage
}

/**
 * @description Show modal layer based on a image
 * 
 * @requires createButton()
 * @requires modalLayer_InitShowIviewerZoomImage()
 * @requires modalLayer_Hide()
 * @requires $.resource()
 * 
 * @param {object} newImg new image object
 * @param {object} oriImg original image object
 * @returns {undefined}
 */
function modalLayer_ShowImage(newImg, oriImg) {
  var title = oriImg.title,
    imgWidth = newImg.width,
    imgHeight = newImg.height,
    modalBG = $("#modal-bg"),
    modalFG = $("#modal-fg");

  modalBG.find("#loaderIcon").remove();

  if (imgWidth===0) { // Only IE, only if newImg = oriImg.clone(): cloned image in IE has no width
    imgWidth = oriImg.width;
    imgHeight = oriImg.height;
  }
  // extend height of modal layer for no-zoom msg
  var zoomIsPossible = (imgWidth !== oriImg.width),
    layerHeight = imgHeight + 105 + ((!zoomIsPossible) ? 60 : 0),
    layerWidth = Math.max(300, imgWidth + 70); // reserve minimal text width
    // delete alt text & add click function to hide modal
    $(newImg).removeAttr("alt")
      .attr("title", title.replace($.resource("ImageZoom1st_toolTipImageZooming"),$.resource("ClueTip_toolTipClose")))
      .click( function() {modalLayer_Hide();});
      
    modalFG
      .css({width: layerWidth + "px", height: layerHeight + "px", "margin-left": -(layerWidth/2)})
      .append( zoomIsPossible ? $('<div style="position:absolute;left:4px;" id="iviewer_zoom_icon"/>').append(createButton("zoomImg",  modalLayer_InitShowIviewerZoomImage)) :"" )
      // append content to foreground; wrap image with <div>
      .append($("<div id='modal-fg-wrapper' style='margin-left:" +
        (layerWidth-imgWidth) / 2 + "px; margin-top:35px;'/>")
        .append(newImg)// add image
      )
      // + caption
      .append($("<div id='zoomcaption' class='zoomcaption' style='text-align:center; margin:8px 2px 2px 2px; font-weight:bold;'/>")
        .append(title.replace("("+$.resource("ImageZoom1st_toolTipImageZooming")+")","")+"<br />")
      // URL to metadata page from "a[href]" around img
        .append($.linkBuilder("ImageZoom1st_imageMetadataLink", "", $(oriImg).closest("a").attr("href"), "target='_blank'"))
        .append( !zoomIsPossible ? "<br/><br/><span style='color:red;'>" + $.resource("ImageZoom1st_zoomNotPossible") + "</span>" : "")
      );
  // take away IE6 modifications
  if ( !( navigator.userAgent.toUpperCase().indexOf('MSIE ') > 0 && parseFloat(navigator.appVersion) < 7 )) {
    modalFG.css({"margin-top": -((layerHeight + 8) / 2)}); // 8 from other margin-top
  }
  modalFG.fadeIn(50);
}

/** 
 * @description: load script and functionality on demand for jQuery plugin iviewer
 * @requires MediaWiki:Jquery.zoomImage.js with function modalLayer_ShowIviewerZoomImage()
 * @requires mw.config.get
 * @requires modalLayer_ShowIviewerZoomImage()
 * @returns {Boolean}
 */
function modalLayer_InitShowIviewerZoomImage() {
  if (typeof modalLayer_ShowIviewerZoomImage !== "function") {
    $.getScript(mw.config.get('wgServer') + mw.config.get('wgScript') + "?title=MediaWiki:Jquery.zoomImage.js&action=raw&ctype=text/javascript",
      function(){modalLayer_ShowIviewerZoomImage();});
  } else {
    modalLayer_ShowIviewerZoomImage ();
  }
  return false;// needed for click on <a href'#'></a> → no # appended to the URL
}

/**
 * @description: custom function to be passed to modal layer zooming an image
 * @param {object} paramsObj object containing "caller" = ref to a link including an img
 * @requires $.random()
 * @requires modalLayer_ShowImage()
 * @returns {undefined}
 */
function modalLayer_ZoomImage(paramsObj) {
  var oriImg = $(paramsObj.caller).find("img").get(0), // caller is typically a[href]
    urlParts = oriImg.src.split("/");
  if ((oriImg.src.search(/\/thumb\//) === -1) ||
      (urlParts[urlParts.length - 1].search(/px-/) === -1))   {
    // no larger picture possible, use existing
    modalLayer_ShowImage($(oriImg).clone().get(0), oriImg);
  } else { // images with "/thumb/" in path can be enlarged using URL-based resize
    var stdThumbWidths = [1600,1400,1280,1024,900,800,700,640,600,550,480,400,350,320,300,250,200,180,150,120,100,80],
      maxHeight = $(window).height() - 105, // 70 for additional text; 35 for space at top and bottom
      maxWidth  = $(window).width()  - 50; // 50 for space left & right
    // smallest possible of upscaling factor for height, width, multiply back to get max possible width
    var maxScaledWidth = oriImg.width * Math.min(maxHeight/oriImg.height, maxWidth/oriImg.width),
      maxScaledHeight = oriImg.height * maxScaledWidth / oriImg.width;
    // reduce to next smaller standard thumb width (mediawiki preview settings plus additions)
    for (var i = 0; i < 22; i++) {
      if (stdThumbWidths[i] < maxScaledWidth) {
        maxScaledWidth = stdThumbWidths[i];
        maxScaledHeight = oriImg.height * maxScaledWidth / oriImg.width;
        break;
      }
    }
    urlParts[urlParts.length - 1] = maxScaledWidth + "px-" + urlParts[urlParts.length - 2];
    var newImg = new Image(maxScaledWidth, maxScaledHeight);
    // Load image. load/error occur asynchronously, need independent calls
    // appending "random()" seems necessary for IE6-8, else "zoom image, close, zoom again" fails. REASON?
    $(newImg)
      .attr("src", urlParts.join("/")+"&rnd="+$.random(0,10000))
      .load(function() { // load succeeded: create modal layer after loading image, else values (width, etc.) are 0
        modalLayer_ShowImage(newImg, oriImg);
      })
      .error(function() { // Error loading thumb, main reason: thumbs must be smaller than ori size.
      // Currently assuming this reason, loading full original image; BETTER: test using API:
      // http://commons.wikimedia.org/w/api.php?action=query&titles=Image:Lamium_purpureum_scan.jpg&prop=imageinfo&iiprop=size
      urlParts.pop(); // remove last part (e.g. 800px-xyz.jpg)
      // set default width and height in case of thumbnail generation failure with huge images
      newImg = new Image(maxScaledWidth, maxScaledHeight); // load original image
      $(newImg)
        .attr("src", urlParts.join("/").replace("/thumb", "")) // remove "/thumb" from url to get full. DO NOT ADD random here!
        .load(function() {modalLayer_ShowImage(newImg, oriImg);}) // load succeeded
      // 2nd level fail -> load from wikimedia.org
        .error(function() {
          // set default width and height in case of thumbnail generation failure with huge images
          newImg = new Image(maxScaledWidth, maxScaledHeight);
          $(newImg)
            .attr("src", "http://commons.wikimedia.org/w/thumb.php?f="+urlParts[urlParts.length-1]+"&width="+maxScaledWidth+"px"+"&rnd="+$.random(0,10000))
            .load(function() {modalLayer_ShowImage(newImg, oriImg);}) // load succeeded
      // 3rd level fail -> load unchanged wiki page thumb
            .error(function() {modalLayer_ShowImage($(oriImg).clone().get(0), oriImg);});
        });
      }); // end first level error
  }
}


/**
 * @description: Show image in modal layer
 * 
 * @requires modalLayer_Create()
 * @requires modalLayer_ZoomImage()
 * @param {caller} caller reference to a link around image, function to call
 * @returns {False}
 */ 
function zoomImage(caller) {
  modalLayer_Create(modalLayer_ZoomImage, {caller: caller});
  return false; // cancel default event
}

/**
 * @description: Add a modal zoom functionality to all images linking to own metadata
 * the CSS 
 * class=no-image-popup or class=no-popup-image OR
 * class=no-image-zoom or class=no-zoom-image prevents an image from being zoomable, e.g.
 * [[File:Example.jpg|caption text|thumb|class=no-popup-image]]
 * @requires $.resource()
 * @requires zoomImage()
 * @returns {undefined}
 */
function initImageZooming() {
  $("a[href].image img:not(.no-image-popup,.no-popup-image,.no-image-zoom,.no-zoom-image)").each(function() {
    // check existing click events extension:MultimediaViewer
    var thisRawDOMelement = $(this).get(0)
      , thisEventObject= $._data(thisRawDOMelement, 'events');
    if (thisEventObject !== undefined && thisEventObject.click !== undefined) {
    var jParent = $(this).parent(),
      metaURL = jParent.attr("href"),
      urlParts = this.src.split("/"),
        imgFileName = (this.src.search(/\/thumb\//) !== -1) ? urlParts[urlParts.length - 2] : urlParts[urlParts.length - 1];
      // Is file name also in metadata page link? Else abort (e.g. for |link=parameter| wiki-images)
      // Problem: a.href and img.scr inconsistently! use encoded or non-encoded versions of e.g. () or "," -> unescape
        if (unescape(metaURL).indexOf(unescape(imgFileName)) === -1) { return; }
      // pass along the image
      jParent.click(function() { return zoomImage(this); });
      // set or change title, set alt to title
        var newTitle = this.alt + ((this.alt.length === 0) ? "" : " ") + "(" + $.resource("ImageZoom1st_toolTipImageZooming") + ")";
      $(this).attr({title:newTitle, alt:newTitle});
    }
  });
}// initImageZooming
// END Modal Layer/Img Zoom
////////////////////////////

// jKey Source
importScript("MediaWiki:JKey.js");
switch (mw.config.get('wgAction')) {
  case "edit":
  case "submit":
    importScript("MediaWiki:JKeyWikiEditorHelp.js"); // load help for the wikiEditor
  break;
}

// click-text modifications for mw-customcollapsible triggering from outside of mw-collapsible
importScript("MediaWiki:Mw-customcollapsible.js");

/**
 * @description create jQuery UI Tabs on CSS class="use-jquery-ui-tabs"
 * 
 * @requires https://www.mediawiki.org/wiki/Extension:Semantic_Forms
 * @requires jQuery-UI mw.loader.using( 'jquery.ui.tabs', function () {});
 * @returns {undefined}
 */
function initUiTabsInForms() {
  console.log("initUiTabsInForms");
  var use_jquery_ui_tabs= $(".use-jquery-ui-tabs");
  var use_jquery_ui_tabs_vertical= $(".use-jquery-ui-tabs-vertical");
  if (use_jquery_ui_tabs.length || use_jquery_ui_tabs_vertical.length) {
    mw.loader.using( 'jquery.ui.tabs', function () {
      var use_jquery_ui_tabs= $(".use-jquery-ui-tabs");// Google Chrome Fix
      var use_jquery_ui_tabs_vertical= $(".use-jquery-ui-tabs-vertical");// Google Chrome Fix
      console.log("initUiTabsInForms mw.loader.using");
      if ($(".use-jquery-ui-tabs").length) {
        $(".use-jquery-ui-tabs").tabs({event: "mouseover"});
        console.log("initUiTabsInForms tabs");
      }
      if (use_jquery_ui_tabs_vertical.length) {
        use_jquery_ui_tabs_vertical.tabs({event: "mouseover"}).addClass( "ui-tabs-vertical ui-helper-clearfix" );
        use_jquery_ui_tabs_vertical.find( "li" ).removeClass( "ui-corner-top" ).addClass( "ui-corner-left" );
        console.log("initUiTabsInForms tabs_vertical");
      }
    });
  }
}

/**
 * @description: Get jQuery ui tabs loaded for characters displayed as nested ui tabs
 * @requires: jquery.ui.tabs
 * @requires: template:Character_images_tabs
 * @returns {undefined}
 */
function init_character_ui_tabs() {
  var character_ui_tabs_nested=$('#character_ui_tabs_nested');
  if (character_ui_tabs_nested.length) {
    mw.loader.using('jquery.ui.tabs', function () {
      var ui_version =$.ui.version.split('.').map(function(x) { return parseInt(x, 10); });
      if (ui_version[0] <= 1 
        && ui_version[1] < 10) {
        character_ui_tabs_nested.tabs({
          fx: [{opacity:'toggle', duration: 'slow'}, {opacity:'toggle', duration: 'slow'}]
        });
        character_ui_tabs_nested.find('.inner-tabs-container').tabs({
          fx: [{opacity:'toggle', duration: 'slow'}, {opacity:'toggle', duration: 'slow'}]
        });
      } else {
        character_ui_tabs_nested.tabs({
          hide: {effect: "fadeOut", duration: 'slow'},
          show: {effect: "fadeIn", duration: 'slow'},
          heightStyle: 'auto'
        });
        character_ui_tabs_nested.find('.inner-tabs-container').tabs({
          hide: {effect: "fadeOut", duration: 'slow'},
          show: {effect: "fadeIn", duration: 'slow'},
          heightStyle: 'auto'
        });
      }
    });
  }
}
/**
 * @description: formats background of form elements created by template:Hidden
 * @requires https://www.mediawiki.org/wiki/Extension:Semantic_Forms
 * @requires CSS class indicateHiddenInputs or other fields
 * @requires CSS class indicateFilledFormElements
 * @returns {undefined}
 */
function initMarkAllFilledFormElements(){
  /*
  structure of template:Hidden:
 
    div.collapsebox
      ├ div.switcher (float)
      ├ div.collapsetitle
      └ div.collapsecontent
 
    tr.collapsebox
      └ th/td
          ├ div.switcher (float)
          └ div.collapsetitle
    tr.collapsecontent
  */
  var jDivHiddenFormTexts = $(
    // select only input type text + textarea with values
       "div.collapsecontent.indicateHiddenInputs * :text[value!='']" +
     ", div.collapsecontent.indicateHiddenInputs * textarea[value!='']"
    );
  var jTrHiddenFormTexts = $(
    // select only input type text + textarea with values
       "tr.collapsecontent.indicateHiddenInputs *:not(.collapsecontent) :text[value!='']" +
     ", tr.collapsecontent.indicateHiddenInputs *:not(.collapsecontent) textarea[value!='']"
    );
  var jIndicateFilledFormElements = $(
       ".indicateFilledFormElements:text[value!='']" +
     ", textarea[value!=''].indicateFilledFormElements "
  );
  /*
    pale orange:
    hsv →  36 20 99 36 10 99  36 05 99
           #fce8ca  #fcf2e3   #fcf7f0
    pale yellow
    hsv → 50 18 100  50 10 100
          #fff7d0    #fffbe6
  */
  var bgcolor =       {'background-color':'#fffbe6'};
  var bgcolor_darker = {'background-color':'#fff7d0'};
//  hidden elements
  // <div>
    //indicate the fields itself
    jDivHiddenFormTexts
      .css(bgcolor); //pale orange
    // parent switcher
    jDivHiddenFormTexts
      .parentsUntil('.collapsebox')
      .parent()
      .find('div.switcher')
      .css(bgcolor_darker); //pale orange
 
  // <tr>
    //indicate the fields itself
    jTrHiddenFormTexts
      .css({'background-color':'#fce8ca'}); //pale orange
    jTrHiddenFormTexts.each(function(){
      if($.trim($(this).text())){// make sure there is really text
        $(this).parentsUntil('.collapsebox', '.formtable')
          .find('div.switcher')
          .css(bgcolor_darker); //pale orange
      }
    });
//<select>
    $(
      '.collapsecontent.indicateHiddenInputs select option[value!=""]:selected' +
      ', select.indicateFilledFormElements option[value!=""]:selected'
    ).parent().css(bgcolor);
// all visible form elements
  jIndicateFilledFormElements
     .css(bgcolor); //pale orange
 
}// END initMarkAllFilledFormElements()


/**
 * @description init moveable table of contents
 * offers floating the table of contents to the left or right side (will make part of the page invisible!)
 * 
 * @requires $.resource()
 * @requires moveTOC()
 * @returns {undefined}
 */
function initmoveTOC() {
  $('#togglelink').after(' | <span style="cursor:pointer;color:blue;" title="'+
    $.resource('MoveTOC_toolTipFloatleft') +'" onclick="moveTOC(\'0px\')" >◄</span> <span style="cursor:pointer;color:blue;" title="'+
    $.resource('MoveTOC_toolTipFloatright') +'" onclick="moveTOC(\'right\')" >►</span> <span id="restorestatic" style="display:none;">| <span style="cursor:pointer;" title="'+
    $.resource('MoveTOC_toolTipUnfloat') +'" onclick="moveTOC(\'restore\')" >×</span></span> ');
}// end initmoveTOC

/**
 * @description init moveable table of contents
 * @requires initmoveTOC()
 * @param {string} position may be string "right", or "restore", everthing else is interpreted as "left"
 * @returns {undefined}
 */
function moveTOC(position) {
  var offsetX, offsetY;
  var TOC = $('#toc');
  if ($('#moveTOC').length === 0) {
    TOC.wrapAll('<div id="moveTOC"></div>');
  }
  if ($('#toTop').length === 0) {
    $("#toctitle h2").after('<a href="#mw-head" id="toTop" title="'+$.resource('MoveTOC_toolTipNavigatePagetop') +'">↑&nbsp;</a>');
  }
  switch (position) {
  case "right":
    offsetX = $("#content").width() - 140;
    offsetY = - 55;
    break;
  case "restore":
    TOC.unwrap();
    $('#toTop').remove();
    $('#restorestatic').hide('slow');
    $("#toctitle h2").show('slow');
    break;
  default:
    offsetY = -110;
    offsetX = 0 - $("#mw-panel").width() - 14 ; // padding#content
    break;
  }// end switch

  if (position != "restore") {
    $("#toctitle h2").hide('slow');
    $('#restorestatic').show();
    $('#moveTOC')
      .css({"position":"absolute"})
      .animate({'max-width':"13em"})
      .animate({
        'left': $(window).scrollLeft()+ offsetX +"px",
        'top' : $(window).scrollTop() + offsetY +"px"
      });
    // emulate position fixed see CSS .navigation-left .navigation-right
    $(window).scroll(function(){
      $('#moveTOC')
        .animate({
          'left': $(window).scrollLeft()+ offsetX +"px",
          'top' : $(window).scrollTop() + offsetY +"px"
        },{queue: false, duration: 400} // queue: don't wait
        );
    });
  } // end positioning unless restore
}// end function moveTOC()

///////////////////////////////////////////////////////////////
// specific to  http://offene-naturfuehrer.de
///////////////////////////////////////////////////////////////

/**
 * @description: collapsible parts: div and tr → Template:Hidden
 *   may be fused later with toggleCollapse
 *   main difference: initCollapsebox() uses a switcher defined by the Wikitemplate
 *   and is not using a javascript resource title.
 *   * the only advanced feature it has: collpsible table rows
 *   * otherwise use http://www.mediawiki.org/wiki/RL/DM#jQuery.makeCollapsible
 * @requires $.resources()
 * @requires $.jl18n.en.CollapseBox_toolTipExpand, $.jl18n.en.CollapseBox_toolTipCollapse
 * @returns {Boolean}
 */
function initCollapsebox() {
  /* is nested in: div.collapsebox
                    └ div.switcher
                    └ div.collapsecontent */
  var hasSwitcher = $("div.switcher .show, div.switcher .hide");
  if(hasSwitcher.length){
    $.each(hasSwitcher, function(index){// add tooltip
      hasSwitcher[index].title = hasSwitcher[index].className === "show"? $.resource("CollapseBox_toolTipExpand") : $.resource("CollapseBox_toolTipCollapse");
    });
    $("div.collapsebox div.switcher").on('click',
      function() {
        $(this).nextAll("div.collapsecontent:first").slideToggle(250);
        /* $(this).toggle() does not work in live as toggle is a bind()
            therefore toggle must be bound to a different DOM element */
        $(this).find(".show, .hide").toggle();
      });
    /* is in a table: tr.collapsebox
                        └ div.switcher
                      tr.collapsecontent */
    $("tr.collapsebox div.switcher").on('click',
      function() {
        $(this).closest("tr.collapsebox").nextAll("tr.collapsecontent:first").toggle();
        $(this).find(".show, .hide").toggle();
      });
    // TODO is a better generic check possible?
    if (mw.config.get('wgPageName') === "Spezial:Suche") {
      if (!$(".mw-search-results").length) {
        // open box with additional search possibilities
        $(".switcher .show").trigger('click');
      }
    }
    return true;
  }
  return false;
}// END initCollapsebox()

///////////////////////
// Table Filter/Sort //
///////////////////////
/**
 * @description Table Filter/Sort 
 * TODO ?replace by jQuery plugin datatable?
 * 
 * @requires $.resource()
 * @requires $.jl18n.en
 * @returns {undefined}
 */
function initTableFilterSort(){ // see MediaWiki:SortTableFilter.js
/* Note: problem is auto-inserted <tbody></tbody> by the browser: must be removed and
   replaced by thead + tbody. jQuery.unwrap() was not successful */
   // add possibly more classes from http://www.javascripttoolbox.com/lib/table/documentation.php
  // Note: only applies to non-nested tables
  var jAutotables = $('table.table-autosort, table.table-autofilter, table-autostripe,table-sorted-asc,table-sorted-desc, table-filtered');
  if (jAutotables.length) {
    // Note: in MediaWiki:SortTableFilter.js 'SortTableFilter_InputFilterTitle' was not recognized
    $.extend(true, $.jI18n, {
    en: {
      SortTableFilter_AutoSortTitle : 'Click to sort',
      SortTableFilter_FilterAllLabel: 'Filter: All',
      SortTableFilter_InputFilterTitle: 'Filter text (case sensitive, uses reg. expressions)'
    },
    de: {
      SortTableFilter_AutoSortTitle : 'Zum Sortieren klicken',
      SortTableFilter_FilterAllLabel: 'Zeige: alle',
      SortTableFilter_InputFilterTitle: 'Text filtern (GROß/klein!, nutzt reg. Ausdrücke)'
    }
    });
    // get sortable/filterable here already otherwise multiple th-filters
    $.getScript(mw.config.get( 'wgServer' ) + mw.config.get( 'wgScript' ) + "?title=MediaWiki:SortTableFilter.js&action=raw&ctype=text/javascript",
      function(){return true;});
    // modify tables to introduce thead structure
    jAutotables.each(function(index){ //TODO simplify code? if()…
      // There may or may not be a tbody around tr. NOTE: $.unwrap() does not work here!
      // Memo: find('tr th') finds th, .parent() retrieves tr! .wrapAll will wrap inside DOM, not in return value! .detach() returns detached
      // OK in FF 3.6 and IE 7-8, not in IE6 (like the old code)
      // FURTHER WORK: Ideally, all normal tr td should remain in a tbody.
      var jThis = $(this),
        jThead = jThis.find('tr th').parent().detach(),
        jTfoot = jThis.find('tr[class=tfoot] td').parent().detach(),// remove it from the DOM
        jTbody = jThis.find('tbody:first');
      if (jTbody.length===0) {
        jThis.children().wrapAll('<tbody/>');
        jTbody = jThis.find('tbody:first');
      }
      jTbody.before($("<thead/>").append(jThead));
      if (jTfoot.length) {
        jTbody.after($("<tfoot/>").append(jTfoot));
      }
      // th with class="input" gives an input field instead of selections
      $(this).find('th[class=input]').append('<input name="filter" title="'+$.resource('SortTableFilter_InputFilterTitle')+'" size="8" onkeyup="Table.filter(this,this)">');
    });// end each()
  } // END if any autotable
}

/**
 * @description: Collapse the TOC using id="toc-autocollapse"
 * @returns {undefined}
 */
function initTOCautocollapse() {
  if ($('#toc-autocollapse').length) {
    $('#togglelink').click();
  }
}// end initTOCautocollapse()
///////////////////////
// specific to  http://offene-naturfuehrer.de
// Page-specific scripts:
switch (mw.config.get( 'wgPageName' )) { // Minimize the pages on which the code will be loaded
  // vielleicht später auch woanders, d.h. als vorgewählte Suche auf Taxaseiten beispielsweise?
  case "Datenquellen":
  case "Online-Überprüfung_von_Bestimmungen":
  case "Tipps_zur_Bildersuche":

  case "Spezial:Suche":
    importScript("MediaWiki:SearchTools.js");
    break;
  case "Hilfe:Nummerierungen_im_jKey_abändern_(Lead_Nummern)":
    importScript("MediaWiki:JKeyRenumberingTool.js");
    break;
  case "Hilfe:Konvertierung_geschachtelt-eingerückter_Schlüssel_in_das_ON-Format":
  case "Testseite":
    importScript("MediaWiki:JKeyTextToLeadTemplateTool.js");
    break;
}

// Page-specific scripts more flexible:
if (mw.config.get('wgPageName').match(/^Basismerkmale_für/i)
    || mw.config.get('wgPageName').match(/^Vorlage:Character_State/i)
    || mw.config.get('wgPageName').match(/^Vorlage:Character_Definition/i)
) {
  importScript("MediaWiki:ToolGetRandomId.js");
}

/*
   mediaWiki.loader.state({"site":"ready"}); is appended and takes care of
   the document ready event. If $(document).ready() is used, for forms
   $ is then unknown. Scope or closure problem? (AP 2011-02-20)
 */
// When document is completely loaded
$(document).ready(function() {
  reference_footnote_tooltips();
  initImageZooming();
  initTargetHighlighting(); // page-internal jumps
  initmoveTOC(); // TOC CSS position fixed or static
  initTOCautocollapse(); // auto collapse on #toc-autocollapse e.g. Template:Artinformation BiolFlor
  // specific to  http://offene-naturfuehrer.de
    initTableFilterSort(); // tests internally whether table with corresponding classes exist 
}); // end $(document).ready()
  // ***** CAN initCollapseButtons BE DELETED?
  // TEST: COMMENTED OUT
  // initCollapseButtons(); // strongly changes page layout: execute first
  initCluetips();
  // page specific
  if(mw.config.get( 'wgAction' )==="formedit" 
    || mw.config.get( 'wgCanonicalSpecialPageName' )==="FormEdit"){
   // initConfirmDeleteSubform();
    initMarkAllFilledFormElements();
  }
// specific to  http://offene-naturfuehrer.de
initCollapsebox(); //collapsible parts 
// </syntaxhighlight>