MediaWiki:GallerySlideshow.js

From Wikimedia Foundation Governance Wiki
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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.
/*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 = $('<div class="slideshow-container"></div>');
				this.$imageContainer = $('<div id="slideshow" class="slideshow"></div>');
				this.$captionContainer = $('<div id="caption" class="caption-container"></div>');
				this.$loadingContainer = $('<div id="loading" class="loader"></div>');
				this.$controlsContainer = $('<div id="controls" class="controls"></div>');
				// Gray lines for navigation
				this.$ctrBack = $('<div>', {
					'class': 'bar-bwd'
				}).append($('<div>', {
					'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 = $('<div>', {
					'class': 'bar-fwd'
				}).append($('<div>', {
					'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 = $('<div>', {
					'class': 'slideshow-close-button',
					'title': this.hideText
				}).text('×').click(function(e) {
					gallery.pause();
					gallery.toggleVisibility();
					// stop propagation & prevent default
					return false;
				});

				this.append('<div id="thumbs" class="navigation"><ul class="thumbs"></ul></div>');
				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 = $('<li><a class="thumb"><img ' + size + '=75px src="' + (imageData.slideThumb || imageData.slideUrl) + '" /></a></li>');
					$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 = $('<div>').append(
					$('<a>', {
						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(
				$('<span>', {
					'class': 'image-wrapper current'
				}).css(isRtl ? 'right' : 'left', hPos).css('top', top).append(
				$('<a>', {
					'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 || $('<span>').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(
					$('<span>', {
						'class': 'image-caption current',
						style: 'height:' + (this.maxImageHeight - 30) + 'px;'
					})).find('span.current').css('opacity', '0').append(imageData.caption, $('<br>')).append(
					$('<div>', {
						'class': 'gs-img-description',
						id: 'desc' + imageData.index,
						append: descript
					})).append(
					$('<div>', {
						'class': 'gs-img-uploader'
					}).append(imageData.$user.clone())).append(
					imageData.$cats,
					imageData.$licenses).append(
					$('<div>', {
						'class': 'gs-img-metrics',
						html: imageData.oWidth + ' × ' + imageData.oHeight + ' / ' + imageData.oSize
					})).append(imageData.contKey ? $('<div>', {
						'class': 'cont-key-container gs-icon',
						title: this.continueKeyHowTo,
						append: $('<a>', {
							href: mw.util.getUrl(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 = $('<a>').attr({
						'class': 'gs-play gs-play-pause',
						title: this.pauseLinkText,
						href: '#pause'
					});
				} else {
					$playBtn = $('<a>').attr({
						'class': 'gs-play gs-play-play',
						title: this.playLinkText,
						href: '#play'
					});
				}

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

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

				$navCont = $('<div class="nav-controls">');
				$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 = $('<a>', {
						'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 = $('<a>', {
					'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 = $('<a>', {
					'class': 'gs-help-link gs-icon gs-icon-help',
					href: mw.util.getUrl('Help:Slideshow'),
					title: gallery.helpLinkTitle,
					target: '_blank'
				});
				var otherCont = $('<div>', {
					'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 = $('<div id="GallerySlideHelpSplash"></div>').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 = $('<div>').append(
				$('<div>').addClass('gs-img-description-desc').append(
				$('#fileinfotpl_desc', parsedDOM).siblings().eq(0).contents()),
				$('<div>').addClass('gs-img-description-aut').append($author).prepend(
				$('<span>', {
					'class': 'gs-author-label'
				}).text($(parsedDOM).find('#fileinfotpl_aut').text())),
				$('<div>').addClass('gs-img-description-date').append(
				$(parsedDOM).find('#fileinfotpl_date').siblings().eq(0).contents().clone()).prepend(
				$('<span>', {
					'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',
						rawcontinue: '',
						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',
						rawcontinue: '',
						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',
						rawcontinue: '',
						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' + '<span class="digit-separator">&nbsp;</span>' + '$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 = $('<div>', {
							'class': 'cat-wrap'
						}),
						$licenses = $('<div>', {
							'class': 'license-wrap'
						}).append($('<span>', {
							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(
								$('<a>', {
									'class': 'cat-label',
									href: mw.util.getUrl(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(
										$('<span>', {
											'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) + '&nbsp;<abbr title="1 KibiByte= 1024 Bytes">KiB</abbr>';
					n.$user = $('<span>').append(
					$('<span>', {
						'class': 'gs-uploader-label'
					}).text(this.uploaderLabel)).append(
					$('<span>').css({
						direction: 'ltr',
						display: 'inline-block'
					}).append(
					$('<a>', {
						href: mw.util.getUrl(mw.config.get('wgFormattedNamespaces')[2] + ':' + r.user),
						target: '_blank',
						text: r.user
					}), ' (',
					$('<a>', {
						href: mw.util.getUrl(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(['mediawiki.cookie', 'mediawiki.util', 'jquery.ui.dialog', 'jquery.spinner'], function() {
			$('body').append('<div id="SlideContainer"></div>');
			window.GallerySlide = $('#SlideContainer').galleriffic();

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


})(jQuery);