You are not currently logged in. Are you accessing the unsecure (http) portal? Click here to switch to the secure portal. |
MediaWiki:GallerySlideshow.js
Jump to navigation
Jump to search
Note: After saving, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Clear the cache in Tools → Preferences
/**
* 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]]
*/
/*global mw, jQuery, GallerySlide, alert, prompt */
/*jshint forin: false, bitwise: false, laxbreak: true, browser: true, onevar: false, white: true, nomen: false */
(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 = {
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 ...'
},
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.wikiGetlink(mw.config.get('wgPageName')) + '?gsContinue=' + imageData.contKey + extraParams,
target: '_blank',
text: imageData.contKey
})
}) : ''
);
}
this.shrinkTowerOfBabel(newCaption.find('.gs-img-description'));
// Hide the loading conatiner
if (this.$loadingContainer) {
this.$loadingContainer.hide();
}
// Transition in the new image
if (this.onTransitionIn) {
this.onTransitionIn(newSlide, newCaption, isSync);
} else {
newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
if (newCaption) {
newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
}
}
if (this.isSlideshowRunning) {
if (this.slideshowTimeout) {
clearTimeout(this.slideshowTimeout);
}
this.slideshowTimeout = setTimeout(function () {
gallery.next(true);
}, this.delay);
}
// Save the current position in a cookie or delete the cookie
if (imageData.contKey) {
this.saveContinueKey(imageData.contKey);
} else {
this.saveContinueKey(null);
}
$(document).triggerHandler('slideshow', ['newSlide', this]); // For external scripts
return this;
},
saveContinueKey: function (key) {
if (this.remoteUse) {
return;
}
$.cookie(
'gs' + mw.config.get('wgPageName').replace('Category:', '1:').replace('Commons:', '2:'),
key,
{
expires: this.lastPositionExpiry
}
);
},
getContinueKey: function () {
return $.cookie('gs' + mw.config.get('wgPageName').replace('Category:', '1:').replace('Commons:', '2:'));
},
// Applies the selected class to the current image's corresponding thumbnail.
// Also checks if the current page has changed and updates the displayed page of thumbnails if necessary.
syncThumbs: function () {
// Remove existing selected class and add selected class to new thumb
var $thumbs = this.$thumbsUl.children();
$thumbs.filter('.selected').removeClass('selected').fadeTo('fast', 0.67);
$thumbs.eq(this.currentImage.index).addClass('selected').fadeTo('fast', 1);
return this;
},
findImageSize: function (h, w) {
var that = this,
sOld = this.defaultSizes[0];
$.each(this.defaultSizes, function (i, s) {
if (s.w > w || s.h > h) {
sOld = s;
return;
} else {
that.actualMaxSize = sOld;
return false;
}
});
},
init: function () {
var $navCont, $playBtn, $prevBtn, $nextBtn;
this.createContainer();
var gallery = this;
$(window).resize(function () {
gallery.maxImageHeight = $(window).height() - gallery.$thumbsUl.height() - gallery.$controlsContainer.height();
gallery.maxImageWidth = gallery.$slideshowContainer.width() - gallery.$captionContainer.width() - 16;
gallery.findImageSize(gallery.maxImageHeight, gallery.maxImageWidth);
gallery.css('height', $(window).height());
}).resize();
// Initialize the thumbails
this.initializeThumbs();
this.currentImage = this.data[0];
// Hide the loadingContainer
this.$loadingContainer.hide();
this.gotoIndex(0);
// Setup controls
if (this.autoPlay) {
$playBtn = $('<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.wikiGetlink('Help:Slideshow'),
title: gallery.helpLinkTitle,
target: '_blank'
});
var otherCont = $('<div>', { 'class' : 'other-controls' });
this.$controlsContainer.append(
otherCont.append(
(this.$continueKey || ' '),
$setDelay,
$helpLink
)
);
otherCont.hover(function () {
$(this).fadeTo('fast', 1);
}, function () {
$(this).fadeTo('fast', 0.6);
});
// Setup Keyboard Navigation
if (this.enableKeyboardNavigation) {
$(document).keydown(function (e) {
var key = e.charCode || e.keyCode || 0;
switch (key) {
case 32:
// space
gallery.next();
e.preventDefault();
break;
case 35:
// End
gallery.gotoIndex(gallery.data.length - 1);
e.preventDefault();
break;
case 37:
// left arrow
gallery.previous();
e.preventDefault();
break;
case 39:
// right arrow
gallery.next();
e.preventDefault();
break;
case 19:
// break
gallery.toggleSlideshow();
e.preventDefault();
break;
}
});
$(document).keyup(function (e) {
var key = e.charCode || e.keyCode || 0;
//Hide on escape
if ($('#SlideContainer').height() && key === 27) {
gallery.pause();
gallery.toggleVisibility();
}
});
}
// Auto start the slideshow
if (this.autoPlay) {
this.play();
}
// Kickoff Image Preloader after 1 second
setTimeout(function () {
gallery.preloadInit();
}, 1000);
$(document).triggerHandler('slideshow', ['shown', this]); // For external scripts
},
start: function () {
$(document).triggerHandler('slideshow', ['starting', this]); // For external scripts
$('#GallerySlideStartButtons').find('button').hide();
$('#SlideContainer').animate({
height: $(window).height()
});
// Once done, hide scrollbar
// disabled for IE 6/7
if ('\v' !== 'v') {
$('body').css('overflow', 'hidden');
}
// Settings from URL
var autoPlay = mw.util.getParamValue('gsAutoPlay');
if (autoPlay) {
if ('1' === autoPlay || 'true' === autoPlay || 'yes' === autoPlay || '-1' === autoPlay) {
this.autoPlay = true;
} else {
this.autoPlay = false;
}
}
var delay = mw.util.getParamValue('gsDelay') || $.cookie('slideshow-delay');
if (delay) {
if (/^\d+$/.test(delay)) {
if (delay > 1999) {
this.delay = delay;
} else if ((1 < delay) && (delay < 61)) {
this.delay = delay * 1000;
}
}
}
var cmdir = mw.util.getParamValue('gsDir');
if (cmdir) {
if ('climbing' === cmdir || 'ascending' === cmdir || 'asc' === cmdir || '123' === cmdir || 'rising' === cmdir) {
this.cmdir = 'asc';
} else {
this.cmdir = 'desc';
}
}
var cmcontinue = mw.util.getParamValue('gsContinue');
if (cmcontinue) {
if (this.continueKeyPattern.test(cmcontinue)) {
this.cont = cmcontinue;
} else {
this.cont = '';
}
}
var readFromScreen = mw.util.getParamValue('gsReadFromScreen');
if (readFromScreen) {
this.readFromScreen = true;
this.remoteUse = true;
}
this.findImageSize(this.maxImageHeight, this.maxImageWidth);
this.queryApi();
// For IE 6
if ('\v' === 'v') {
setTimeout(function () {
window.location.hash = '#SlideContainer';
}, 2000);
}
// Display dynamic help from Help:Gadget-GallerySlideshow/OSDHelp
var _this = this;
var showHelpSplash = function (result) {
if (!result) {
return;
}
result = $(result);
result.find('.editsection').remove();
var $slideC = $('#SlideContainer');
var helpSplash = $('<div id="GallerySlideHelpSplash"></div>').append(result);
setTimeout(function () {
helpSplash.fadeOut();
}, 15000);
helpSplash.css('left', ($slideC.width() - helpSplash.width()) / 2);
helpSplash.css('top', ($slideC.height() - helpSplash.height()) / 2);
$slideC.prepend(helpSplash.hide().fadeTo(400, 0.7));
if (_this.readFromScreen && ($.client.profile().name === 'opera' || window.opera)) {
helpSplash.find('#gsOperaScreenread').show();
}
helpSplash.click(function () {
$(this).fadeOut();
});
};
$.get(this.indexURL, {
title: 'Help:Gadget-GallerySlideshow/OSDHelp',
action: 'render'
}, showHelpSplash);
},
toggleVisibility: function () {
$('#GallerySlidestart')
.toggle()
.unbind('click')
.click(GallerySlide.toggleVisibility);
$('#GallerySlideStartButtons').buttonset();
$('#SlideContainer').slideToggle();
$('body').css('overflow', 'visible');
// For external scripts
$(document).triggerHandler('slideshow', ['visibility', GallerySlide]);
},
queryFile: function (title) {
var _this = this;
var params = {
action: 'render',
title: title
};
$.ajax({
url: this.indexURL,
cache: true,
dataType: 'html',
data: params,
type: 'GET',
success: function (result) {
_this.processDetails(result, title);
}
});
},
processDetails: function (result, title) {
if (typeof result !== 'string') {
return;
}
var i,
dItem,
dDescription,
$node,
parsedDOM = $(result),
$author;
parsedDOM.find('table, div').attr('style', '');
parsedDOM.find('table').attr('cellspacing', 1).attr('cellpadding', 0);
// Clean up author field. Some users are very important and therefore designed logos etc. for themselves
// But they are not really important and possibly distract the slideviewer
$author = $('#fileinfotpl_aut', parsedDOM).siblings().eq(0).contents().clone();
$author.find('img').remove();
$author.find('*').removeAttr('style');
$author.find('font').contents().unwrap();
$author.find('b').contents().unwrap();
dDescription = $('<div>').append(
$('<div>')
.addClass('gs-img-description-desc')
.append(
$('#fileinfotpl_desc', parsedDOM).siblings().eq(0).contents()
),
$('<div>')
.addClass('gs-img-description-aut')
.append($author)
.prepend(
$('<span>', { 'class': 'gs-author-label' })
.text($(parsedDOM).find('#fileinfotpl_aut').text())
),
$('<div>')
.addClass('gs-img-description-date')
.append(
$(parsedDOM).find('#fileinfotpl_date').siblings().eq(0).contents().clone()
)
.prepend(
$('<span>', { 'class': 'gs-date-label' }).text($('#fileinfotpl_date', parsedDOM).text())
)
).html();
if (!dDescription) {
dDescription = result;
}
for (i in this.data) {
dItem = this.data[i];
if (dItem.title === title) {
dItem.description = dDescription;
$node = $('#desc' + i);
if ($node.length !== 0) {
$node.html(dDescription);
this.shrinkTowerOfBabel($node);
}
}
}
},
queryApi: function () {
var _this = this;
var params = {};
if (_this.queryRunning || (_this.cont === false && !_this.readFromScreen)) {
return;
}
$(document).triggerHandler('slideshow', ['beforeQuery', this]); // For external scripts
var limit = Math.floor($('#SlideContainer').width() / 81) + 1;
if (_this.readFromScreen) {
_this.qFiles = [];
if (!_this.$galleryBoxes) {
_this.$galleryBoxes = $('.gallerybox');
_this.queryImageId = -1;
}
if (_this.queryImageId === (_this.$galleryBoxes.length - 1)) {
return; // All images loaded
}
_this.$galleryBoxes.each(function (i, e) {
if (_this.queryImageId >= i) {
return;
}
if (_this.qFiles.length === limit) {
return;
}
_this.queryImageId = i;
_this.qFiles.push('File:' + mw.libs.commons.titleFromImgSrc($(e).find('.thumb').find('img').attr('src')));
});
}
if (_this.readFromScreen) {
params = {
action: 'query',
titles: _this.qFiles.join('|'),
prop: 'imageinfo|categories',
clprop: 'hidden',
cllimit: 500,
iiprop: 'url|user|size',
iilimit: 500,
iiurlwidth: this.actualMaxSize.w,
iiurlheight: this.actualMaxSize.h,
format: 'json'
};
} else if (isCategory) {
params = {
action: 'query',
generator: 'categorymembers',
gcmtitle: mw.config.get('wgPageName'),
gcmlimit: limit,
gcmtype: 'file',
gcmdir: this.cmdir,
prop: 'imageinfo|categories',
clprop: 'sortkey|hidden',
cllimit: 500,
iiprop: 'url|user|size',
iilimit: 500,
iiurlwidth: this.actualMaxSize.w,
iiurlheight: this.actualMaxSize.h,
format: 'json'
};
if (this.cont) {
params.gcmcontinue = this.cont;
}
} else {
params = {
action: 'query',
generator: 'images',
titles: mw.config.get('wgPageName'),
gimlimit: limit,
prop: 'imageinfo|categories',
clprop: 'hidden',
cllimit: 500,
iiprop: 'url|user|size',
iilimit: 500,
iiurlwidth: this.actualMaxSize.w,
iiurlheight: this.actualMaxSize.h,
format: 'json'
};
if (this.cont) {
params.gimcontinue = this.cont;
}
}
if (!this.initial && !this.cont && !this.readFromScreen) {
return;
}
_this.queryRunning = true;
$.ajax({
url: this.apiURL,
cache: false,
dataType: 'json',
data: params,
type: 'POST',
success: function (result) {
_this.queryRunning = false;
_this.processReturn(result);
},
error: function () {
_this.queryRunning = false;
}
});
},
processReturn: function (result) {
$(document).triggerHandler('slideshow', ['afterQuery', this]); // For external scripts
var pages = result.query.pages,
data = [],
i = 0;
if (result['query-continue']) {
if (typeof this.cont !== 'undefined') {
this.contOld = this.cont;
}
this.cont = isCategory
? result['query-continue'].categorymembers.gcmcontinue
: result['query-continue'].images.gimcontinue;
} else {
this.cont = false;
}
// Fromatt a number
var fm = function (iNr) {
iNr += '';
var rx = /(\d+)(\d{3})/;
while (rx.test(iNr)) {
iNr = iNr.replace(rx, '$1' + '<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.wikiGetlink(tCat.title),
target: '_blank',
text: tCat.title.replace('Category:', '')
}),
' '
);
} else {
var recogID, recogLen = this.licenseRecognization.length;
for (recogID = 0; recogID < recogLen; recogID++) {
if (this.licenseRecognization[recogID][0].test(tCat.title)) {
$licenses.append(
$('<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.wikiGetlink(mw.config.get('wgFormattedNamespaces')[2] + ':' + r.user),
target: '_blank',
text: r.user
}),
' (',
$('<a>', {
href: mw.util.wikiGetlink(mw.config.get('wgFormattedNamespaces')[3] + ':' + r.user),
target: '_blank',
text: 'talk'
}),
')'
)
);
n.$cats = $cats;
n.$licenses = $licenses;
n.contKey = (sortkey || this.contOld);
// reset to empty string when using screen-read-mode (too instable to rely on it)
if (this.readFromScreen) {
n.contKey = '';
}
i++;
}
this.passedData = data;
if (this.initial) {
this.init();
} else {
this.initializeThumbs();
}
this.initial = false;
}
});
// Now initialize the gallery
$.extend(this, defaults, i18n, settings);
return this;
};
$(document).ready(function () {
if ($('.gallery li').length < 2) {
// no need for a gallery with a few images
return;
}
mw.loader.using(['jquery.cookie', 'mediawiki.util', 'jquery.ui.dialog', 'jquery.spinner'], function () {
$('body').append('<div id="SlideContainer"></div>');
window.GallerySlide = $('#SlideContainer').galleriffic();
$(document).triggerHandler('slideshow', ['codeLoaded', window.GallerySlide]); // For external scripts
});
});
})(jQuery);