MediaWiki:Gadget-LargerGallery.js

From wikishia

Note: After publishing, 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 / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
* Larger gallerie thumbnail images (in categories and galleries) with extra zoom feature
* Fulfilled [[phab:T3340]]
* @Author original Brian Wolff (User:Bawolff), archived in [[Commons:Village_pump/Archive/2016/08#Larger_thumbnails]]
* @Author User:Perhelion expanded/improved as Gadget, 03-2018; tested and further User:Speravir
* @Revision: 20:42, 1 August 2023 (UTC)
*/
// <nowiki>
// window.largerGallery = 1.5; // Custom autorun factor
// window.largerGalleryZoom = 1; // disable or change the hover zoom factor
/* eslint indent:["error","tab",{"outerIIFEBody":0}] */
/* eslint-disable vars-on-top, one-var, no-bitwise, valid-jsdoc, space-in-parens, computed-property-spacing, curly */
/* global jQuery, mediaWiki */
(function ($, mw) {
'use strict';
var oldMag,
	nsNr = mw.config.get('wgNamespaceNumber'),
	isCat = nsNr === 14,
	special,
	zoom = 0,
	initial, // default image size
	factor = 1,
	galleries, // , ul.gallery
	galleryBoxes;

function mouseenter(e) {
	var $this = $(this || e.target),
		$img = $this.find('img'),
		img = $img[0];
	if (!img) {
		// no supported element, e.g. in case of a video
		return;
	}
	e.stopPropagation();
	$this.parent().height('+=0');
	// Set higher resolution (1.5x)
	if (img && img.srcset)
		$img.attr('src', img.srcset.split(' ')[0]);

	$img.css({
		position: 'relative',
		// if smaller as default width, a center must be emulated ("+=" is needed for -moz)
		left: '+=' + String(($this.width() - img.width) / 2),
		zIndex: 1003,
		background: 'none',
		transform: 'scale(' + zoom + ')',
		transformOrigin: 'center 25%',
		transition: 'transform .6s'
	});
}

// Reset
function mouseleave(e) {
	e.stopPropagation();
	$(this || e.target)
		.find('img')
		.css({
			position: 'relative',
			left: '',
			zIndex: 1,
			background: '',
			transform: '',
			transition: 'transform .3s'
		});
	// [0].srcset = "" // not needed anymore, only maybe do set a new mag factor (which should very rarely the case!?)
}

/**
* { Change all galleries properties }
*
* @param    $ {DOM LI/IMG}               galleryboxes  The galleryboxes
* @param    {boolean}                    single        not multiple elements
* @return   {DOM IMG}  { }
*/
function run(galleryboxes, single) {
	var mag = Number(factor); // magnification factor (introduced by Speravir)

	galleryboxes = galleryboxes || galleryBoxes;
	zoom = window.largerGalleryZoom || 0;
	// calculate new mag
	if (oldMag && !single)
		mag /= oldMag;
	if (single) {
		initial = 120;
		oldMag = 0; // To set hover
	}

	// calculate relative to scale factor (max 2.5 x 1.4 = 3.5)
	zoom = zoom || 2.5 - factor * 0.6;
	zoom.toFixed(1);

/**
* Scale an image.
* @param   {DOM IMG}   img   The image element
* @return  {number}  { The new image size }
*/
	function scaleImage(img) {
		var iW = img.width || $(img).width(),
			max = 600, //  initial * 5 as the default MW value for raster img, so it could maybe load faster
			iWnew,
			iHnew, // new dimensions
			// responsive scaling factor
			bW = img.getAttribute('data-file-width') || max, // max width
			// Round the thumps to decadic numbers due the cache issue
			decadicRound = function (number) {
				return Math.round(number / 10) * 10;
			};

		if (/\.(svg|SVG)\.png$/.test(img.src)) { // SVG
			var SVGm = bW / iW; // SVG mag
			if (SVGm < 1) {
			// relative scaling to fixed dimension
				SVGm += -SVGm / mag + 1;
			} else {
				SVGm = mag;
			}
			// max = 512; // as the default MW value for SVG img, so it could maybe loaded faster
			iWnew = iW * SVGm;
		} else {
			iWnew = Math.min(iW * mag, bW); // maximum
		}

		iWnew = Math.min(iWnew, max); // maximum
		var proportion = img.height / iW;
		if (proportion > 1) { // higher then wide (needed for possible reset)
			iHnew = Math.max(120, proportion * iWnew); // minimum
			iWnew = iHnew / proportion; // if was lesser then 120
		} else {
			iWnew = Math.max(120, iWnew); // minimum
			iHnew = proportion * iWnew; // if was lesser then 120
		}
		var zW = Math.min(decadicRound(iWnew * 1.5), max); // maximum (default WM on file pages)
		img.height = decadicRound(iHnew); // keep proportion;
		iWnew = decadicRound(iWnew); // we need integer

		var zW2 = Math.min(decadicRound(iWnew * 2), max); // not larger as possible
		// img.srcset = img.src + " 1x, " + img.srcset || ''; // fallback if new image size not loaded?
		img.src = img.src.replace(/\d+(px-[^/]*$)/, iWnew + '$1');
		img.srcset = img.srcset ? img.srcset.replace(/\d+(px-[^/]* 1\.5x)/, zW + '$1').replace(/\d+(px-[^/]* 2x)/, zW2 + '$1') :
			img.src.replace(/\d+(px-[^/]*$)/, zW + '$1') + ' 1.5x, ' + img.src.replace(/\d+(px-[^/]*$)/, zW2 + '$1') + ' 2x';
		img.width = iWnew;
		return iWnew;
	}

	if (special && !single) {
		if (special === 'Search')
			$('.mw-search-results').first().width(function (i, w) {
				return w * Math.min(Math.max(mag, 0.66), 1.5);
			}).css('maxWidth', 'none');

		var i = galleryboxes.length;
		while (i--) {
			scaleImage(galleryboxes[i]);
		}
	} else {
		if (galleries) galleries.hide(); // JS speedup >^2
		galleryboxes.each(function () {
			var $this = $(this),
				child1 = $this.children('div').first(), // .thumb
				child2 = child1.children('span');
			if (!child2[0])
				return; // caption e.q. or faulty thumb

			var img = child2[0].querySelector('a img, video');

			if (!img)
				return; // non image file

			// already there?
			if (!oldMag && zoom) { // hover
				child2.on('mouseenter', mouseenter).on('mouseleave', mouseleave);
			} else {
				child2.height(''); // clear old absolute
				if (!zoom)
					child2.off('mouseenter mouseleave');
			}

			var iWnew = scaleImage(img);
			// box width not too small
			var bW = (iWnew < initial * mag) ?
					(initial * mag + Math.max(120, iWnew)) / 2 : // average
					iWnew,
				bH = Math.max(initial * mag, bW) / 2 - img.height / 2 + 15; // height
			bW += 35; // static padding

			$this.width(bW);
			// child1.first().height(bW - 5);
			child1.first().height('');
			child1.first().width(bW);
			// Default margin on max iW is 15, margin-width (needed for lesser hover)
			child2.css('margin', bH + 'px ' + ((bW - 5 - iWnew) / 2) + 'px');
		});
		if (galleries) galleries.show();

	}
	if (!single) {
		initial *= mag;
		oldMag = factor;
	}
}

/**
* Create select element and autorun if possible
*
* @param      {string}    targetSelect   The target id for the element before inserting the select
* @param      {jQuery object}  targetGallery  The target gallery
*/
function init(targetSelect, targetGallery) {
	$(function () {
		// only once
		if (document.getElementById('largerGallery2')) return;
		var customMag = window.largerGallery || 0, // magnification factor
			// Then, build a select and bind the change-event
			$select = $('<select>', {
				id: 'largerGallery' + (targetSelect ? '2' : ''),
				title: 'Larger-Gallery',
				style: 'float:right;margin:2px 5px 0 .5em;'
			}).on('change', function (e) {
				e.preventDefault();
				factor = this.value;
				// Dynamic page
				if (special === 0) galleryBoxes = galleries.find('li');
				run();
			}),
			factors = [1, 1.25, 1.5, 1.66, 1.83, 2.08, 2.5], // official values: 120, 150, 180, 200, 220, 250, 300, 400px; linear would be: 1.25, 1.5, 1.75, 2, 2.25, 2.5
			content = document.getElementById(isCat ? 'mw-category-media' : 'mw-content-text') || document.getElementById('bodyContent'),
			mi = 0; // mag index
		initial = 120;
		oldMag = 0;

		galleries = $('ul.mw-gallery-traditional, ul.mw-gallery-nolines'); // , .gallery
		if (nsNr % 2)
			galleries = $(); // Not on talk pages
		if (targetGallery) {
			galleries = targetGallery;
			special = 0;
		} else if (nsNr === -1) {
			special = mw.config.get('wgCanonicalSpecialPageName');
			if (special === 'Search' || special === 'Listfiles') {
				galleries = $(content);
				galleryBoxes = galleries.find('.image img, .mw-file-description img');
			} else {
				special = 0;
			}
		}
		if (!special) galleryBoxes = galleries.find('li');

		if (!galleryBoxes[0]) return; // Nothing to do

		customMag = Number(customMag);
		if (customMag && factors.indexOf(customMag) === -1) {
			factors.push(customMag);
			// We can use string sort as the numbers are only tens digits
			factors.sort();
			// Alignment to default values. Real add is disabled due cache issue
			mi = factors.indexOf(customMag);
			if (mi) {
				// It's closer to the lower value?
				if (mi < factors.length - 1) {
					if (customMag - factors[mi - 1] < factors[mi + 1] - customMag)
						customMag = factors[mi - 1];
					else customMag = factors[mi + 1];
				} // else { factors.pop(); } its too big
			} // else { factors.shift(); } its too small
			factors.splice(mi);
		}

		factors.forEach(function (i) {
			$select.append($('<option>', {
				value: i,
				text: (i * 100).toFixed() + '%' // Fix bad floating point arithmetic
			}));
		});

		// Set custom value for dropdown (not on special pages)
		if (customMag > 1 && !special) {
			$select[0].selectedIndex = factors.indexOf(customMag);
			factor = customMag;
			run(galleryBoxes); // autorun
		}
		// Insert dropdown field
		if (targetSelect) {
			targetSelect = document.getElementById(targetSelect);
			targetSelect.parentNode.appendChild($select[0]);
		} else {
			isCat = isCat ? content.getElementsByTagName('H2')[0] : 0;
			if (isCat) {
				var p = $(isCat).next();
				if (p && p.prop('tagName') === 'P')
					p.prepend($select);
				else
					content.insertBefore($select[0], isCat.nextSibling);
			} else {
				if (!$('#contentSub:visible, #siteSub:visible').first().prepend($select)[0])
					content.insertBefore($select[0], content.firstChild);
			}
		}
	});
}

mw.libs.largerGallery = { init: init, run: run };

init();

}(jQuery, mediaWiki));
// </nowiki>