MediaWiki:GallerySlideshow.js

/*global mw, jQuery, GallerySlide, alert, prompt */ /*jshint bitwise: false, laxbreak: true, browser: true, onevar: false, nomen: false, smarttabs: true */ /** * This is a derivative work of: */	/**	 * jQuery Galleriffic plugin *	 * Copyright (c) 2008 Trent Foley (http://trentacular.com) * Licensed under the MIT License: *  http://www.opensource.org/licenses/mit-license.php *	 * Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com) *	 */ /** * Rewritten for commons by User:DieBuche, * additional features by User:Rillke * jshint valid */

(function($) {

if (typeof window.GallerySlide !== 'undefined' || mw.config.get('wgNamespaceNumber') < 0) { return; }

// Declare global variable so that it is no longer undefined, // will be populated later from the document ready hook window.GallerySlide = null;

// Globally keep track of all images by their unique hash. Each item is an image data object. var isCategory = (mw.config.get('wgNamespaceNumber') === 14); var isRtl = $('body').hasClass('rtl'); var allImages = {}; var imageCounter = 0;

// Galleriffic static class $.galleriffic = { version: '2.2',

// Strips invalid characters and any leading # characters normalizeHash: function(hash) { return hash.replace('#', ''); },

getImage: function(hash) { if (!hash) { return; }

hash = $.galleriffic.normalizeHash(hash); return allImages[hash]; },

// Global function that looks up an image by its hash and displays the image. // Returns false when an image is not found for the specified hash. // @param {String} hash This is the unique hash value assigned to an image. gotoImage: function(hash) { var imageData = $.galleriffic.getImage(hash); if (!imageData) { return false; }			var gallery = imageData.gallery; gallery.gotoImage(imageData);

return true; }

};

var i18n; var i18nStore = { ca: { delayInsertBtn: 'Canvia el temps de demora', delayInsert: 'Demora en milisegons entre cada imatge', delayInvalid: 'Entrada no vàlida. Només s’accepten números superiors a 1500.', playLinkText: 'Reprodueix', pauseLinkText: 'Pausa', prevLinkText: 'Anterior', nextLinkText: 'Següent', hideText: 'Tanca la presentació', continueKeyHowTo: 'Clau de represa - Podeu desar aquesta clau o afegir l’enllaç en les adreces d’interès si més endavant voleu començar des d’aquesta posició.', continueKeyInsert: 'Afegiu la clau de represa. Heu d’anar endavant en la presentació per constatar el canvi.', continueKeyInsertBtn: 'Afegeix la clau de represa', continueKeyInvalid: 'Clau invàlida.', licenseLabel: 'Llicències disponibles: ', uploaderLabel: 'Usuari ', helpLinkTitle: 'Ajuda i documentació d’aquesta eina', descriptionLoadText: 'Carregant la descripció ...' },		en: { delayInsertBtn: 'Set delay in ms', delayInsert: 'How many ms to wait for a new image?', delayInvalid: 'Invalid input. Only numbers greater than 1500 are accepted.', playLinkText: 'Play', pauseLinkText: 'Pause', prevLinkText: 'Previous', nextLinkText: 'Next', hideText: 'Close slideshow', continueKeyHowTo: 'Continue Key - You can save this key or bookmark the link if you\'d like to start at this position later.', continueKeyInsert: 'Please insert the continue-key. You have to go forward in the slideshow to see effect.', continueKeyInsertBtn: 'Insert continue-key', continueKeyInvalid: 'Invalid key.', licenseLabel: 'Available Licenses: ', uploaderLabel: 'Uploader ', helpLinkTitle: 'Help and documentation for this tool', descriptionLoadText: 'Loading description ...' },		fr: { delayInsertBtn: 'Définir l’intervalle de temps', delayInsert: 'Combien de millisecondes entre chaque image ?', delayInvalid: 'Entrée invalide. Seuls les nombres supérieurs à 1500 sont acceptés.', playLinkText: 'Lire', pauseLinkText: 'Pause', prevLinkText: 'Précédent', nextLinkText: 'Suivant', hideText: 'Quitter le diaporama', continueKeyHowTo: 'Clé de départ - Vous pouvez sauvegarder cette clé ou mettre en marque-page le lien si vous souhaitez commencer à cette position plus tard.', continueKeyInsert: 'Veuillez insérer la clé de départ. Vous devez faire défiler le diaporama pour constater un changement.', continueKeyInsertBtn: 'Insérer la clé de départ', continueKeyInvalid: 'Clé invalide', licenseLabel: 'Licences disponibles : ', uploaderLabel: 'La personne qui a téléchargé le fichier sur le serveur ', helpLinkTitle: 'Aide et documentation de cet outil', descriptionLoadText: 'Chargement de la description ...' },		ml: { delayInsertBtn: 'കാലതാമസം മില്ലിസെക്കന്റിൽ', delayInsert: 'പുതിയ ചിത്രത്തിനായി എത്ര മില്ലിസെക്കന്റുകൾ കാത്തിരിക്കേണ്ടതുണ്ട്?', delayInvalid: 'നൽകിയ വില അസാധുവാണ്. 1500-നു മുകളിലുള്ള സംഖ്യകൾ മാത്രമേ സ്വീകരിക്കാനാകൂ.', playLinkText: 'പ്രവർത്തിപ്പിക്കുക', pauseLinkText: 'ഇടയ്ക്കുനിർത്തുക', prevLinkText: 'മുൻപത്തേത്', nextLinkText: 'അടുത്തത്', hideText: 'സ്ലൈഡ്ഷോ അടയ്ക്കുക', continueKeyHowTo: 'തുടർച്ചാ ചാവി - ഈ സ്ഥാനത്തുനിന്ന് പിന്നീട് തുടങ്ങണമെന്നുണ്ടെങ്കിൽ താങ്കൾക്ക് ഈ ചാവി സൂക്ഷിച്ചുവെയ്ക്കുകയോ, ഈ കണ്ണി ബുക്ക്\u200cമാർക്ക് ചെയ്യുകയോ ചെയ്യാവുന്നതാണ്.', continueKeyInsert: 'ദയവായി തുടർച്ചാ-ചാവി നൽകുക. ഫലത്തിനായി സ്ലൈഡ്\u200cഷോയിൽ മുന്നോട്ട് പോകേണ്ടതാണ്.', continueKeyInsertBtn: 'തുടർച്ചാ-ചാവി നൽകുക', continueKeyInvalid: 'ചാവി അസാധുവാണ്.', licenseLabel: 'ലഭ്യമായ അനുമതികൾ: ', uploaderLabel: 'അപ്\u200cലോഡ് ചെയ്തയാൾ ', helpLinkTitle: 'ഈ ഉപകരണത്തിനുള്ള സഹായവും വിവരണവും', descriptionLoadText: 'വിവരണം ശേഖരിക്കുന്നു...' },		ms: { delayInsertBtn: 'Tetapkan selang masa dalam milisaat', delayInsert: 'Berapa milisaat perlu ditunggu untuk ke imej baru?', delayInvalid: 'Input tidak sah. Hanya nombor lebih daripada 1500 diterima.', playLinkText: 'Main', pauseLinkText: 'Jeda', prevLinkText: 'Sebelumnya', nextLinkText: 'Selepasnya', hideText: 'Tutup tayangan slaid', continueKeyHowTo: 'Kekunci Sambungan - Anda boleh menyimpan kekunci ini atau tandakan pautan jika anda ingin bermula pada kedudukan ini nanti.', continueKeyInsert: 'Sila masukkan kekunci untuk bermula. Anda mesti menatal melalui tayangan slaid untuk melihat perubahan.', continueKeyInsertBtn: 'Masukkan kekunci', continueKeyInvalid: 'Kekunci tidak sah.', licenseLabel: 'Lesen yang disediakan: ', uploaderLabel: 'Pemuat naik ', helpLinkTitle: 'Bantuan dan pendokumenan alatan ini', descriptionLoadText: 'Penerangan sedang dimuatkan ...' },		ru: { delayInsertBtn: 'Установить задержку в милисек.', delayInsert: 'Сколько милисек. ждать до смены изображения?', delayInvalid: 'Неправильный ввод. Разрешены только числа больше 1500.', playLinkText: 'Запуск', pauseLinkText: 'Пауза', prevLinkText: 'Предыдущее', nextLinkText: 'Следующее', hideText: 'Закрыть слайдшоу', continueKeyHowTo: 'Ключ продолжения — вы можете сохранить этот ключ или добавить ссылку в закладки, если хотите начать с этого места позже.', continueKeyInsert: 'Вставьте, пожалуйста, ключ продолжения. Вам нужно вернуться обратно в слайдшоу, чтобы увидеть эффект.', continueKeyInsertBtn: 'Вставьте ключ продолжения', continueKeyInvalid: 'Неверный ключ', licenseLabel: 'Доступные лицензии: ', uploaderLabel: 'Загрузивший ', helpLinkTitle: 'Справка и документация для этого инструмента', descriptionLoadText: 'Загрузка описания ...' },		sv: { delayInsertBtn: 'Ställ in fördröjning i ms', delayInsert: 'Hur många ms det ska dröja innan nästa bild visas?', delayInvalid: 'Ogiltig indata. Endast nummer större än 1500 accepteras.', playLinkText: 'Spela', pauseLinkText: 'Pausa', prevLinkText: 'Föregående', nextLinkText: 'Nästa', hideText: 'Stäng bildspel', continueKeyHowTo: 'Fortsättningsnyckel - Du kan spara denna nyckel eller lägga ett bokmärke på länken om du vill börja på denna plats senare.', continueKeyInsert: 'Var god ange fortsättningsnyckeln. Du måste gå framåt i bildspelet för att det ska träda i kraft.', continueKeyInsertBtn: 'Ange fortsättningsnyckel', continueKeyInvalid: 'Ogiltig nyckel.', licenseLabel: 'Tillgängliga licenser: ', uploaderLabel: 'Uppladdare ', helpLinkTitle: 'Hjälp och dokumentation för detta verktyg', descriptionLoadText: 'Läser in beskrivning ...' }	};

i18n = $.extend({}, i18nStore.en, i18nStore[mw.config.get('wgUserLanguage').split('-')[0]], i18nStore[mw.config.get('wgUserLanguage')]);

var defaults = { delay: 7000, preloadAhead: 25, enableKeyboardNavigation: true, autoPlay: false, defaultTransitionDuration: 700, defaultSizes: [{ w: 1500, h: 1500 }, {			w: 1280, h: 1024 }, {			w: 1024, h: 768 }, {			w: 800, h: 600 }, {			w: 640, h: 480 }, {			w: 320, h: 240 }, {			w: 220, h: 240 }],		// The maximum image heigh (window's height - space - thumbbar) maxImageHeight: $(window).height - 125, // The maximum image width (window's width - navi-controls - space - caption bar) maxImageWidth: $(window).width - 50 - Math.max(Math.min($(window).width * 0.2, 320), 180), actualMaxSize: { w: 640, h: 480 },		cmdir: 'asc', continueKey: '', continueKeyPattern: isCategory ? /^file\|[\da-fA-F]+\|\d+$/ : /\d+\|.+/, lastPositionExpiry: 2, readFromScreen: false, readFromScreenSmallImages: false, licenseRecognization: [ // RegExp for the tag			note to add to the thumb-page [/Category:CC[\- _]BY-SA.*/i, 'CC-By-SA'], [/Category:CC[\- _]BY.*/i, 'CC-By'], [/Category:CC[\- _]Zero.*/i, 'CC0'], [/Category:GFDL.*/i, 'GFDL'], [/Category:PD[\- _]Old.*/i, 'PD-old'], [/Category:PD[\- _]self.*/i, 'PD-self'], [/Category:PD[\- _]author.*/i, 'PD-author'], [/Category:PD.*/i, 'PDx'], [/Category:FAL/i, 'Art Libre - Free Art'], [/Category:Images requiring attribution/i, 'Attribution'], [/Category:Copyrighted free use.*/i, 'Copyrighted FreeUse'], [/Category:Mozilla Public License/i, 'MPL'], [/Category:GPL/i, 'GPL'], [/Category:LGPL/i, 'LGPL'], [/Category:Copyright by Wikimedia.*/i, '(c)WMF'], [/Category:Free screenshot.*/i, 'free-Screenshot'] ],		onSlideChange: function(prevIndex, nextIndex) { var current, offset;

var displayed = Math.floor(this.$thumbsUl.parent.width / 81); var spaceRight = displayed - (nextIndex - this.hiddenLeft); var spaceLeft = (1 + nextIndex - this.hiddenLeft); if (spaceRight < 3) { // Time to slide viewport current = this.$thumbsUl.css('left').replace('px', ''); offset = (parseFloat(current) - (3 - spaceRight) * 81); this.$thumbsUl.animate({					left: offset				}, 'fast'); this.hiddenLeft = this.hiddenLeft + (3 - spaceRight); }			if (spaceLeft < 3 && nextIndex > 1) { current = this.$thumbsUl.css('left').replace('px', ''); offset = (parseFloat(current) + (3 - spaceLeft) * 81); this.$thumbsUl.animate({					left: offset				}, 'fast'); this.hiddenLeft = this.hiddenLeft - (3 - spaceLeft); }

if (nextIndex === 0) { this.$thumbsUl.animate({					left: 0				}, 'fast'); this.hiddenLeft = 0; }

if (this.data.length - 5 < nextIndex) { // Time to fetch more this.queryApi; }		},		// accepts a delegate like such: function(prevIndex, nextIndex) { ... }		onTransitionOut: undefined, // accepts a delegate like such: function(slide, caption, isSync, callback) { ... }		onTransitionIn: undefined };

// Primary Galleriffic initialization function that should be called on the thumbnail container. $.fn.galleriffic = function(settings) { // Extend Gallery Object $.extend(this, {			// Returns the version of the script			version: $.galleriffic.version,

// Current state of the slideshow isSlideshowRunning: false, slideshowTimeout: undefined, hiddenLeft: 0, apiURL: mw.util.wikiScript('api'), indexURL: mw.util.wikiScript('index'), initial: true, data: [],

// This function is attached to the click event of generated hyperlinks within the gallery clickHandler: function(e, link) { this.pause;

// The href attribute holds the unique hash for an image var hash = $.galleriffic.normalizeHash($(link).attr('href')); $.galleriffic.gotoImage(hash); e.preventDefault;

},

createContainer: function { var gallery = this;

this.$slideshowContainer = $(' '); this.$imageContainer = $(' '); this.$captionContainer = $(' '); this.$loadingContainer = $(' '); this.$controlsContainer = $(' '); // Gray lines for navigation this.$ctrBack = $(' ', {					'class': 'bar-bwd'				}).append($(' ', { 'class': 'bar-btn-bwd' }).text('<')).click(function(e) {					gallery.previous;					e.preventDefault;				}).mouseenter(function {					$(this).find('div').fadeIn('fast');				}).mouseleave(function {					$(this).find('div').fadeOut('fast');				});

this.$ctrFwd = $(' ', {					'class': 'bar-fwd'				}).append($(' ', { 'class': 'bar-btn-fwd' }).text('>')).click(function(e) {					gallery.next;					e.preventDefault;				}).mouseenter(function {					$(this).find('div').last.fadeIn('fast');				}).mouseleave(function {					$(this).find('div').last.fadeOut('fast');				});

this.$closeButton = $(' ', {					'class': 'slideshow-close-button',					'title': this.hideText				}).text('×').click(function(e) {					gallery.pause;					gallery.toggleVisibility;					// stop propagation & prevent default					return false;				});

this.append('  '); this.append(this.$controlsContainer).append(this.$slideshowContainer); this.$slideshowContainer.append(this.$loadingContainer).append(this.$captionContainer).append(this.$imageContainer); this.append(this.$ctrBack).append(this.$ctrFwd.prepend(this.$closeButton)); this.$thumbsUl = this.find('ul.thumbs'); },			// Scrapes the thumbnail container for thumbs and adds each to the gallery initializeThumbs: function {

var data = this.passedData;

var gallery = this;

$.each(data, function(i, imageData) {					var hash;					imageData.index = hash = imageCounter;					imageData.gallery = gallery;

var aspect = (imageData.width / imageData.height); var size = (aspect > 1) ? 'height' : 'width'; var $thumb = $(' '); $thumb.css('opacity', 0.67).hover(function {						$(this).not('.selected').fadeTo('fast', 1);					}, function {						$(this).not('.selected').fadeTo('fast', 0.67);					});

gallery.$thumbsUl.append($thumb);

imageData.caption = $(' ').append(					$('', { href: imageData.link, text: imageData.title.replace('File:', ).replace(/\.[\w]{3,4}$/, ) })).html;

// Register the image globally allImages['' + hash] = imageData;

// Setup attributes and click handler $thumb.find('a').attr('href', '#' + hash).removeAttr('name').click(function(e) {						gallery.clickHandler(e, this);					}); imageCounter++;

});				this.data = this.data.concat(data);

return this; },

isPreloadComplete: false,

// Initalizes the image preloader preloadInit: function { if (this.preloadAhead === 0) { return this; }

this.preloadStartIndex = this.currentImage.index; var nextIndex = this.getNextIndex(this.preloadStartIndex); return this.preloadRecursive(this.preloadStartIndex, nextIndex); },

// Changes the location in the gallery the preloader should work // @param {Integer} index The index of the image where the preloader should restart at. preloadRelocate: function(index) { // By changing this startIndex, the current preload script will restart this.preloadStartIndex = index; return this; },

// Recursive function that performs the image preloading // @param {Integer} startIndex The index of the first image the current preloader started on. // @param {Integer} currentIndex The index of the current image to preload. preloadRecursive: function(startIndex, currentIndex) { // Check if startIndex has been relocated if (startIndex !== this.preloadStartIndex) { var nextIndex = this.getNextIndex(this.preloadStartIndex); return this.preloadRecursive(this.preloadStartIndex, nextIndex); }

var gallery = this;

// Now check for preloadAhead count var preloadCount = currentIndex - startIndex; if (preloadCount < 0) { preloadCount = this.data.length - 1 - startIndex + currentIndex; }				if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) { // Do this in order to keep checking for relocated start index setTimeout(function {						gallery.preloadRecursive(startIndex, currentIndex);					}, 500); return this; }

var imageData = this.data[currentIndex]; if (!imageData) { return this; }

// If already loaded, continue if (imageData.image) { return this.preloadNext(startIndex, currentIndex); }

// Preload the image var image = new Image;

image.onload = function { imageData.image = this; gallery.preloadNext(startIndex, currentIndex); };

image.alt = imageData.title; image.src = imageData.slideUrl;

return this; },

// Called by preloadRecursive in order to preload the next image after the previous has loaded. // @param {Integer} startIndex The index of the first image the current preloader started on. // @param {Integer} currentIndex The index of the current image to preload. preloadNext: function(startIndex, currentIndex) { var nextIndex = this.getNextIndex(currentIndex); if (nextIndex === startIndex) { this.isPreloadComplete = true; } else { // Use setTimeout to free up thread var gallery = this; setTimeout(function {						gallery.preloadRecursive(startIndex, nextIndex);					}, 100); }

return this; },

// Safe way to get the next image index relative to the current image. // If the current image is the last, returns 0 getNextIndex: function(index) { var nextIndex = index + 1; if (nextIndex >= this.data.length) { nextIndex = 0; }				return nextIndex; },

// Safe way to get the previous image index relative to the current image. // If the current image is the first, return the index of the last image in the gallery. getPrevIndex: function(index) { var prevIndex = index - 1; if (prevIndex < 0) { prevIndex = this.data.length - 1; }				return prevIndex; },

// Pauses the slideshow pause: function { this.isSlideshowRunning = false; $(document).triggerHandler('slideshow', ['actionPause', this]); // For external scripts

if (this.slideshowTimeout) { clearTimeout(this.slideshowTimeout); this.slideshowTimeout = undefined; }

if (this.$controlsContainer) { this.$controlsContainer.find('div.nav-controls a.gs-play-pause').removeClass('gs-play-pause').addClass('gs-play-play').attr('title', this.playLinkText).attr('href', '#play'); }

return this; },

// Plays the slideshow play: function { this.isSlideshowRunning = true; $(document).triggerHandler('slideshow', ['actionPlay', this]); // For external scripts

if (this.$controlsContainer) { this.$controlsContainer.find('div.nav-controls a.gs-play-play').removeClass('gs-play-play').addClass('gs-play-pause').attr('title', this.pauseLinkText).attr('href', '#pause'); }

if (!this.slideshowTimeout) { var gallery = this; this.slideshowTimeout = setTimeout(function {						gallery.next(true);					}, this.delay); }

return this; },

// Toggles the state of the slideshow (playing/paused) toggleSlideshow: function { if (this.isSlideshowRunning) { this.pause; } else { this.play; }

return this; },			// Advances the gallery to the next image. // @param {Boolean} dontPause Specifies whether to pause the slideshow. next: function(dontPause) { this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause); return this; },

// Navigates to the previous image in the gallery. // @param {Boolean} dontPause Specifies whether to pause the slideshow. previous: function(dontPause) { this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause); return this; },

// Navigates to the image at the specified index in the gallery // @param {Integer} index The index of the image in the gallery to display. // @param {Boolean} dontPause Specifies whether to pause the slideshow. gotoIndex: function(index, dontPause) { if (!dontPause) { this.pause; }

if (index < 0) { index = 0; } else if (index >= this.data.length) { index = this.data.length - 1; }

var imageData = this.data[index];

this.gotoImage(imageData);

return this; },

// This function is guaranteed to be called anytime a gallery slide changes. // @param {Object} imageData An object holding the image metadata of the image to navigate to. gotoImage: function(imageData) { var index = imageData.index;

if (this.onSlideChange) { this.onSlideChange(this.currentImage.index, index); }

this.currentImage = imageData; this.preloadRelocate(index);

this.refresh;

return this; },

// Returns the default transition duration value. The value is halved when not // performing a synchronized transition. // @param {Boolean} isSync Specifies whether the transitions are synchronized. getDefaultTransitionDuration: function(isSync) { if (isSync) { return this.defaultTransitionDuration; }				return this.defaultTransitionDuration / 2; },

// Rebuilds the slideshow image and controls and performs transitions refresh: function { var imageData = this.currentImage; if (!imageData) { return this; }

var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current'); var previousCaption = 0;

if (this.$captionContainer) { previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current'); }

// Perform transitions simultaneously if the next image is already preloaded var isSync = imageData.image;

// Flag we are transitioning var isTransitioning = true; var gallery = this;

var transitionOutCallback = function { // Flag that the transition has completed isTransitioning = false;

// Remove the old slide previousSlide.remove;

// Remove old caption if (previousCaption) { previousCaption.remove; }

if (!isSync) { if (imageData.image && imageData.index === gallery.data[gallery.currentImage.index].index) { gallery.buildImage(imageData, isSync); } else { // Show loading container if (gallery.$loadingContainer) { gallery.$loadingContainer.show; }							}						}					};

if (previousSlide.length === 0) { // For the first slide, the previous slide will be empty, so we will call the callback immediately transitionOutCallback; } else {

previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback); if (previousCaption) { previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0); }

}

// Go ahead and begin transitioning in of next image if (isSync) { this.buildImage(imageData, isSync); }

if (!imageData.image) { var image = new Image;

// Wire up mainImage onload event image.onload = function { imageData.image = this;

// Only build image if the out transition has completed and we are still on the same image hash if (!isTransitioning && imageData.index === gallery.data[gallery.currentImage.index].index) { gallery.buildImage(imageData, isSync); }					};

// set alt and src image.alt = imageData.title; image.src = imageData.slideUrl; }

// This causes the preloader (if still running) to relocate out from the currentIndex this.relocatePreload = true;

return this.syncThumbs; },

// Shrinking The Tower of Babel // Hide other languages, if script is available shrinkTowerOfBabel: function($node) { if (window.multilingual) { var ml = window.multilingual; ml.langCountThreshold = 2; ml.method = 'prepend'; ml.$p = ml.$OuterContainer = $node; ml.init; }			},

// Called by the refresh method after the previous image has been transitioned out or at the same time // as the out transition when performing a synchronous transition. // @param {Object} imageData An object holding the image metadata of the image to build. // @param {Boolean} isSync Specifies whether the transitions are synchronized. buildImage: function(imageData, isSync) { var gallery = this; var nextIndex = this.getNextIndex(imageData.index);

// We have loaded bigger images, size them down, now; 1 prevents upscaling (looks ugly) var scaleRatio = Math.max(				imageData.width / this.maxImageWidth,				imageData.height / (this.maxImageHeight - 20),				1);

var imgWidth = Math.floor(imageData.width / scaleRatio); var imgHeight = Math.floor(imageData.height / scaleRatio);

// computing the "center-position of the space" var hSpace = isRtl ? (this.$captionContainer.position.left - 2 * this.$imageContainer.position.left) : (this.$imageContainer.width - this.$captionContainer.position.left - this.$captionContainer.width); var hPos = isRtl ? (this.$imageContainer.width - this.$captionContainer.position.left + 2 * this.$imageContainer.position.left + (hSpace - imgWidth) / 2) : (this.$captionContainer.position.left + this.$captionContainer.width + (hSpace - imgWidth) / 2); var vSpace = this.$imageContainer.height - 130; var top = (vSpace - imgHeight) / 2;

// Send a XHrequest in case of unknown description if (typeof imageData.description !== 'string') { this.queryFile(imageData.title); }				if (typeof this.data[nextIndex].description !== 'string') { this.queryFile(this.data[nextIndex].title); }

// Construct new hidden span for the image var newSlide = this.$imageContainer.append(				$(' ', { 'class': 'image-wrapper current' }).css(isRtl ? 'right' : 'left', hPos).css('top', top).append( $('', {					'class': 'advance-link',					href: imageData.link,					title: imageData.title,					target: '_blank',					css: {						width: imgWidth					}				}))).find('span.current').css('opacity', '0');

newSlide.find('a').append(imageData.image);

var descript = imageData.description || $(' ').append($.createSpinner, mw.html.escape(this.descriptionLoadText));

var newCaption = 0; var extraParams = '&gsDir=' + this.cmdir + '&gsAutoStart=1' + (mw.util.getParamValue('withJS') ? ('&withJS=' + mw.util.getParamValue('withJS')) : ) + (mw.util.getParamValue('withCSS') ? ('&withCSS=' + mw.util.getParamValue('withCSS')) : ); if (this.$captionContainer) { // Construct new hidden caption for the image newCaption = this.$captionContainer.append(					$(' ', { 'class': 'image-caption current', style: 'height:' + (this.maxImageHeight - 30) + 'px;' })).find('span.current').css('opacity', '0').append(imageData.caption, $(' ')).append(					$(' ', { 'class': 'gs-img-description', id: 'desc' + imageData.index, append: descript })).append(					$(' ', { 'class': 'gs-img-uploader' }).append(imageData.$user.clone)).append(					imageData.$cats,					imageData.$licenses).append(					$(' ', { 'class': 'gs-img-metrics', html: imageData.oWidth + ' × ' + imageData.oHeight + ' / ' + imageData.oSize })).append(imageData.contKey ? $(' ', { 'class': 'cont-key-container gs-icon', title: this.continueKeyHowTo, append: $('', {							href: mw.util.wikiGetlink(mw.config.get('wgPageName')) + '?gsContinue=' + imageData.contKey + extraParams,							target: '_blank',							text: imageData.contKey						}) }) : '');				}

this.shrinkTowerOfBabel(newCaption.find('.gs-img-description'));

// Hide the loading conatiner if (this.$loadingContainer) { this.$loadingContainer.hide; }

// Transition in the new image if (this.onTransitionIn) { this.onTransitionIn(newSlide, newCaption, isSync); } else { newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); if (newCaption) { newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0); }				}

if (this.isSlideshowRunning) { if (this.slideshowTimeout) { clearTimeout(this.slideshowTimeout); }

this.slideshowTimeout = setTimeout(function {						gallery.next(true);					}, this.delay); }

// Save the current position in a cookie or delete the cookie if (imageData.contKey) { this.saveContinueKey(imageData.contKey); } else { this.saveContinueKey(null); }

$(document).triggerHandler('slideshow', ['newSlide', this]); // For external scripts

return this; },

saveContinueKey: function(key) { if (this.remoteUse) { return; }				$.cookie('gs' + mw.config.get('wgPageName').replace('Category:', '1:').replace('Commons:', '2:'),				key, {					expires: this.lastPositionExpiry				}); },

getContinueKey: function { return $.cookie('gs' + mw.config.get('wgPageName').replace('Category:', '1:').replace('Commons:', '2:')); },

// Applies the selected class to the current image's corresponding thumbnail. // Also checks if the current page has changed and updates the displayed page of thumbnails if necessary. syncThumbs: function { // Remove existing selected class and add selected class to new thumb var $thumbs = this.$thumbsUl.children;

$thumbs.filter('.selected').removeClass('selected').fadeTo('fast', 0.67); $thumbs.eq(this.currentImage.index).addClass('selected').fadeTo('fast', 1);

return this; },

findImageSize: function(h, w) { var that = this, sOld = this.defaultSizes[0]; $.each(this.defaultSizes, function(i, s) {					if (s.w > w || s.h > h) {						sOld = s;						return;					} else {						that.actualMaxSize = sOld;						return false;					}				}); },

init: function { var $navCont, $playBtn, $prevBtn, $nextBtn;

this.createContainer; var gallery = this; $(window).resize(function {					gallery.maxImageHeight = $(window).height - gallery.$thumbsUl.height - gallery.$controlsContainer.height;					gallery.maxImageWidth = gallery.$slideshowContainer.width - gallery.$captionContainer.width - 16;					gallery.findImageSize(gallery.maxImageHeight, gallery.maxImageWidth);					gallery.css('height', $(window).height);				}).resize;

// Initialize the thumbails this.initializeThumbs;

this.currentImage = this.data[0];

// Hide the loadingContainer this.$loadingContainer.hide; this.gotoIndex(0);

// Setup controls if (this.autoPlay) { $playBtn = $('').attr({						'class': 'gs-play gs-play-pause',						title: this.pauseLinkText,						href: '#pause'					}); } else { $playBtn = $('').attr({						'class': 'gs-play gs-play-play',						title: this.playLinkText,						href: '#play'					}); }

$playBtn.click(function(e) {					gallery.toggleSlideshow;					e.preventDefault;				});

$prevBtn = $('').attr({					'class': 'gs-play gs-play-bwd',					title: this.prevLinkText,					href: '#previous'				}).click(function(e) {					gallery.previous;					e.preventDefault;				}); $nextBtn = $('').attr({					'class': 'gs-play gs-play-fwd',					title: this.nextLinkText,					href: '#next'				}).click(function(e) {					gallery.next;					e.preventDefault;				});

$navCont = $(' '); $navCont.hover(function {					$(this).fadeTo('fast', 0.75);				}, function {					$(this).fadeTo('fast', 0.4);				}); this.$controlsContainer.append(				$navCont.append($prevBtn, $playBtn, $nextBtn));

// Option icons if (!this.remoteUse) { this.$continueKey = $('', {						'class': 'continue-key-insert gs-icon gs-icon-keyGo',						href: '#',						title: gallery.continueKeyInsertBtn,						click: function(e) {							e.preventDefault;							var ckey = prompt(gallery.continueKeyInsert, gallery.cont ? gallery.cont : '');							ckey = $.trim(ckey);							if (gallery.continueKeyPattern.test(ckey)) {								gallery.cont = ckey;							} else {								alert(gallery.continueKeyInvalid);							}						}					}); }				var $setDelay = $('', {					'class': 'delay-insert gs-icon gs-icon-clock',					href: '#',					title: gallery.delayInsertBtn,					click: function(e) {						e.preventDefault;						var delay = prompt(gallery.delayInsert, gallery.delay ? gallery.delay : );						if (!delay) {							return;						}						delay = $.trim(delay.replace(/ms|s/, ));						if (/^\d+$/.test(delay)) {							if (delay > 1000) {								gallery.delay = delay;							} else if ((0.9 < delay) && (delay < 61)) {								gallery.delay = delay * 1000;							} else {								alert(gallery.delayInvalid);							}							// Set cookie							$.cookie('slideshow-delay', gallery.delay, { expires: 100, path: '/' });						} else {							alert(gallery.delayInvalid);						}

}				});				var $helpLink = $('', { 'class': 'gs-help-link gs-icon gs-icon-help', href: mw.util.wikiGetlink('Help:Slideshow'), title: gallery.helpLinkTitle, target: '_blank' });				var otherCont = $(' ', { 'class': 'other-controls' });				this.$controlsContainer.append( otherCont.append(				(this.$continueKey || ' '),				$setDelay,				$helpLink));				otherCont.hover(function { $(this).fadeTo('fast', 1); }, function { $(this).fadeTo('fast', 0.6); });

// Setup Keyboard Navigation if (this.enableKeyboardNavigation) { $(document).keydown(function(e) {						var key = e.charCode || e.keyCode || 0;						switch (key) {						case 32:							// space							gallery.next;							e.preventDefault;							break;						case 35:							// End							gallery.gotoIndex(gallery.data.length - 1);							e.preventDefault;							break;						case 37:							// left arrow							gallery.previous;							e.preventDefault;							break;						case 39:							// right arrow							gallery.next;							e.preventDefault;							break;						case 19:							// break							gallery.toggleSlideshow;							e.preventDefault;							break;						}					}); $(document).keyup(function(e) {						var key = e.charCode || e.keyCode || 0;						//Hide on escape						if ($('#SlideContainer').height && key === 27) {							gallery.pause;							gallery.toggleVisibility;						}					}); }				// Auto start the slideshow if (this.autoPlay) { this.play; }

// Kickoff Image Preloader after 1 second setTimeout(function {					gallery.preloadInit;				}, 1000);

$(document).triggerHandler('slideshow', ['shown', this]); // For external scripts },

start: function { $(document).triggerHandler('slideshow', ['starting', this]); // For external scripts

$('#GallerySlideStartButtons').find('button').hide; $('#SlideContainer').animate({					height: $(window).height				}); // Once done, hide scrollbar

// disabled for IE 6/7 if ('\v' !== 'v') { $('body').css('overflow', 'hidden'); }

// Settings from URL var autoPlay = mw.util.getParamValue('gsAutoPlay'); if (autoPlay) { if ('1' === autoPlay || 'true' === autoPlay || 'yes' === autoPlay || '-1' === autoPlay) { this.autoPlay = true; } else { this.autoPlay = false; }				}				var delay = mw.util.getParamValue('gsDelay') || $.cookie('slideshow-delay'); if (delay) { if (/^\d+$/.test(delay)) { if (delay > 1999) { this.delay = delay; } else if ((1 < delay) && (delay < 61)) { this.delay = delay * 1000; }					}				}				var cmdir = mw.util.getParamValue('gsDir'); if (cmdir) { if ('climbing' === cmdir || 'ascending' === cmdir || 'asc' === cmdir || '123' === cmdir || 'rising' === cmdir) { this.cmdir = 'asc'; } else { this.cmdir = 'desc'; }				}				var cmcontinue = mw.util.getParamValue('gsContinue'); if (cmcontinue) { if (this.continueKeyPattern.test(cmcontinue)) { this.cont = cmcontinue; } else { this.cont = ''; }				}				var readFromScreen = mw.util.getParamValue('gsReadFromScreen'); if (readFromScreen) { this.readFromScreen = true; this.remoteUse = true; }

this.findImageSize(this.maxImageHeight, this.maxImageWidth);

this.queryApi; // For IE 6 if ('\v' === 'v') { setTimeout(function {						window.location.hash = '#SlideContainer';					}, 2000); }

// Display dynamic help from Help:Gadget-GallerySlideshow/OSDHelp var _this = this; var showHelpSplash = function(result) { if (!result) { return; }						result = $(result); result.find('.editsection').remove; var $slideC = $('#SlideContainer'); var helpSplash = $(' ').append(result); setTimeout(function {							helpSplash.fadeOut;						}, 15000); helpSplash.css('left', ($slideC.width - helpSplash.width) / 2); helpSplash.css('top', ($slideC.height - helpSplash.height) / 2); $slideC.prepend(helpSplash.hide.fadeTo(400, 0.7));

if (_this.readFromScreen && ($.client.profile.name === 'opera' || window.opera)) { helpSplash.find('#gsOperaScreenread').show; }						helpSplash.click(function {							$(this).fadeOut;						}); };				$.get(this.indexURL, {					title: 'Help:Gadget-GallerySlideshow/OSDHelp',					action: 'render'				}, showHelpSplash); },			toggleVisibility: function { $('#GallerySlidestart').toggle.unbind('click').click(GallerySlide.toggleVisibility); $('#GallerySlideStartButtons').buttonset; $('#SlideContainer').slideToggle; $('body').css('overflow', 'visible'); // For external scripts $(document).triggerHandler('slideshow', ['visibility', GallerySlide]); },

queryFile: function(title) { var _this = this;

var params = { action: 'render', title: title };

$.ajax({					url: this.indexURL,					cache: true,					dataType: 'html',					data: params,					type: 'GET',					success: function(result) {						_this.processDetails(result, title);					}				}); },

processDetails: function(result, title) { if (typeof result !== 'string') { return; }

var i,				dItem, dDescription, $node, parsedDOM = $(result), $author;

parsedDOM.find('table, div').attr('style', ''); parsedDOM.find('table').attr('cellspacing', 1).attr('cellpadding', 0);

// Clean up author field. Some users are very important and therefore designed logos etc. for themselves // But they are not really important and possibly distract the slideviewer $author = $('#fileinfotpl_aut', parsedDOM).siblings.eq(0).contents.clone; $author.find('img').remove; $author.find('*').removeAttr('style'); $author.find('font').contents.unwrap; $author.find('b').contents.unwrap;

dDescription = $(' ').append(				$(' ').addClass('gs-img-description-desc').append( $('#fileinfotpl_desc', parsedDOM).siblings.eq(0).contents),				$(' ').addClass('gs-img-description-aut').append($author).prepend( $(' ', {					'class': 'gs-author-label'				}).text($(parsedDOM).find('#fileinfotpl_aut').text)),				$(' ').addClass('gs-img-description-date').append( $(parsedDOM).find('#fileinfotpl_date').siblings.eq(0).contents.clone).prepend( $(' ', {					'class': 'gs-date-label'				}).text($('#fileinfotpl_date', parsedDOM).text))).html; if (!dDescription) { dDescription = result; }

for (i in this.data) { dItem = this.data[i]; if (dItem.title === title) { dItem.description = dDescription; $node = $('#desc' + i); if ($node.length !== 0) { $node.html(dDescription); this.shrinkTowerOfBabel($node); }					}				}			},

queryApi: function { var _this = this; var params = {};

if (_this.queryRunning || (_this.cont === false && !_this.readFromScreen)) { return; }

$(document).triggerHandler('slideshow', ['beforeQuery', this]); // For external scripts

var limit = Math.floor($('#SlideContainer').width / 81) + 1;

if (_this.readFromScreen) { _this.qFiles = []; if (!_this.$galleryBoxes) { _this.$galleryBoxes = $('.gallerybox'); _this.queryImageId = -1; }					if (_this.queryImageId === (_this.$galleryBoxes.length - 1)) { return; // All images loaded }					_this.$galleryBoxes.each(function(i, e) {						if (_this.queryImageId >= i) {							return;						}						if (_this.qFiles.length === limit) {							return;						}						_this.queryImageId = i;

_this.qFiles.push('File:' + mw.libs.commons.titleFromImgSrc($(e).find('.thumb').find('img').attr('src'))); });				}

if (_this.readFromScreen) { params = { action: 'query', titles: _this.qFiles.join('|'), prop: 'imageinfo|categories', clprop: 'hidden', cllimit: 500, iiprop: 'url|user|size', iilimit: 500, iiurlwidth: this.actualMaxSize.w,						iiurlheight: this.actualMaxSize.h,						format: 'json' };				} else if (isCategory) { params = { action: 'query', generator: 'categorymembers', gcmtitle: mw.config.get('wgPageName'), gcmlimit: limit, gcmtype: 'file', gcmdir: this.cmdir, prop: 'imageinfo|categories', clprop: 'sortkey|hidden', cllimit: 500, iiprop: 'url|user|size', iilimit: 500, iiurlwidth: this.actualMaxSize.w,						iiurlheight: this.actualMaxSize.h,						format: 'json' };					if (this.cont) { params.gcmcontinue = this.cont; }				} else { params = { action: 'query', generator: 'images', titles: mw.config.get('wgPageName'), gimlimit: limit, prop: 'imageinfo|categories', clprop: 'hidden', cllimit: 500, iiprop: 'url|user|size', iilimit: 500, iiurlwidth: this.actualMaxSize.w,						iiurlheight: this.actualMaxSize.h,						format: 'json' };					if (this.cont) { params.gimcontinue = this.cont; }				}				if (!this.initial && !this.cont && !this.readFromScreen) { return; }

_this.queryRunning = true;

$.ajax({					url: this.apiURL,					cache: false,					dataType: 'json',					data: params,					type: 'POST',					success: function(result) {						_this.queryRunning = false;						_this.processReturn(result);					},					error: function {						_this.queryRunning = false;					}				}); },

processReturn: function(result) { $(document).triggerHandler('slideshow', ['afterQuery', this]); // For external scripts

var pages = result.query.pages, data = [], i = 0;

if (result['query-continue']) { if (typeof this.cont !== 'undefined') { this.contOld = this.cont; }					this.cont = isCategory ? result['query-continue'].categorymembers.gcmcontinue : result['query-continue'].images.gimcontinue; } else { this.cont = false; }

// Fromatt a number var fm = function(iNr) { iNr += ''; var rx = /(\d+)(\d{3})/; while (rx.test(iNr)) { iNr = iNr.replace(rx, '$1' + ' ' + '$2'); }						return iNr; };

if (this.readFromScreen) { // sorting the mess, the API has created to fit the page-diplay var pages2 = {}, qFilesL = this.qFiles.length, qFilesTitle, qfi, p, pg;

sreenreadorderloop: for (qfi = 0; qfi !== qFilesL; qfi++) { qFilesTitle = this.qFiles[qfi]; for (p in pages) { pg = pages[p]; if (pg.title === qFilesTitle) { pages2[p] = pg; continue sreenreadorderloop; }						}					}					pages = pages2; }

for (var id in pages) { var v = pages[id]; if ('undefined' !== typeof v.missing || !v.imageinfo) { continue; }					var r = v.imageinfo[v.imageinfo.length - 1], rc = v.imageinfo[0], n = data[i] = {}, sortkey = '', $cats = $(' ', {							'class': 'cat-wrap'						}), $licenses = $(' ', {							'class': 'license-wrap'						}).append($(' ', { style: 'display:inline-block;' }).text(this.licenseLabel));

// Process categories; Extract visible cats, sortkey for current cat, licenses if (v.categories) { var c, clen = v.categories.length; processCats: for (c = 0; c < clen; c++) { var tCat = v.categories[c]; if (isCategory && tCat.title === mw.config.get('wgPageName').replace(/_/g, ' ')) { sortkey = 'file' + '|' + tCat.sortkey + '|' + id; }							if (typeof tCat.hidden === 'undefined') { $cats.append(								$('', { 'class': 'cat-label', href: mw.util.wikiGetlink(tCat.title), target: '_blank', text: tCat.title.replace('Category:', '') }), ' ');							} else { var recogID, recogLen = this.licenseRecognization.length; for (recogID = 0; recogID < recogLen; recogID++) { if (this.licenseRecognization[recogID][0].test(tCat.title)) { $licenses.append(										$(' ', { 'class': 'license-label', title: tCat.title.replace('Category:', ''), html: this.licenseRecognization[recogID][1] }), ' ');										continue processCats; }								}							}						}					}

if (!isCategory) { sortkey = mw.config.get('wgArticleId') + '|' + v.title.replace('File:', ''); }

n.title = v.title; n.link = rc.descriptionurl; n.slideUrl = rc.thumburl; n.width = rc.thumbwidth; n.height = rc.thumbheight; n.oWidth = fm(r.width); n.oHeight = fm(r.height); n.oSize = fm(r.size >> 10) + ' KiB '; n.$user = $(' ').append(					$(' ', { 'class': 'gs-uploader-label' }).text(this.uploaderLabel)).append(					$(' ').css({ direction: 'ltr', display: 'inline-block' }).append( $('', {						href: mw.util.wikiGetlink(mw.config.get('wgFormattedNamespaces')[2] + ':' + r.user),						target: '_blank',						text: r.user					}), ' (',					$('', { href: mw.util.wikiGetlink(mw.config.get('wgFormattedNamespaces')[3] + ':' + r.user), target: '_blank', text: 'talk' }), ')'));					n.$cats = $cats; n.$licenses = $licenses; n.contKey = (sortkey || this.contOld); // reset to empty string when using screen-read-mode (too instable to rely on it) if (this.readFromScreen) { n.contKey = ''; }					i++; }

this.passedData = data;

if (this.initial) { this.init; } else { this.initializeThumbs; }				this.initial = false; }		});

// Now initialize the gallery $.extend(this, defaults, i18n, settings);

return this; };

$(document).ready(function {		if ($('.gallery li').length < 2) {			// no need for a gallery with a few images			return;		}		mw.loader.using(['jquery.cookie', 'mediawiki.util', 'jquery.ui.dialog', 'jquery.spinner'], function { $('body').append(' '); window.GallerySlide = $('#SlideContainer').galleriffic;

$(document).triggerHandler('slideshow', ['codeLoaded', window.GallerySlide]); // For external scripts });	});

})(jQuery);