MediaWiki:Gadget-Thickbox.js

From JoJo's Bizarre Encyclopedia - JoJo Wiki
Revision as of 06:55, 30 June 2024 by Vish (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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.
/* <pre>
 * Thickbox4MediaWiki v3.15 - Based on Thickbox 3.1 By Cody Lindley (http://www.codylindley.com)
 * Copyright (c) 2010 - 2024 Jesús Martínez (User:Ciencia_Al_Poder) / Vish (User:Vish), Original Thickbox Copyright (c) 2007 Cody Lindley
 * Licensed under the MIT License: http://www.opensource.org/licenses/mit-license.php
*/
window.Thickbox = (function($, mw) {
	'use strict';
	var _version = '3.15',
	// Minimum dimensions
	_minWidth = 210,
	// Margin between the image and the border of ThickBox
	_imageMarginWidth = 15,
	// Minimum margin to the edge of the window. If the image is exceeded it will be reduced
	_minMarginWidth = 30,
	_minMarginHeight = 15,
	// Waiting time for the loader to appear in ms
	_loaderWait = 500,
	// Internal
	_imgPreloader = null,
	_videoPreloader = null,
	_galleryData = null,
	_galleryIndex = -1,
	_width = null,
	_height = null,
	_getCaption = null,
	_imgTip = null,
	_imgTipTarget = null,
	_imgTipVisible = false,
	_loaderPresent = false,
	_loaderTm = null,
	_logger = console.error,
	// Private functions
	_init = function() {
		// COMPAT
		if (window.Thickbox4MediaWikiLoaded) { return; }
		window.Thickbox4MediaWikiLoaded = true;
		// We could put an event directly in each 'a.image', but this is much faster and more efficient (it only takes 20% in FF2) than to go through the entire DOM
		$('#mw-content-text').off('click.thickbox mouseover.thickbox_imgtip').on({
			'click.thickbox': _triggerEvent,
			'mouseover.thickbox_imgtip': _imgTipEvent
		});
	},
	_triggerEvent = function(e) {
	    try {
	        // If there is any special key pressed, we exit
	        if (e.ctrlKey || e.altKey || e.shiftKey) {
	            return true;
	        }
	        var target = e.target;
	        if (_isTag(target, 'img') || _isTag(target, 'video')) { // Gallery or thumb
	            var a = target.parentNode;
	            // External image or video handling
	            if (!a || (!_isTag(a, 'a') && !_isTag(target, 'video'))) {
	                return true;
	            }
	            if (_isExternalLink(a.href)) {
	                e.preventDefault(); // Prevent the default action
	                a.blur();
	                _getCaption = _getCaptionEmpty;
	                var gb = $(a).closest('li.gallerybox');
	                // Check if it's part of a gallery
	                if (gb.length) {
	                    var t = gb.closest('ul.gallery');
	                    if (t.length) {
	                        _getCaption = _getCaptionEmpty;
	                        _galleryData = t.find('div.ext2 a.external');
	                        _galleryIndex = _galleryData.index(a);
	                        _showMedia(a, true);
	                        return false;
	                    }
	                }
	                _showMedia(a, true);
	                return false; // Stop event propagation
	            }
	            // Internal images and videos
	            if (!_isClass(a, 'image') && !_isClass(a, 'gBoxVideo') && !_isClass(a, 'link-internal') && !_isClass(a, 'link-external') && !_isTag(target, 'video')) {
	                return true;
	            }
	            if (_isClass(target, 'thumbimage') || _isClass(target, 'html5Video')) {
	                // It's a thumbnail
	                a.blur();
	                _getCaption = _getCaptionThumb;
	                _showMedia(a);
	                return false;
	            }
	            var gb = $(a).closest('li.gallerybox');
	            // MediaWiki gallery
	            if (gb.length) {
	                var t = gb.closest('ul.gallery');
	                if (t.length) {
	                    a.blur();
	                    _getCaption = _getCaptionMW;
	                    _galleryData = t.find('div.thumb a.image, div.thumb a.gBoxVideo');
	                    _galleryIndex = _galleryData.index(a);
	                    _showMedia(a);
	                    return false;
	                }
	            }
	            // It's a generic thumbnail
	            a.blur();
	            _getCaption = _getCaptionEmpty;
	            _showMedia(a);
	            return false;
	        }
	        return true;
	    } catch (error) {
	        _log('Error in _triggerEvent: ' + error);
	        return true;
	    }
	},
	// Helper and speedy functions
	_isClass = function(el, cn) {
		return el.className && (el.className === cn || (' ' + el.className + ' ').indexOf(' ' + cn + ' ') != -1);
	},
	_isTag = function(el, tn) {
		return (el.nodeName && el.nodeName.toUpperCase() === tn.toUpperCase());
	},
	_isExternalLink = function(url) {
	    var externalPattern = /^https?:\/\/(?:[a-z]+\.)?imgur\.com/;
	    return externalPattern.test(url);
	},
	// Loader image
	_startLoader = function() {
		if (_loaderPresent || _loaderTm) {
			return;
		}
		if (_loaderWait > 0) {
			_loaderTm = setTimeout(_displayLoader, _loaderWait);
		} else {
			_displayLoader();
		}
	},
	_stopLoader = function() {
		var t = _loaderTm;
		_loaderTm = null;
		if (t) {
			clearTimeout(t);
		}
		if (_loaderPresent) {
			$('#TB_load').remove();
			_loaderPresent = false;
		}
	},
	_displayLoader = function() {
		_loaderPresent = true;
		_loaderTm = null;
		$(document.body).append('<div id="TB_load">');
	},
	// Main functions
	_preload = function() {
		$(document.body).addClass('thickbox_loaded');
		$('#TB_overlay').add('#TB_window').add('#TB_load').remove();
		$(document.body).append('<div id="TB_overlay"></div><div id="TB_window" class="fixedpos"></div>');
		$('#TB_overlay').click(_remove);
		_startLoader();
	},
	_showMedia = function(elem, isExternal) {
	    try {
	        var url, $a, $media, descUrl, TB_secondLine = '', TB_descLink;
	        _preload();
	        $a = $(elem);
	        $media = $a.find('> img, > video').eq(0);
	
	        if (isExternal) {
	            // Handle external media
	            url = $a.attr('href');
	            descUrl = url;
	        } else {
	            // Handle internal wiki media
	            if (_isTag($media[0], 'img')) {
	                url = _getUrlFromThumb($media.attr('src'));
	            } else if (_isTag($media[0], 'video')) {
	                url = $media.find('source').data('src') || $media.attr('src');
	            }
	            descUrl = $media.closest('li.gallerybox').data('filepage') || $a.attr('href');
	            if ($media.data('image-key')) {
	                // image-key is the name for the URL. Do not use image-name because it is encoded
	                descUrl = mw.util.wikiGetlink(mw.config.get('wgFormattedNamespaces')['6'] + ':' + decodeURIComponent($media.data('image-key')));
	            }
	        }
	
	        // Add file page link
	        TB_descLink = '<a id="TB_descLink" class="sprite details" title="Go to the media\'s description page">File Page</a>';
	
	        // Is it a gallery?
	        if (_galleryIndex != -1) {
	            TB_secondLine = '<div id="TB_secondLine">' +
	                '<span id="TB_imageCount"></span>' +
	                '<span id="TB_prev"><a href="#" title="See previous media [Left arrow]"></a></span>' +
	                '<span id="TB_next"><a href="#" title="See next media [Right arrow]"></a></span></div>';
	        }
	
	        // Append ThickBox elements
	        var thickboxContent = '<div id="TB_closeWindow"><a href="#" id="TB_closeWindowButton" title="Close [ESC]">close</a></div>' +
	            '<div id="TB_ImageOff">';
	
	        if (_isTag($media[0], 'img')) {
	            thickboxContent += '<img id="TB_Image" alt="Image" title="Close" />';
	        } else if (_isTag($media[0], 'video')) {
	            thickboxContent += '<video id="TB_Video" controls autoplay loop></video>';
	        }
	
	        thickboxContent += TB_descLink + '</div>' + TB_secondLine + '<div id="TB_caption"></div>';
	
	        $('#TB_window').empty().append(thickboxContent); // Empty the window before appending new content
	
	        // Update navigation if it's a gallery
	        if (_galleryIndex != -1) {
	            _updateNavigation();
	        }
	
	        // Set caption
	        $('#TB_caption').html((_getCaption($a) || null));
	
	        // Attach event listeners
	        $('#TB_Image').add('#TB_Video').add('#TB_closeWindowButton').click(_remove);
	        $(document).on('keyup.thickbox', _keyListener);
	        $('#TB_prev').add('#TB_next').click(_navigate);
	        $('#TB_descLink').attr('href', descUrl);
	
	        // Preload media
	        if (_isTag($media[0], 'img')) {
	            if (_imgPreloader === null) {
	                _imgPreloader = new Image();
	            }
	            _imgPreloader.onload = _mediaLoaded;
	            _imgPreloader.onerror = _mediaError;
	            _imgPreloader.src = ''; // chromium bug 7731
	            _videoPreloader = null; // Reset the preloader
	            _imgPreloader.src = url;
	        } else if (_isTag($media[0], 'video')) {
	            if (_videoPreloader === null) {
	                _videoPreloader = document.createElement('video');
	            }
	            _videoPreloader.onloadeddata = _mediaLoaded;
	            _videoPreloader.onerror = _mediaError;
	            _imgPreloader = null; // Reset the preloader
	            _videoPreloader.src = ''; // Reset the preloader
	            _videoPreloader.src = url;
	        }
	
	    } catch (e) {
	        _log('Error in _showMedia: ' + e);
	    }
	},
	_showElement = function(target) {
		try {
			var url = target.href, idx = url.indexOf('#');
			if (idx == -1) {
				return false;
			}
			var baseurl = url.substr(0, idx),
				hash = decodeURIComponent(url.substr(idx + 1)),
				// We check that the URL is from the same document
				locbase = document.location.href.replace(baseurl, ''),
				rel = document.getElementById(hash);
			if ((locbase !== '' && locbase.indexOf('#') !== 0) || rel === null) {
				return false;
			}

			$('#TB_overlay').add('#TB_window').remove();
			$(document.body).append('<div id="TB_overlay" class="transparent"></div><div id="TB_window"></div>');
			$('#TB_overlay').click(_remove);

			var titleHTML = '<div id="TB_title"><div id="TB_closeAjaxWindow"><a href="#" id="TB_closeWindowButton" title="Close [ESC]">close</a></div></div>',
				wnd = $('#TB_window'),
				cel = $(rel).clone();
			cel.contents().eq(0).remove();
			cel.find('> sup').remove();
			wnd.width(_minWidth).append(titleHTML + '<div id="TB_ajaxContent">' + cel.html() + '</div>');

			var tgEl = $(target),
				// horizontal space on each side of the element
				elOffset = tgEl.offset(),
				lw = elOffset.left,
				rw = $(document).width() - elOffset.left - tgEl.width(),
				// We calculate the optimal dimensions. We calculate the area and determine that the ideal is ratio 3/2
				prefw = parseInt(Math.sqrt(wnd.width() * wnd.height() * 3 / 2), 10),
				// Minimum width correction if scroll occurs
				cd = $('#TB_ajaxContent')[0];
			prefw += cd.scrollWidth - cd.clientWidth;
			// The minimum width should not be reduced
			if (prefw < _minWidth) {
				prefw = _minWidth;
			}
			// Position. 5px of margin with respect to the origin. Ideal situation: to the right of the element
			var margin = 5, left = $(document).width() - rw + margin;
			if (rw > prefw + margin) {
				// is already correct
			} else if (lw > prefw + margin) {
				left = lw - prefw - margin;
			} else if (lw < 250 || rw < 250) { // It does not fit on either side. We look to see if the minimum width (250) cannot be used. In that case the width we force it and put it to the right
				prefw = 250;
			} else if (rw > lw) { // The available width of the major side is used
				prefw = rw - margin;
			} else {
				prefw = lw - margin * 2;
				left = margin;
			}
			wnd.css({ width: prefw, left: left });
			// Now the vertical position. it needs that we have assigned the width to calculate it well
			var top = elOffset.top - parseInt(wnd.height(), 10) - margin;
			// If it does not fit above we place it below
			if (top < margin) {
				top = elOffset.top + tgEl.height() + margin;
			}
			wnd.css({ top: top, visibility: 'visible' });
			// Animation if it is outside the visual field
			if (($('html')[0].scrollTop || $('body')[0].scrollTop) > top - margin) {
				$('html,body').animate({ scrollTop: top - margin }, 250, 'swing');
			}

			$('#TB_closeWindowButton').click(_remove);
			$(document).on('keyup.thickbox', _keyListener);
		} catch (e) {
			_log('Error in _showElement: ' + e);
		}
	},
	//helper functions below
	_displayClean = function() {
		_stopLoader();
		$('#TB_window').css('visibility', 'visible');
	},
	_remove = function() {
		try {
			$(document).off('keyup.thickbox');
			_galleryData = null;
			_galleryIndex = -1;
			if (_imgPreloader !== null) {
				_imgPreloader.onload = null;
				_imgPreloader.onerror = null;
			}
			if (_videoPreloader !== null) {
				_videoPreloader.onloadeddata = null;
				_videoPreloader.onerror = null;
			}
			$('#TB_ImageOff').add('#TB_Image').add('#TB_Video').add('#TB_closeWindowButton').add('#TB_prev').add('#TB_next').off();
			$('#TB_window').add('#TB_Image').add('#TB_Video').queue('fx', []).stop();
			$('#TB_window').fadeOut('fast', function() { $('#TB_window').add('#TB_overlay').off().remove(); });
			_stopLoader();
			$(document.body).removeClass('thickbox_loaded');
			return false;
		} catch (e) {
			_log('Error in _remove: ' + e);
		}
	},
	_keyListener = function(e) {
		var keycode = e.which;
		if (keycode == 27) { // close
			_remove();
		} else if (keycode == 37) { // 'Left arrow' display previous media
			$('#TB_prev').click();
		} else if (keycode == 39) { // 'Right arrow' display next media
			$('#TB_next').click();
		}
	},
	_position = function(anim) {
		// Minimum width
		var border = 4;
		if (_width < _minWidth) {
			_width = _minWidth;
		}
		var o = { marginLeft: '-' + parseInt((_width / 2) + border, 10).toString() + 'px', width: _width + 'px', marginTop: '-' + parseInt((_height / 2) + border, 10).toString() + 'px' };
		if (anim) {
			$('#TB_window').animate(o, { queue: false, duration: 'fast' });
		} else {
			$('#TB_window').css(o);
		}
	},
	_getPageSize = function() {
		var de = document.documentElement,
			w = window.innerWidth || (de && de.clientWidth) || document.body.clientWidth,
			h = window.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
		return [w, h];
	},
	_getUrlFromThumb = function(thumb) {
		if (thumb.indexOf('.svg/') != -1) {
			return thumb;
		}

		// If the image is not thumb, or it is an SVG, we use the image as is.
		if (thumb.indexOf('/thumb/') == -1 || thumb.indexOf('.svg/') != -1 ) {
			return thumb;
		}
		var urlparts = thumb.split('/');
		return thumb.replace('/thumb/','/').replace('/'+urlparts[urlparts.length-1], '');
		
	},
	_getCaptionThumb = function(elem) {
		return elem.closest('.thumbinner').find('> .thumbcaption').clone().find('> div.magnify').remove().end().html();
	},
	_getCaptionEmpty = function(elem) {
		return $('<div>').text((elem.attr('title') || '')).html();
	},
	_getCaptionMW = function(gitem) {
		return gitem.closest('li.gallerybox').find('div.gallerytext').eq(0).html();
	},
	_imageError = function() {
		_stopLoader();
	},
	_mediaLoaded = function() {
	    if (_imgPreloader && _imgPreloader.src) {
	        _updateMediaView(_imgPreloader.width, _imgPreloader.height, _imgPreloader.src);
	    } else if (_videoPreloader && _videoPreloader.src) {
	        _updateMediaView(_videoPreloader.videoWidth, _videoPreloader.videoHeight, _videoPreloader.src);
	    }
	},
	_mediaError = function() {
	    _stopLoader();
	    _log('Media failed to load.');
	},
	_updateMediaView = function(mediaWidth, mediaHeight, mediaSrc) {
	    var navigation = (_galleryIndex != -1),
	        $img = $('#TB_Image'),
	        $video = $('#TB_Video'),
	        $wndH = $('#TB_window').height(),
	        // Resizing large media
	        pagesize = _getPageSize(),
	        // Maximum dimensions
	        x = pagesize[0] - _minMarginWidth * 2 - _imageMarginWidth * 2,
	        y = pagesize[1] - _minMarginHeight * 2 - $wndH + ($img.height() !== null && $img.height() !== undefined ? $img.height() : $video.height()),
	        firstNav, mediaOpt;
	    
	    // You can enter by one or both. In fact, this check is enough, because if you have to go through both it does not matter which side is reduced first
	    if (mediaWidth > x) {
	        mediaHeight = mediaHeight * (x / mediaWidth);
	        mediaWidth = x;
	    }
	    if (mediaHeight > y) {
	        mediaWidth = mediaWidth * (y / mediaHeight);
	        mediaHeight = y;
	    }
	    // End Resizing
	
	    firstNav = ($img.attr('src') || '') === '' && ($video.attr('src') || '') === '';
	    // Thickbox window dimensions to position
	    _width = mediaWidth + _imageMarginWidth * 2; // 15px of space on each side
	    // We know the height of the window. Just replace the old media and put the new dimensions. The height has to be done differently because more elements are involved than in the width
	    _height = $wndH - ($img.height() !== null && $img.height() !== undefined ? $img.height() : $video.height()) + mediaHeight;
	    
	    // Clear previous src attributes
	    $img.attr('src', '');
	    $video.attr('src', '');
	    
	    if ($img.length && _isTag($img[0], 'img')) {
	        $img.attr({
	            src: mediaSrc,
	            alt: $('#TB_caption').text()
	        });
	    } else if ($video.length && _isTag($video[0], 'video')) {
	        $video.attr({
	            src: mediaSrc
	        });
	    }
	
	    mediaOpt = { width: mediaWidth, height: mediaHeight, opacity: 1 };
	    // We look to see if it loads when opening or after browsing. If it comes from opening, without animation
	    if (firstNav) {
	        if ($img.is(':visible')) {
	            $img.css(mediaOpt);
	        } else {
	            $video.css(mediaOpt);
	        }
	    } else {
	        if ($img.is(':visible')) {
	            $img.animate(mediaOpt, { duration: 'fast' });
	        } else {
	            $video.animate(mediaOpt, { duration: 'fast' });
	        }
	    }
	
	    _position(navigation && !firstNav);
	    _displayClean();
	},
	_updateNavigation = function() {
		var seq = _galleryIndex, len = _galleryData.length;
		$('#TB_prev').css('display', (seq === 0 ? 'none' : ''));
		$('#TB_next').css('display', (seq >= len - 1 ? 'none' : ''));
		$('#TB_imageCount').text('Media ' + (seq + 1) + ' of ' + len);
	},
	_navigate = function() {
	    try {
	        var url, seq = _galleryIndex + (this.id == 'TB_prev' ? -1 : 1), len = _galleryData.length, gitem, $media;
	        if (seq < 0 || seq > len - 1) {
	            return false;
	        }
	        _galleryIndex = seq;
	        gitem = _galleryData.eq(seq);
	        $media = gitem.find('> img, > video').eq(0);
	        if (_isTag($media[0], 'img')) {
	            url = _getUrlFromThumb($media.attr('src'));
	        } else {
	            url = $media.find('source').data('src') || $media.attr('src');
	        }
	        _updateNavigation();
	        if ((_imgPreloader && _imgPreloader.src != url) || (_videoPreloader && _videoPreloader.src != url)) {
	            $('#TB_window').stop();
	            $('#TB_Image, #TB_Video').queue('fx', []).stop().each(function() {
	                _startLoader();
	                if (url.length > 3 && url.substr(url.length - 4).toLowerCase() == '.svg') {
	                	// For SVG we already know its aspect ratio, although not its original dimensions
	                	// It would have to be done differently to load the SVG. Instead, here it is dynamically determined
	                	// It is artificially enlarged, and then this function will reduce it to the maximum window size
	                    _updateMediaView($media.prop('width') * 1000, $media.prop('height') * 1000, url);
	                    if (_imgPreloader) {
	                        _imgPreloader.src = '';
	                    }
	                } else {
	                    if (_isTag($media[0], 'img')) {
	                        _imgPreloader.src = url;
	                    } else {
	                        _videoPreloader.src = url;
	                    }
	                }
	            });
	        }
	        $('#TB_caption').html((_getCaption(gitem) || null));
	        $('#TB_descLink').attr('href', gitem.attr('href'));
	        return false;
	    } catch (e) {
	        _log('Error in _navigate: ' + e);
	        return false;
	    }
	},
	_setParams = function(p) {
		var val;
		if (typeof p != 'object') {
			return;
		}
		for (var n in p) {
			if (p.hasOwnProperty(n)) {
				val = p[n];
				switch (n) {
					case 'minWidth':
						_minWidth = val;
						break;
					case 'imageMarginWidth':
						_imageMarginWidth = val;
						break;
					case 'minMarginWidth':
						_minMarginWidth = val;
						break;
					case 'minMarginHeight':
						_minMarginHeight = val;
						break;
					case 'loaderWait':
						_loaderWait = (typeof val == 'number' && val);
						break;
					case 'logger':
						_logger = (typeof val == 'function' && val);
						break;
				}
			}
		}
	},
	_log = function(msg) {
		if (_logger) {
			_logger(msg);
		}
	},
	_imgTipEvent = function(e) {
		var target = e.target, a, t;
		if (e.ctrlKey || e.altKey || e.shiftKey) {
			_hideImgTip();
			return;
		}
		if (_isTag(target, 'img')) { // Gallery or thumb
			a = target.parentNode;
			if (!_isTag(a, 'a') || !_isClass(a, 'image') || _isClass(a, 'link-internal')) {
				_hideImgTip();
				return;
			}
			t = $(target);
			// We show only if the image has a minimum size
			if (t.width() < 40 || t.height() < 40) {
				return;
			}
			_showImgTip(t);
			return;
		}
		_hideImgTip();
	},
	_imgTipClickEvent = function() {
		if (_imgTipTarget) {
			$(_imgTipTarget).click();
			return false;
		}
	},
	_createImgTip = function() {
		_imgTip = $('<div id="TB_imagetip" title="Click on the image to enlarge. Click with Ctrl or Shift to go to the file page.">').appendTo(document.body);
		_imgTip.on('click', _imgTipClickEvent);
	},
	_showImgTip = function(target) {
		if (!_imgTip) {
			_createImgTip();
		}
		var of = target.offset();
		_imgTip.css({
			display: 'block',
			left: of.left + target.width(),
			top: of.top
		});
		_imgTipVisible = true;
		_imgTipTarget = target;
	},
	_hideImgTip = function() {
		if (_imgTipVisible) {
			_imgTip.css('display', 'none');
			_imgTipVisible = false;
			_imgTipTarget = null;
		}
	};

	// Public functions
	return {
		init: _init,
		showMedia: _showMedia,
		showElement: _showElement,
		remove: _remove,
		setParams: _setParams
	};

}(jQuery, mw));

if (mw.config.get('wgAction', '') != 'history' || !(mw.config.get('wgNamespaceNumber', 0) == -1 && mw.config.get('wgCanonicalSpecialPageName', '') == 'Recentchanges')) {
	$(window.Thickbox.init);
}
/* </pre> */