User:Mr. Stradivarius/Sandbox/GallerySideshow.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:Mr. Stradivarius/Sandbox/GallerySideshow. |
/*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;
}
mw.cookie.get('gs' + mw.config.get('wgPageName').replace('Category:', '1:').replace('Commons:', '2:'),
key, {
expires: this.lastPositionExpiry
});
},
getContinueKey: function() {
return mw.cookie.get('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
mw.cookie.set('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') || mw.cookie.get('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"> </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) + ' <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', 'jquery.spinner'], function() {
$('body').append('<div id="SlideContainer"></div>');
window.GallerySlide = $('#SlideContainer').galleriffic();
$(document).triggerHandler('slideshow', ['codeLoaded', window.GallerySlide]); // For external scripts
});
});
})(jQuery);