MediaWiki:GallerySlideshow.js

MediaWiki界面页面
可打印版不再被支持且可能有渲染错误。请更新您的浏览器书签并改用浏览器默认的打印功能。

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera: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 = {
		bn: {
			delayInsertBtn: 'মিলিসেকেন্ডে বিলম্ব নির্ধারন করুন',
			delayInsert: 'একটি নতুন চিত্রের জন্য কত মিলিসেকেন্ড অপেক্ষা করতে চান?',
			delayInvalid: 'অকার্যকর ইনপুট। শুধুমাত্র 1500 থেকে বড় সংখ্যা গৃহীত হয়।',
			playLinkText: 'চালান',
			pauseLinkText: 'বিরতি',
			prevLinkText: 'পূর্ববর্তী',
			nextLinkText: 'পরবর্তী',
			hideText: 'স্লাইডশো বন্ধ করুন',
			continueKeyHowTo: 'অবিরত-চাবি - আপনি যদি পরে এই অবস্থান থেকে আরম্ভ করতে চান তাহলে, আপনি এই চাবি সংরক্ষণ বা লিঙ্ক বুকমার্ক করতে পারেন।',
			continueKeyInsert: 'দয়া করে অবিরত-চাবি প্রবেশ করান। প্রভাব দেখতে আপনাকে স্লাইডশোতে এগিয়ে যেতে হবে।',
			continueKeyInsertBtn: 'অবিরত-চাবি প্রবেশ করান',
			continueKeyInvalid: 'অকার্যকর চাবি।',
			licenseLabel: 'উপলভ্য লাইসেন্স: ',
			uploaderLabel: 'আপলোডকারী ',
			helpLinkTitle: 'এই সরঞ্জামের জন্য সাহায্য এবং নথিপত্র',
			descriptionLoadText: 'বিবরণ লোড হচ্ছে ...'
		},
		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ó ...'
		},
		cs: {
			delayInsertBtn: 'Nastavit prodlevu v ms',
			delayInsert: 'Kolik milisekund se má čekat na další obrázek?',
			delayInvalid: 'Chybné zadání. Povolena jsou pouze čísla větší než 1500.',
			playLinkText: 'Spustit',
			pauseLinkText: 'Pozastavit',
			prevLinkText: 'Předchozí',
			nextLinkText: 'Následující',
			hideText: 'Zavřít prezentaci',
			continueKeyHowTo: 'Klíč pro pokračování – můžete si uschovat tento klíč nebo si do oblíbených uložit odkaz, pokud budete později pokračovat od této pozice.',
			continueKeyInsert: 'Vložte prosím klíč pro pokračování. Abyste viděli výsledek, musíte pak v prezentaci přejít na další snímek.',
			continueKeyInsertBtn: 'Vložte klíč pro pokračování',
			continueKeyInvalid: 'Neplatný klíč.',
			licenseLabel: 'Dostupné licence: ',
			uploaderLabel: 'Načetl(a) ',
			helpLinkTitle: 'Nápověda a dokumentace k tomuto nástroji',
			descriptionLoadText: 'Načítá se popis'
		},
		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 ...'
		},
		es: {
			delayInsertBtn: 'Fijar el atraso en milisegundos',
			delayInsert: '¿Cuántos milisegundos queres esperar hasta que aparezca la nueva imagen?',
			delayInvalid: 'Entrada no válida. Solo se aceptan los números mayores que 1500.',
			playLinkText: 'Reproducir',
			pauseLinkText: 'Pausar',
			prevLinkText: 'Anterior',
			nextLinkText: 'Siguiente',
			hideText: 'Cerrar la presentación',
			continueKeyHowTo: 'Clave de continuación: Puedes guardar esta clave o añadir el enlace a los favoritos si quieres empezar la presentación desde este punto en otro momento.',
			continueKeyInsert: 'Agregue la clave para reiniciar. Tienes que seguir la presentación para comprobar los cambios.',
			continueKeyInsertBtn: 'Introducir una clave de continuación',
			continueKeyInvalid: 'Clave no válida.',
			licenseLabel: 'Licencias disponibles: ',
			uploaderLabel: 'Persona que subió la foto',
			helpLinkTitle: 'Ayuda y documentación de esta herramienta',
			descriptionLoadText: 'Cargando la descripción...'
		},
		fa: {
			delayInsertBtn: 'تنظیم تأخیر در میلی\u200cثانیه',
			delayInsert: 'چقدر میلی\u200cثانیه تأخبر برای تصویر جدید؟',
			delayInvalid: 'ورودی نادرست. فقط اعداد بزرگتر از ۱۵۰۰ قبول هستند.',
			playLinkText: 'پخش',
			pauseLinkText: 'توقف',
			prevLinkText: 'قبلی',
			nextLinkText: 'بعدی',
			hideText: 'بستن اسلایدشو',
			continueKeyHowTo: 'کلید ادامه - شما می\u200cتوانید این کلید را ذخیره یا بوک\u200cمارک کنید پیوندش را اگر می\u200cخواهید بعداً از این مکان شروع کنید.',
			continueKeyInsert: 'لطفاً کلید ادامه را فشار دهید. شما باید در اسلایدشو جلو بروید یا اثرش را ببینید.',
			continueKeyInsertBtn: 'قراردادن کلید ادامه',
			continueKeyInvalid: 'کلید نادرست.',
			licenseLabel: 'مجوزهای موجود: ',
			uploaderLabel: 'بارگذار ',
			helpLinkTitle: 'کمک و مستندات این ابزار',
			descriptionLoadText: 'بارگیری توضیحات ...'
		},
		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éversé le fichier sur le serveur ',
			helpLinkTitle: 'Aide et documentation de cet outil',
			descriptionLoadText: 'Chargement de la description ...'
		},
		gl: {
			delayInsertBtn: 'Fixar o atraso en milisegundos',
			delayInsert: 'Cantos milisegundos queres agardar ata que apareza unha nova imaxe?',
			delayInvalid: 'Entrada non válida. Só se aceptan os números maiores que 1500.',
			playLinkText: 'Reproducir',
			pauseLinkText: 'Pausar',
			prevLinkText: 'Anterior',
			nextLinkText: 'Seguinte',
			hideText: 'Pechar a presentación',
			continueKeyHowTo: 'Clave de continuación: Podes gardar esta clave ou engadir a ligazón aos marcadores se queres comezar a presentación desde este punto noutro momento.',
			continueKeyInsert: 'Escribe a clave de continuación. Tes que avanzar na presentación para comprobar os cambios.',
			continueKeyInsertBtn: 'Inserir unha clave de continuación',
			continueKeyInvalid: 'Clave non válida.',
			licenseLabel: 'Licenzas dispoñibles: ',
			uploaderLabel: 'Persoa que cargou a imaxe ',
			helpLinkTitle: 'Axuda e documentación desta ferramenta',
			descriptionLoadText: 'Cargando a descrición...'
		},
		hr: {
			delayInsertBtn: 'Kašnjenje u milisekundama',
			delayInsert: 'Čekanje u milisekundama na novu sliku.',
			delayInvalid: 'Krivi unos. Broj mora biti veći od 1500.',
			playLinkText: 'Pokreni',
			pauseLinkText: 'Stanka',
			prevLinkText: 'Prethodna',
			nextLinkText: 'Sljedeća',
			hideText: 'Zatvori prezentaciju',
			continueKeyHowTo: 'Ključ za nastavak: možete ga spremiti ili zabilježiti poveznicu da biste naknadno nastavili prezentaciju od ove slike.',
			continueKeyInsert: 'Unesite ključ za nastavak. Da biste vidjeli promjenu, morate nastaviti s prezentacijom.',
			continueKeyInsertBtn: 'Unesite ključ za nastavak',
			continueKeyInvalid: 'Nevaljali ključ.',
			licenseLabel: 'Dostupne licencije: ',
			uploaderLabel: 'Postavio ',
			helpLinkTitle: 'Pomoć i dokumentacija ovog alata',
			descriptionLoadText: 'Učitavanje opisa ...'
		},
		mk: {
			delayInsertBtn: 'Задај задршка во милисек.',
			delayInsert: 'Колку милисек. да се чека за нова слика?',
			delayInvalid: 'Погрешен внос. Се допуштаат само броеви поголеми од 1500.',
			playLinkText: 'Пушти',
			pauseLinkText: 'Запри',
			prevLinkText: 'Претходна',
			nextLinkText: 'Следна',
			hideText: 'Затвори',
			continueKeyHowTo: 'Клуч за продолжување — Можете да го зачувате овој клуч или да ја ставите врската во одбележани доколку сакате подоцна да продолжите од ова исто место.',
			continueKeyInsert: 'Внесете го клучот за продолжување. Ќе треба да отидете напред во сликоредот за да го видите ефектот.',
			continueKeyInsertBtn: 'Внесете клуч за продолжување',
			continueKeyInvalid: 'Погрешен клуч.',
			licenseLabel: 'Расположиви лиценци: ',
			uploaderLabel: 'Подигач ',
			helpLinkTitle: 'Помош и документација за алаткава',
			descriptionLoadText: 'Го вчитувам описот ...'
		},
		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 ...'
		},
		pl: {
			delayInsertBtn: 'Ustaw opóźnienie w ms',
			delayInsert: 'Ile milisekund czekać przed pokazaniem następnej grafiki?',
			delayInvalid: 'Nieprawidłowe opóźnienie. Tylko liczby większe niż 1500 są akceptowane.',
			playLinkText: 'Odtwórz',
			pauseLinkText: 'Pauza',
			prevLinkText: 'Wstecz',
			nextLinkText: 'Dalej',
			hideText: 'Zamknij pokaz slajdów',
			continueKeyHowTo: 'Klucz wznowienia - możesz zapisać ten klucz lub dodać go do zakładek, jeśli chcesz później wznowić przeglądanie od obecnej pozycji.',
			continueKeyInsert: 'Proszę wpisać klucz wznowienia. Aby ujrzeć efekt, musisz przejść do następnej grafiki.',
			continueKeyInsertBtn: 'Wpisz klucz wznowienia',
			continueKeyInvalid: 'Nieprawidłowy klucz.',
			licenseLabel: 'Dostępne licencje: ',
			uploaderLabel: 'Przesłane przez ',
			helpLinkTitle: 'Pomoc i dokumentacja dla tego narzędzia',
			descriptionLoadText: 'Pobieranie opisu ...'
		},
		pt: {
			delayInsertBtn: 'Alterar o tempo de atraso',
			delayInsert: 'Atraso em milissegundos entre cada imagem',
			delayInvalid: 'Entrada inválida. Somente números maiores que 1500 são aceitos.',
			playLinkText: 'Reproduzir',
			pauseLinkText: 'Pausa',
			prevLinkText: 'Anterior',
			nextLinkText: 'Seguinte',
			hideText: 'Fechar a apresentação',
			continueKeyHowTo: 'Continuação de Chave - Você pode salvar esta chave e adicionar a ligação para os seus favoritos, se mais tarde quiser começar a partir dessa posição.',
			continueKeyInsert: 'Adicione a chave para reiniciar. Você tem que continuar a apresentação para verificar as alterações.',
			continueKeyInsertBtn: 'Insira a chave',
			continueKeyInvalid: 'Chave inválida.',
			licenseLabel: 'Licenças disponíveis: ',
			uploaderLabel: 'Utilizador(a) ',
			helpLinkTitle: 'Ajuda e documentação desta ferramenta',
			descriptionLoadText: 'A carregar descrição ...'
		},
		'pt-br': {
			delayInsertBtn: 'Alterar o tempo de atraso',
			delayInsert: 'Atraso em milissegundos entre cada imagem',
			delayInvalid: 'Entrada inválida. São aceitos somente números maiores que 1500.',
			playLinkText: 'Reproduzir',
			pauseLinkText: 'Pausa',
			prevLinkText: 'Anterior',
			nextLinkText: 'Seguinte',
			hideText: 'Fechar a apresentação',
			continueKeyHowTo: 'Chave de Continuação - Você pode salvar esta chave e adicionar o link aos seus favoritos, se mais tarde quiser iniciar a partir dessa posição.',
			continueKeyInsert: 'Adicione a chave para reiniciar. Você tem que continuar a apresentação para verificar as alterações.',
			continueKeyInsertBtn: 'Insira a chave',
			continueKeyInvalid: 'Chave inválida.',
			licenseLabel: 'Licenças disponíveis: ',
			uploaderLabel: 'Utilizador(a) ',
			helpLinkTitle: 'Ajuda e documentação desta ferramenta',
			descriptionLoadText: 'Carregando descrição ...'
		},
		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="com-gs-thumbs" class="navigation"><ul class="com-gs-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.com-gs-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="com-gs-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) {
				// Breaks some clients! Needs investigation and migration to jStorage.
				return;
				/*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('right', 0);
						helpSplash.css('bottom', 0);
						$slideC.prepend(helpSplash.hide().fadeTo(400, 0.7));

						helpSplash.click(function() {
							$(this).fadeOut();
						});
					};
				$.get(this.indexURL, {
					title: 'Help:Gadget-GallerySlideshow/OSDHelp',
					action: 'render',
					uselang: mw.config.get('wgUserLanguage')
				}, showHelpSplash);
			},
			toggleVisibility: function() {
				$('#GallerySlidestart').toggle().off('click').click(GallerySlide.toggleVisibility);
				$('#GallerySlideStartButtons').buttonset();
				$('#SlideContainer').slideToggle(function() {
					$(document).triggerHandler('slideshow', ['visibility', GallerySlide]);
				});
				$('body').css('overflow', 'visible');
				// For external scripts
			},

			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

				// New restrictions in image info API
				var limit = Math.min(Math.floor($('#SlideContainer').width() / 81) + 1, 50);

				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 = {
						format: 'json',
						action: 'query',
						titles: _this.qFiles.join('|'),
						rawcontinue: 1,
						prop: 'imageinfo|categories',
						clprop: 'hidden',
						cllimit: 500,
						iiprop: 'url|user|size',
						iilimit: 1,
						iiurlwidth: this.actualMaxSize.w,
						iiurlheight: this.actualMaxSize.h,
						indexpageids: 1
					};
				} else if (isCategory) {
					params = {
						format: 'json',
						action: 'query',
						generator: 'categorymembers',
						rawcontinue: 1,
						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: 1,
						iiurlwidth: this.actualMaxSize.w,
						iiurlheight: this.actualMaxSize.h,
						indexpageids: 1
					};
					if (this.cont) {
						params.gcmcontinue = this.cont;
					}
				} else {
					params = {
						action: 'query',
						generator: 'images',
						rawcontinue: 1,
						titles: mw.config.get('wgPageName'),
						gimlimit: limit,
						prop: 'imageinfo|categories',
						clprop: 'hidden',
						cllimit: 500,
						iiprop: 'url|user|size',
						iilimit: 1,
						iiurlwidth: this.actualMaxSize.w,
						iiurlheight: this.actualMaxSize.h,
						format: 'json',
						indexpageids: 1
					};
					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,
					pages2 = [],
					data = [],
					i = 0, j = 0;

				if (result['query-continue'] && (result['query-continue'].categorymembers || result['query-continue'].images)) {
					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 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.push(pg);
								continue sreenreadorderloop;
							}
						}
					}
					pages = pages2;
				} else {
					// This is for Chrome: https://bugs.chromium.org/p/v8/issues/detail?id=164
					// and Opera and InternetExplorer
					var pgIds = result.query.pageids;
					
					for (j = 0; j < pgIds.length; j++) {
						pages2.push(pages[pgIds[j]]);
					}
					pages = pages2;
				}

				for (j = 0; j < pages.length; j++) {
					var v = pages[j];
					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 + '|' + v.pageid;
							}
							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(['jquery.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
		});
	});

	// When this script is loaded, someone started the slideshow gadget
	// To prepare un-defaulting, set a user preference and count usage
	if (mw.user.isAnon()) return;
	mw.loader.using('ext.gadget.SettingsManager', function() {
		mw.libs.settingsManager.fetchGadgetSetting('slideshow-usage').done(function(o, v) {
			v = Number(v) || 0;
			v++;
			if (v > 9) return;
			mw.libs.settingsManager.switchGadgetPref('slideshow-usage', v);
		});
	});

})(jQuery);