MediaWiki:ImageZoom.js

From GFBio Public Wiki
Jump to: navigation, search

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences
/**
 * @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.
 * @requires MediaWiki:Jquery.zoomImage.js, MediaWiki:Common.css for #modal-fg, .iviewer_zoom_in, .iviewer_zoom_out, .iviewer_zoom_zero, .iviewer_zoom_fit
 * @requires jQuery
 * @augments $
 * @type object
 */
$.jI18n = {
  en: {
    captionCollapse :        " (show less) ",
    captionExpand :          " (more...) ",
    expandAll :              "Show all extras",
    iconCloseWindowHover :   "https://upload.wikimedia.org/wikipedia/commons/d/d0/Close_icon_hover.jpg",
    iconCloseWindow :        "https://upload.wikimedia.org/wikipedia/commons/8/87/Close_icon_default.jpg",
    iconOverview  :          "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/View-pause_Gion_simple.svg/20px-View-pause_Gion_simple.svg.png",
    iconResume  :            "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/View-playback_Gion_simple.svg/20px-View-playback_Gion_simple.svg.png",
    iconStart1st  :          "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/View-playback_Gion_simple.svg/20px-View-playback_Gion_simple.svg.png",
    iconStartNew  :          "https://upload.wikimedia.org/wikipedia/commons/thumb/0/05/View-refresh_Gion_simple.svg/20px-View-refresh_Gion_simple.svg.png",
    imageMetadataLink :      "(Information about Creator, License and Copyright)",
    newWindow :              "(New Window …)",
    toolTipClose  :          "Click to close",
    toolTipCollapse :        "(click to hide information below)",
    toolTipExpand :          "(click to show more information below)",
    toolTipImageZooming :    "Images can be enlarged by clicking on it",
    toolTipNavigatePagetop : "Top of page",
    toolTipNewWindow :       "(click to open content in a new window or tab)",
    toolTipFloatleft : "floating on the left side",
    toolTipFloatright: "floating on the right side",
    toolTipUnfloat: "back to default position",
    toolTipNoContentLoadable:"<i>No content could be loaded</i>",
    //toolTipHeadingLink:      "Click to show (permanent) link to this headline", // MediaWiki:Gadget-HeadingLink
    //toolTipHeadingLinkHelp:  "(1) Normal link to this head line or (2) the permanent link with version number:",// MediaWiki:Gadget-HeadingLink
    zoomNotPossible :        "(This image can not be further enlarged)",
    // see MediaWiki:Jquery.zoomImage.js
    ZoomImage_iconMagnifier: "http://www.species-id.net/o/media/f/f7/Iviewer.zoom_in.gif",
    ZoomImage_iconMagnifierHover: "http://www.species-id.net/o/media/5/5c/Iviewer.zoom_out.gif",
    ZoomImage_iconLoader:  "https://upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
    ZoomImage_toolTipLoad :  "(click to load largest available image; this may take considerable time to load)",
    ZoomImage_textZoomOrig:  "Zooming facility"
  },
  de: {
    captionCollapse :        "&nbsp;(weniger anzeigen)&nbsp;",
    captionExpand :          "&nbsp;(mehr...)&nbsp;",
    expandAll :              "Alle Zusatzinformationen zeigen",
    imageMetadataLink :      "(Informationen zu Autor, Lizenz und Copyright)",
    newWindow :              "(Neues Fenster …)",
    toolTipClose  :          "Zum Schließen klicken",
    toolTipCollapse :        "(klicken um Zusatzinformationen zu verbergen)",
    toolTipExpand :          "(klicken um Zusatzinformationen anzuzeigen)",
    //toolTipHeadingLink:      "Klicken um (permanenten) Link dieser Überschrift anzuzeigen",// MediaWiki:Gadget-HeadingLink
    //toolTipHeadingLinkHelp:  "(1) Link zu dieser Überschrift oder (2) Link mit Versionsnummer:",// MediaWiki:Gadget-HeadingLink
    toolTipImageZooming :    "Bilder können durch Anklicken vergrößert betrachtet werden",
    toolTipNavigatePagetop : "Zum Seitenanfang",
    toolTipNewWindow :       "(klicken um Inhalt in neuem Fenster oder Reiter zu öffnen)",
    toolTipFloatleft : "Links schwebend",
    toolTipFloatright: "Rechts schwebend",
    toolTipUnfloat: "Zurück zur Normalposition",
    toolTipNoContentLoadable:"<i>Leider konnte der Inhalt nicht geladen werden.</i>",
    zoomNotPossible :        "(Dieses Bild kann nicht weiter vergrößert werden)",
    // see MediaWiki:zoomImage.js
    ZoomImage_toolTipLoad :  "(klicken um Originalbild nachzuladen; bei großen Bildern kann dies u. U. langsam sein)",
    ZoomImage_textZoomOrig:  "Vergrößerungsfunktion"
  },
  it: {
    captionCollapse :        "&nbsp;(mostra di meno)&nbsp;",
    captionExpand :          "&nbsp;(più...)&nbsp;",
    expandAll :              "Mostra tutti informazione", //REVISE
    imageMetadataLink   :    "(Informazione sull'Autore, Licenza e Copyright)",
    toolTipClose  :          "Clicca per chiudere",
    toolTipImageZooming :    "Le immagini possono essere ingrandite cliccandoci sopra",
    zoomNotPossible :        "(Al momento non è possibilie ingrandire questa immagine)" // TODO translation see en version
  }
};

/**
 * @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.");
  };

/**
 * @descriptions: Create html string for link with image and/or text content
 * @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>" : "")
  );
};
/**
 * @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));
};

/**
 * @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('ZoomImage_toolTipLoad')+"' />")
      .append(
        "<img src='"+$.resource("ZoomImage_iconMagnifier")+"' align='middle' style='border:1px solid gray;'>" +
        '<span style="position:absolute;left:20px;top:0px;white-space:nowrap;">'+$.resource("ZoomImage_textZoomOrig")+'</span>'
      ) // text after img seems to be inline only with position:absolute
      .hover(
        function() { $(this).find("img:first").attr({src: $.resource("ZoomImage_iconMagnifierHover"), style :'border:1px solid black;'}); },
        function() { $(this).find("img:first").attr({src: $.resource("ZoomImage_iconMagnifier"), style :'border:1px solid gray;'}); })
      .click(fnAction);
    break;
  case "close":
  default:
    return $("<a href='#' title='"+$.resource('toolTipClose')+"'/>")
      .append("<img src='"+$.resource("iconCloseWindow")+"' />")
      .hover(
        function() { $(this).find("img:first").attr("src", $.resource("iconCloseWindowHover")); },
        function() { $(this).find("img:first").attr("src", $.resource("iconCloseWindow")); })
      .click(fnAction);
  }// end switch case
}

////////////////////////////////////
// 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
    ($.browser.msie && $.browser.version < 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('ZoomImage_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("toolTipImageZooming"),$.resource("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("toolTipImageZooming")+")","")+"<br />")
      // URL to metadata page from "a[href]" around img
        .append($.linkBuilder("imageMetadataLink", "", $(oriImg).closest("a").attr("href"), "target='_blank'"))
        .append( !zoomIsPossible ? "<br/><br/><span style='color:red;'>" + $.resource("zoomNotPossible") + "</span>" : "")
      );
  // take away IE6 modifications
  if ( !($.browser.msie && $.browser.version < 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 {
    if (modalLayer_ShowIviewerZoomImage) {
    modalLayer_ShowIviewerZoomImage ();
    } else {
      console.log("Error: Function modalLayer_ShowIviewerZoomImage() not found to make the image scalable.");
    }
  }
  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:
      // https://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", "https://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() {
    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("toolTipImageZooming") + ")";
    $(this).attr({title:newTitle, alt:newTitle});
  });
}
 
// END Modal Layer/Img Zoom
////////////////////////////

/* Load when documetn is ready */
$(document).ready(function() {
  initImageZooming();
});
// </syntaxhighlight>