diff --git a/doc/credits.bb b/doc/credits.bb index 4923bdadc..5459c7d7a 100644 --- a/doc/credits.bb +++ b/doc/credits.bb @@ -1,7 +1,6 @@ [b]Credits[/b] Mike Macgirvin -Thomas Willingham Fabio Comuni Simon L'nu marijus diff --git a/doc/debian_install.bb b/doc/debian_install.bb deleted file mode 100644 index e8f4a7bd3..000000000 --- a/doc/debian_install.bb +++ /dev/null @@ -1,32 +0,0 @@ -[b]Installing On Debian[/b] - -While following the instructions for any other installation will work on Debian, for this platform we also provide an install script -which can be [url=http://gitweb.whogotzot.com/debian-install-script]downloaded here[/url] - -[b]THIS SCRIPT IS MEANT TO BE RUN ON A NEW OR JUST REINSTALLED SERVER[/b] - -Some programs such as Apache & Samba are removed by this script. - -Note, this script will use Nginx as the webserver. It will also install PHP and MySQL from the DotDeb repository. The DotDeb is not an official Debian repository, though it is maintained by Debian developers. - -The file setup-debian.sh has to be on your server. - -For the initial setup git may not be installed on your server, to install git: - -[code]apt-get install git[/code] - -If wget is installed try - -[code]wget http://git.beardyunixer.com/debian-install-script/blob/HEAD:/debian-setup.sh[/code] - -To install wget: -[code]apt-get install wget[/code] - -For intitial server setup run -[code]bash setup-debian.sh all[/code] - -To install Red for domain example.com, after the initial server setup run - -[code]bash setup-debian.sh red example.com[/code] - -#include doc/macros/main_footer.bb; diff --git a/doc/main.bb b/doc/main.bb index 89cbd7c9b..d71f819b2 100644 --- a/doc/main.bb +++ b/doc/main.bb @@ -37,7 +37,6 @@ Zot is the great new communicaton protocol invented especially for the $Projectn [h3]Administrators Help[/h3] [zrl=[baseurl]/help/install]Install[/zrl] -[zrl=[baseurl]/help/debian_install]Easy Install on Debian via script[/zrl] [zrl=[baseurl]/help/red2pi]Installing Red on the Raspberry Pi[/zrl] [zrl=[baseurl]/help/troubleshooting]Troubleshooting Tips[/zrl] [zrl=[baseurl]/help/hidden_configs]Tweaking $Projectname's Hidden Configurations[/zrl] diff --git a/doc/problems-following-an-update.bb b/doc/problems-following-an-update.bb index e2ba11e3e..3bc7e9a51 100644 --- a/doc/problems-following-an-update.bb +++ b/doc/problems-following-an-update.bb @@ -20,7 +20,7 @@ If you're using php5-fpm, this problem is usually resolved with [code]service ph Symptoms: -1) [zrl=https://beardyunixer.com/page/jargon/wsod]White Screen Of Death[/zrl]. This is most prevalent on the settings and admin pages. +1) White Screen Of Death. This is most prevalent on the settings and admin pages. 2) Missing icons, tabs, menus or features. diff --git a/include/zot.php b/include/zot.php index dfaff268e..9497e4668 100644 --- a/include/zot.php +++ b/include/zot.php @@ -2887,6 +2887,27 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { foreach($arr['abook'] as $abook) { + if(! array_key_exists('abook_blocked',$abook)) { + // convert from redmatrix + if($abook['abook_flags'] & 0x0001) + $abook['abook_blocked'] = 1; + if($abook['abook_flags'] & 0x0002) + $abook['abook_ignored'] = 1; + if($abook['abook_flags'] & 0x0004) + $abook['abook_hidden'] = 1; + if($abook['abook_flags'] & 0x0008) + $abook['abook_archived'] = 1; + if($abook['abook_flags'] & 0x0010) + $abook['abook_pending'] = 1; + if($abook['abook_flags'] & 0x0020) + $abook['abook_unconnected'] = 1; + if($abook['abook_flags'] & 0x0080) + $abook['abook_self'] = 1; + if($abook['abook_flags'] & 0x0100) + $abook['abook_feed'] = 1; + } + + $clean = array(); if($abook['abook_xchan'] && $abook['entry_deleted']) { logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']); diff --git a/library/justifiedGallery/jquery.justifiedGallery.js b/library/justifiedGallery/jquery.justifiedGallery.js index 213a7f286..7c63149a3 100644 --- a/library/justifiedGallery/jquery.justifiedGallery.js +++ b/library/justifiedGallery/jquery.justifiedGallery.js @@ -1,699 +1,1097 @@ /*! - * Justified Gallery - v3.5.4 + * Justified Gallery - v3.6.0 * http://miromannino.github.io/Justified-Gallery/ * Copyright (c) 2015 Miro Mannino * Licensed under the MIT license. */ (function($) { - /* Events - jg.complete : called when all the gallery has been created - jg.resize : called when the gallery has been resized - */ + /** + * Justified Gallery controller constructor + * + * @param $gallery the gallery to build + * @param settings the settings (the defaults are in $.fn.justifiedGallery.defaults) + * @constructor + */ + var JustifiedGallery = function ($gallery, settings) { - $.fn.justifiedGallery = function (arg) { + this.settings = settings; + this.checkSettings(); - // Default options - var defaults = { - sizeRangeSuffixes : { - 'lt100': '', // e.g. Flickr uses '_t' - 'lt240': '', // e.g. Flickr uses '_m' - 'lt320': '', // e.g. Flickr uses '_n' - 'lt500': '', // e.g. Flickr uses '' - 'lt640': '', // e.g. Flickr uses '_z' - 'lt1024': '', // e.g. Flickr uses '_b' - }, - rowHeight : 120, - maxRowHeight : 0, // negative value = no limits, 0 = 1.5 * rowHeight - margins : 1, - border: -1, // negative value = same as margins, 0 = disabled - - lastRow : 'nojustify', // or can be 'justify' or 'hide' - justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified - (i.e. lastRow setting is not considered) */ - fixedHeight : false, - waitThumbnailsLoad : true, - captions : true, - cssAnimation: false, - imagesAnimationDuration : 500, // ignored with css animations - captionSettings : { // ignored with css animations - animationDuration : 500, - visibleOpacity : 0.7, - nonVisibleOpacity : 0.0 - }, - rel : null, // rewrite the rel of each analyzed links - target : null, // rewrite the target of all links - extension : /\.[^.\\/]+$/, - refreshTime : 100, - randomize : false + this.imgAnalyzerTimeout = null; + this.entries = null; + this.buildingRow = { + entriesBuff : [], + width : 0, + aspectRatio : 0 }; + this.lastAnalyzedIndex = -1; + this.yield = { + every : 2, // do a flush every n flushes (must be greater than 1) + flushed : 0 // flushed rows without a yield + }; + this.border = settings.border >= 0 ? settings.border : settings.margins; + this.maxRowHeight = this.retrieveMaxRowHeight(); + this.suffixRanges = this.retrieveSuffixRanges(); + this.offY = this.border; + this.spinner = { + phase : 0, + timeSlot : 150, + $el : $('
'), + intervalId : null + }; + this.checkWidthIntervalId = null; + this.galleryWidth = $gallery.width(); + this.$gallery = $gallery; - function getSuffix(width, height, context) { - var longestSide; - longestSide = (width > height) ? width : height; - if (longestSide <= 100) { - return context.settings.sizeRangeSuffixes.lt100; - } else if (longestSide <= 240) { - return context.settings.sizeRangeSuffixes.lt240; - } else if (longestSide <= 320) { - return context.settings.sizeRangeSuffixes.lt320; - } else if (longestSide <= 500) { - return context.settings.sizeRangeSuffixes.lt500; - } else if (longestSide <= 640) { - return context.settings.sizeRangeSuffixes.lt640; - } else { - return context.settings.sizeRangeSuffixes.lt1024; + }; + + /** @returns {String} the best suffix given the width and the height */ + JustifiedGallery.prototype.getSuffix = function (width, height) { + var longestSide, i; + longestSide = (width > height) ? width : height; + for (i = 0; i < this.suffixRanges.length; i++) { + if (longestSide <= this.suffixRanges[i]) { + return this.settings.sizeRangeSuffixes[this.suffixRanges[i]]; } } + return this.settings.sizeRangeSuffixes[this.suffixRanges[i - 1]]; + }; - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } + /** + * Remove the suffix from the string + * + * @returns {string} a new string without the suffix + */ + JustifiedGallery.prototype.removeSuffix = function (str, suffix) { + return str.substring(0, str.length - suffix.length); + }; - function removeSuffix(str, suffix) { - return str.substring(0, str.length - suffix.length); - } + /** + * @returns {boolean} a boolean to say if the suffix is contained in the str or not + */ + JustifiedGallery.prototype.endsWith = function (str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + }; - function getUsedSuffix(str, context) { - var voidSuffix = false; - for (var si in context.settings.sizeRangeSuffixes) { - if (context.settings.sizeRangeSuffixes[si].length === 0) { - voidSuffix = true; - continue; - } - if (endsWith(str, context.settings.sizeRangeSuffixes[si])) { - return context.settings.sizeRangeSuffixes[si]; - } - } - - if (voidSuffix) return ""; - else throw 'unknown suffix for ' + str; - } - - /* Given an image src, with the width and the height, returns the new image src with the - best suffix to show the best quality thumbnail. */ - function newSrc(imageSrc, imgWidth, imgHeight, context) { - var matchRes = imageSrc.match(context.settings.extension); - var ext = (matchRes != null) ? matchRes[0] : ''; - var newImageSrc = imageSrc.replace(context.settings.extension, ''); - newImageSrc = removeSuffix(newImageSrc, getUsedSuffix(newImageSrc, context)); - newImageSrc += getSuffix(imgWidth, imgHeight, context) + ext; - return newImageSrc; - } - - function onEntryMouseEnterForCaption (ev) { - var $caption = $(ev.currentTarget).find('.caption'); - if (ev.data.settings.cssAnimation) { - $caption.addClass('caption-visible').removeClass('caption-hidden'); - } else { - $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, - ev.data.settings.captionSettings.visibleOpacity); + /** + * Get the used suffix of a particular url + * + * @param str + * @returns {String} return the used suffix + */ + JustifiedGallery.prototype.getUsedSuffix = function (str) { + for (var si in this.settings.sizeRangeSuffixes) { + if (this.settings.sizeRangeSuffixes.hasOwnProperty(si)) { + if (this.settings.sizeRangeSuffixes[si].length === 0) continue; + if (this.endsWith(str, this.settings.sizeRangeSuffixes[si])) return this.settings.sizeRangeSuffixes[si]; } } + return ''; + }; - function onEntryMouseLeaveForCaption (ev) { - var $caption = $(ev.currentTarget).find('.caption'); - if (ev.data.settings.cssAnimation) { - $caption.removeClass('caption-visible').removeClass('caption-hidden'); - } else { - $caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, - ev.data.settings.captionSettings.nonVisibleOpacity); - } + /** + * Given an image src, with the width and the height, returns the new image src with the + * best suffix to show the best quality thumbnail. + * + * @returns {String} the suffix to use + */ + JustifiedGallery.prototype.newSrc = function (imageSrc, imgWidth, imgHeight) { + var matchRes = imageSrc.match(this.settings.extension); + var ext = (matchRes != null) ? matchRes[0] : ''; + var newImageSrc = imageSrc.replace(this.settings.extension, ''); + newImageSrc = this.removeSuffix(newImageSrc, this.getUsedSuffix(newImageSrc)); + newImageSrc += this.getSuffix(imgWidth, imgHeight) + ext; + return newImageSrc; + }; + + /** + * Shows the images that is in the given entry + * + * @param $entry the entry + * @param callback the callback that is called when the show animation is finished + */ + JustifiedGallery.prototype.showImg = function ($entry, callback) { + if (this.settings.cssAnimation) { + $entry.addClass('entry-visible'); + if (callback) callback(); + } else { + $entry.stop().fadeTo(this.settings.imagesAnimationDuration, 1.0, callback); } + }; - function showImg($entry, callback, context) { - if (context.settings.cssAnimation) { - $entry.addClass('entry-visible'); - callback(); - } else { - $entry.stop().fadeTo(context.settings.imagesAnimationDuration, 1.0, callback); - } - } + /** + * Extract the image src form the image, looking from the 'safe-src', and if it can't be found, from the + * 'src' attribute. It saves in the image data the 'jg.originalSrc' field, with the extracted src. + * + * @param $image the image to analyze + * @returns {String} the extracted src + */ + JustifiedGallery.prototype.extractImgSrcFromImage = function ($image) { + var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ? $image.data('safe-src') : $image.attr('src'); + $image.data('jg.originalSrc', imageSrc); + return imageSrc; + }; - function hideImgImmediately($entry, context) { - if (context.settings.cssAnimation) { - $entry.removeClass('entry-visible'); - } else { - $entry.stop().fadeTo(0, 0); - } - } + /** @returns {jQuery} the image in the given entry */ + JustifiedGallery.prototype.imgFromEntry = function ($entry) { + var $img = $entry.find('> img'); + if ($img.length === 0) $img = $entry.find('> a > img'); + return $img.length === 0 ? null : $img; + }; - function imgFromEntry($entry) { - var $img = $entry.find('> img'); - if ($img.length === 0) $img = $entry.find('> a > img'); - return $img; - } + /** @returns {jQuery} the caption in the given entry */ + JustifiedGallery.prototype.captionFromEntry = function ($entry) { + var $caption = $entry.find('> .caption'); + return $caption.length === 0 ? null : $caption; + }; - function displayEntry($entry, x, y, imgWidth, imgHeight, rowHeight, context) { - var $image = imgFromEntry($entry); + /** + * Display the entry + * + * @param {jQuery} $entry the entry to display + * @param {int} x the x position where the entry must be positioned + * @param y the y position where the entry must be positioned + * @param imgWidth the image width + * @param imgHeight the image height + * @param rowHeight the row height of the row that owns the entry + */ + JustifiedGallery.prototype.displayEntry = function ($entry, x, y, imgWidth, imgHeight, rowHeight) { + $entry.width(imgWidth); + $entry.height(rowHeight); + $entry.css('top', y); + $entry.css('left', x); + + var $image = this.imgFromEntry($entry); + if ($image !== null) { $image.css('width', imgWidth); $image.css('height', imgHeight); - //if ($entry.get(0) === $image.parent().get(0)) { // this creates an error in link_around_img test - $image.css('margin-left', - imgWidth / 2); - $image.css('margin-top', - imgHeight / 2); - //} - $entry.width(imgWidth); - $entry.height(rowHeight); - $entry.css('top', y); - $entry.css('left', x); - - //DEBUG// console.log('displayEntry (w: ' + $image.width() + ' h: ' + $image.height()); + $image.css('margin-left', - imgWidth / 2); + $image.css('margin-top', - imgHeight / 2); // Image reloading for an high quality of thumbnails var imageSrc = $image.attr('src'); - var newImageSrc = newSrc(imageSrc, imgWidth, imgHeight, context); + var newImageSrc = this.newSrc(imageSrc, imgWidth, imgHeight); $image.one('error', function () { - //DEBUG// console.log('revert the original image'); $image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it. }); - function loadNewImage() { + var loadNewImage = function () { if (imageSrc !== newImageSrc) { //load the new image after the fadeIn $image.attr('src', newImageSrc); } - } + }; - if ($image.data('jg.loaded') === 'skipped') { - onImageEvent(imageSrc, function() { - showImg($entry, loadNewImage, context); - $image.data('jg.loaded', true); - }); + if ($entry.data('jg.loaded') === 'skipped') { + this.onImageEvent(imageSrc, $.proxy(function() { + this.showImg($entry, loadNewImage); + $entry.data('jg.loaded', true); + }, this)); } else { - showImg($entry, loadNewImage, context); - } - - // Captions ------------------------------ - var captionMouseEvents = $entry.data('jg.captionMouseEvents'); - if (context.settings.captions === true) { - var $imgCaption = $entry.find('.caption'); - if ($imgCaption.length === 0) { // Create it if it doesn't exists - var caption = $image.attr('alt'); - if (typeof caption === 'undefined') caption = $entry.attr('title'); - if (typeof caption !== 'undefined') { // Create only we found something - $imgCaption = $('
' + caption + '
'); - $entry.append($imgCaption); - } - } - - // Create events (we check again the $imgCaption because it can be still inexistent) - if ($imgCaption.length !== 0) { - if (!context.settings.cssAnimation) { - $imgCaption.stop().fadeTo(context.settings.imagesAnimationDuration, - context.settings.captionSettings.nonVisibleOpacity); - } - if (typeof captionMouseEvents === 'undefined') { - captionMouseEvents = { - mouseenter: onEntryMouseEnterForCaption, - mouseleave: onEntryMouseLeaveForCaption - }; - $entry.on('mouseenter', undefined, context, captionMouseEvents.mouseenter); - $entry.on('mouseleave', undefined, context, captionMouseEvents.mouseleave); - $entry.data('jg.captionMouseEvents', captionMouseEvents); - } - } - } else { - if (typeof captionMouseEvents !== 'undefined') { - $entry.off('mouseenter', undefined, context, captionMouseEvents.mouseenter); - $entry.off('mouseleave', undefined, context, captionMouseEvents.mouseleave); - $entry.removeData('jg.captionMouseEvents'); - } + this.showImg($entry, loadNewImage); } + } else { + this.showImg($entry); } - function prepareBuildingRow(context, isLastRow) { - var settings = context.settings; - var i, $entry, $image, imgAspectRatio, newImgW, newImgH, justify = true; - var minHeight = 0; - var availableWidth = context.galleryWidth - 2 * context.border - ( - (context.buildingRow.entriesBuff.length - 1) * settings.margins); - var rowHeight = availableWidth / context.buildingRow.aspectRatio; - var justificable = context.buildingRow.width / availableWidth > settings.justifyThreshold; + this.displayEntryCaption($entry); + }; - //Skip the last row if we can't justify it and the lastRow == 'hide' - if (isLastRow && settings.lastRow === 'hide' && !justificable) { - for (i = 0; i < context.buildingRow.entriesBuff.length; i++) { - $entry = context.buildingRow.entriesBuff[i]; - if (settings.cssAnimation) - $entry.removeClass('entry-visible'); - else - $entry.stop().fadeTo(0, 0); + /** + * Display the entry caption. If the caption element doesn't exists, it creates the caption using the 'alt' + * or the 'title' attributes. + * + * @param {jQuery} $entry the entry to process + */ + JustifiedGallery.prototype.displayEntryCaption = function ($entry) { + var $image = this.imgFromEntry($entry); + if ($image !== null && this.settings.captions) { + var $imgCaption = this.captionFromEntry($entry); + + // Create it if it doesn't exists + if ($imgCaption == null) { + var caption = $image.attr('alt'); + if (typeof caption === 'undefined') caption = $entry.attr('title'); + if (typeof caption !== 'undefined') { // Create only we found something + $imgCaption = $('
' + caption + '
'); + $entry.append($imgCaption); + $entry.data('jg.createdCaption', true); } - return -1; } - // With lastRow = nojustify, justify if is justificable (the images will not become too big) - if (isLastRow && !justificable && settings.lastRow === 'nojustify') justify = false; + // Create events (we check again the $imgCaption because it can be still inexistent) + if ($imgCaption !== null) { + if (!this.settings.cssAnimation) $imgCaption.stop().fadeTo(0, this.settings.captionSettings.nonVisibleOpacity); + this.addCaptionEventsHandlers($entry); + } + } else { + this.removeCaptionEventsHandlers($entry); + } + }; - for (i = 0; i < context.buildingRow.entriesBuff.length; i++) { - $image = imgFromEntry(context.buildingRow.entriesBuff[i]); - imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh'); + /** + * The callback for the event 'mouseenter'. It assumes that the event currentTarget is an entry. + * It shows the caption using jQuery (or using CSS if it is configured so) + * + * @param {Event} eventObject the event object + */ + JustifiedGallery.prototype.onEntryMouseEnterForCaption = function (eventObject) { + var $caption = this.captionFromEntry($(eventObject.currentTarget)); + if (this.settings.cssAnimation) { + $caption.addClass('caption-visible').removeClass('caption-hidden'); + } else { + $caption.stop().fadeTo(this.settings.captionSettings.animationDuration, + this.settings.captionSettings.visibleOpacity); + } + }; - if (justify) { - newImgW = (i === context.buildingRow.entriesBuff.length - 1) ? availableWidth - : rowHeight * imgAspectRatio; - newImgH = rowHeight; + /** + * The callback for the event 'mouseleave'. It assumes that the event currentTarget is an entry. + * It hides the caption using jQuery (or using CSS if it is configured so) + * + * @param {Event} eventObject the event object + */ + JustifiedGallery.prototype.onEntryMouseLeaveForCaption = function (eventObject) { + var $caption = this.captionFromEntry($(eventObject.currentTarget)); + if (this.settings.cssAnimation) { + $caption.removeClass('caption-visible').removeClass('caption-hidden'); + } else { + $caption.stop().fadeTo(this.settings.captionSettings.animationDuration, + this.settings.captionSettings.nonVisibleOpacity); + } + }; - /* With fixedHeight the newImgH must be greater than rowHeight. - In some cases here this is not satisfied (due to the justification). - But we comment it, because is better to have a shorter but justified row instead - to have a cropped image at the end. */ - /*if (settings.fixedHeight && newImgH < settings.rowHeight) { - newImgW = settings.rowHeight * imgAspectRatio; - newImgH = settings.rowHeight; - }*/ + /** + * Add the handlers of the entry for the caption + * + * @param $entry the entry to modify + */ + JustifiedGallery.prototype.addCaptionEventsHandlers = function ($entry) { + var captionMouseEvents = $entry.data('jg.captionMouseEvents'); + if (typeof captionMouseEvents === 'undefined') { + captionMouseEvents = { + mouseenter: $.proxy(this.onEntryMouseEnterForCaption, this), + mouseleave: $.proxy(this.onEntryMouseLeaveForCaption, this) + }; + $entry.on('mouseenter', undefined, undefined, captionMouseEvents.mouseenter); + $entry.on('mouseleave', undefined, undefined, captionMouseEvents.mouseleave); + $entry.data('jg.captionMouseEvents', captionMouseEvents); + } + }; + /** + * Remove the handlers of the entry for the caption + * + * @param $entry the entry to modify + */ + JustifiedGallery.prototype.removeCaptionEventsHandlers = function ($entry) { + var captionMouseEvents = $entry.data('jg.captionMouseEvents'); + if (typeof captionMouseEvents !== 'undefined') { + $entry.off('mouseenter', undefined, captionMouseEvents.mouseenter); + $entry.off('mouseleave', undefined, captionMouseEvents.mouseleave); + $entry.removeData('jg.captionMouseEvents'); + } + }; + + /** + * Justify the building row, preparing it to + * + * @param isLastRow + * @returns {*} + */ + JustifiedGallery.prototype.prepareBuildingRow = function (isLastRow) { + var i, $entry, imgAspectRatio, newImgW, newImgH, justify = true; + var minHeight = 0; + var availableWidth = this.galleryWidth - 2 * this.border - ( + (this.buildingRow.entriesBuff.length - 1) * this.settings.margins); + var rowHeight = availableWidth / this.buildingRow.aspectRatio; + var justifiable = this.buildingRow.width / availableWidth > this.settings.justifyThreshold; + + //Skip the last row if we can't justify it and the lastRow == 'hide' + if (isLastRow && this.settings.lastRow === 'hide' && !justifiable) { + for (i = 0; i < this.buildingRow.entriesBuff.length; i++) { + $entry = this.buildingRow.entriesBuff[i]; + if (this.settings.cssAnimation) + $entry.removeClass('entry-visible'); + else + $entry.stop().fadeTo(0, 0); + } + return -1; + } + + // With lastRow = nojustify, justify if is justificable (the images will not become too big) + if (isLastRow && !justifiable && this.settings.lastRow === 'nojustify') justify = false; + + for (i = 0; i < this.buildingRow.entriesBuff.length; i++) { + $entry = this.buildingRow.entriesBuff[i]; + imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height'); + + if (justify) { + newImgW = (i === this.buildingRow.entriesBuff.length - 1) ? availableWidth : rowHeight * imgAspectRatio; + newImgH = rowHeight; + + /* With fixedHeight the newImgH must be greater than rowHeight. + In some cases here this is not satisfied (due to the justification). + But we comment it, because is better to have a shorter but justified row instead + to have a cropped image at the end. */ + /*if (this.settings.fixedHeight && newImgH < this.settings.rowHeight) { + newImgW = this.settings.rowHeight * imgAspectRatio; + newImgH = this.settings.rowHeight; + }*/ + + } else { + newImgW = this.settings.rowHeight * imgAspectRatio; + newImgH = this.settings.rowHeight; + } + + availableWidth -= Math.round(newImgW); + $entry.data('jg.jwidth', Math.round(newImgW)); + $entry.data('jg.jheight', Math.ceil(newImgH)); + if (i === 0 || minHeight > newImgH) minHeight = newImgH; + } + + if (this.settings.fixedHeight && minHeight > this.settings.rowHeight) + minHeight = this.settings.rowHeight; + + return {minHeight: minHeight, justify: justify}; + }; + + /** + * Clear the building row data to be used for a new row + */ + JustifiedGallery.prototype.clearBuildingRow = function () { + this.buildingRow.entriesBuff = []; + this.buildingRow.aspectRatio = 0; + this.buildingRow.width = 0; + }; + + /** + * Flush a row: justify it, modify the gallery height accordingly to the row height + * + * @param isLastRow + */ + JustifiedGallery.prototype.flushRow = function (isLastRow) { + var settings = this.settings; + var $entry, minHeight, buildingRowRes, offX = this.border; + + buildingRowRes = this.prepareBuildingRow(isLastRow); + minHeight = buildingRowRes.minHeight; + if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) { + this.clearBuildingRow(); + return; + } + + if (this.maxRowHeight.percentage) { + if (this.maxRowHeight.value * settings.rowHeight < minHeight) minHeight = this.maxRowHeight.value * settings.rowHeight; + } else { + if (this.maxRowHeight.value > 0 && this.maxRowHeight.value < minHeight) minHeight = this.maxRowHeight.value; + } + + for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) { + $entry = this.buildingRow.entriesBuff[i]; + this.displayEntry($entry, offX, this.offY, $entry.data('jg.jwidth'), $entry.data('jg.jheight'), minHeight); + offX += $entry.data('jg.jwidth') + settings.margins; + } + + //Gallery Height + this.$gallery.height(this.offY + minHeight + this.border + (this.isSpinnerActive() ? this.getSpinnerHeight() : 0)); + + if (!isLastRow || (minHeight <= this.settings.rowHeight && buildingRowRes.justify)) { + //Ready for a new row + this.offY += minHeight + this.settings.margins; + this.clearBuildingRow(); + this.$gallery.trigger('jg.rowflush'); + } + }; + + /** + * Checks the width of the gallery container, to know if a new justification is needed + */ + JustifiedGallery.prototype.checkWidth = function () { + this.checkWidthIntervalId = setInterval($.proxy(function () { + var galleryWidth = parseInt(this.$gallery.width(), 10); + if (this.galleryWidth !== galleryWidth) { + this.galleryWidth = galleryWidth; + this.rewind(); + + // Restart to analyze + this.startImgAnalyzer(true); + } + }, this), this.settings.refreshTime); + }; + + /** + * @returns {boolean} a boolean saying if the spinner is active or not + */ + JustifiedGallery.prototype.isSpinnerActive = function () { + return this.spinner.intervalId != null; + }; + + /** + * @returns {int} the spinner height + */ + JustifiedGallery.prototype.getSpinnerHeight = function () { + return this.spinner.$el.innerHeight(); + }; + + /** + * Stops the spinner animation and modify the gallery height to exclude the spinner + */ + JustifiedGallery.prototype.stopLoadingSpinnerAnimation = function () { + clearInterval(this.spinner.intervalId); + this.spinner.intervalId = null; + this.$gallery.height(this.$gallery.height() - this.getSpinnerHeight()); + this.spinner.$el.detach(); + }; + + /** + * Starts the spinner animation + */ + JustifiedGallery.prototype.startLoadingSpinnerAnimation = function () { + var spinnerContext = this.spinner; + var $spinnerPoints = spinnerContext.$el.find('span'); + clearInterval(spinnerContext.intervalId); + this.$gallery.append(spinnerContext.$el); + this.$gallery.height(this.offY + this.getSpinnerHeight()); + spinnerContext.intervalId = setInterval(function () { + if (spinnerContext.phase < $spinnerPoints.length) { + $spinnerPoints.eq(spinnerContext.phase).fadeTo(spinnerContext.timeSlot, 1); + } else { + $spinnerPoints.eq(spinnerContext.phase - $spinnerPoints.length).fadeTo(spinnerContext.timeSlot, 0); + } + spinnerContext.phase = (spinnerContext.phase + 1) % ($spinnerPoints.length * 2); + }, spinnerContext.timeSlot); + }; + + /** + * Rewind the image analysis to start from the first entry. + */ + JustifiedGallery.prototype.rewind = function () { + this.lastAnalyzedIndex = -1; + this.offY = this.border; + this.clearBuildingRow(); + }; + + /** + * Hide the image of the buildingRow to prevent strange effects when the row will be + * re-justified again + */ + JustifiedGallery.prototype.hideBuildingRowImages = function () { + for (var i = 0; i < this.buildingRow.entriesBuff.length; i++) { + if (this.settings.cssAnimation) { + this.buildingRow.entriesBuff[i].removeClass('entry-visible'); + } else { + this.buildingRow.entriesBuff[i].stop().fadeTo(0, 0); + } + } + }; + + /** + * Update the entries searching it from the justified gallery HTML element + * + * @param norewind if norewind only the new entries will be changed (i.e. randomized, sorted or filtered) + * @returns {boolean} true if some entries has been founded + */ + JustifiedGallery.prototype.updateEntries = function (norewind) { + this.entries = this.$gallery.find(this.settings.selector).toArray(); + if (this.entries.length === 0) return false; + + // Filter + if (this.settings.filter) { + this.modifyEntries(this.filterArray, norewind); + } else { + this.modifyEntries(this.resetFilters, norewind); + } + + // Sort or randomize + if ($.isFunction(this.settings.sort)) { + this.modifyEntries(this.sortArray, norewind); + } else if (this.settings.randomize) { + this.modifyEntries(this.shuffleArray, norewind); + } + + return true; + }; + + /** + * Apply the entries order to the DOM, iterating the entries and appending the images + * + * @param entries the entries that has been modified and that must be re-ordered in the DOM + */ + JustifiedGallery.prototype.insertToGallery = function (entries) { + var that = this; + $.each(entries, function () { + $(this).appendTo(that.$gallery); + }); + }; + + /** + * Shuffle the array using the Fisher-Yates shuffle algorithm + * + * @param a the array to shuffle + * @return the shuffled array + */ + JustifiedGallery.prototype.shuffleArray = function (a) { + var i, j, temp; + for (i = a.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } + this.insertToGallery(a); + return a; + }; + + /** + * Sort the array using settings.comparator as comparator + * + * @param a the array to sort (it is sorted) + * @return the sorted array + */ + JustifiedGallery.prototype.sortArray = function (a) { + a.sort(this.settings.sort); + this.insertToGallery(a); + return a; + }; + + /** + * Reset the filters removing the 'jg-filtered' class from all the entries + * + * @param a the array to reset + */ + JustifiedGallery.prototype.resetFilters = function (a) { + for (var i = 0; i < a.length; i++) $(a[i]).removeClass('jg-filtered'); + return a; + }; + + /** + * Filter the entries considering theirs classes (if a string has been passed) or using a function for filtering. + * + * @param a the array to filter + * @return the filtered array + */ + JustifiedGallery.prototype.filterArray = function (a) { + var settings = this.settings; + if ($.type(settings.filter) === 'string') { + // Filter only keeping the entries passed in the string + return a.filter(function (el) { + var $el = $(el); + if ($el.is(settings.filter)) { + $el.removeClass('jg-filtered'); + return true; } else { - newImgW = settings.rowHeight * imgAspectRatio; - newImgH = settings.rowHeight; + $el.addClass('jg-filtered'); + return false; } + }); + } else if ($.isFunction(settings.filter)) { + // Filter using the passed function + return a.filter(settings.filter); + } + }; - availableWidth -= Math.round(newImgW); - $image.data('jg.jimgw', Math.round(newImgW)); - $image.data('jg.jimgh', Math.ceil(newImgH)); - if (i === 0 || minHeight > newImgH) minHeight = newImgH; + /** + * Modify the entries. With norewind only the new inserted images will be modified (the ones after lastAnalyzedIndex) + * + * @param functionToApply the function to call to modify the entries (e.g. sorting, randomization, filtering) + * @param norewind specify if the norewind has been called or not + */ + JustifiedGallery.prototype.modifyEntries = function (functionToApply, norewind) { + var lastEntries = norewind ? + this.entries.splice(this.lastAnalyzedIndex + 1, this.entries.length - this.lastAnalyzedIndex - 1) + : this.entries; + lastEntries = functionToApply.call(this, lastEntries); + this.entries = norewind ? this.entries.concat(lastEntries) : lastEntries; + }; + + /** + * Destroy the Justified Gallery instance. + * + * It clears all the css properties added in the style attributes. We doesn't backup the original + * values for those css attributes, because it costs (performance) and because in general one + * shouldn't use the style attribute for an uniform set of images (where we suppose the use of + * classes). Creating a backup is also difficult because JG could be called multiple times and + * with different style attributes. + */ + JustifiedGallery.prototype.destroy = function () { + clearInterval(this.checkWidthIntervalId); + + $.each(this.entries, $.proxy(function(_, entry) { + var $entry = $(entry); + + // Reset entry style + $entry.css('width', ''); + $entry.css('height', ''); + $entry.css('top', ''); + $entry.css('left', ''); + $entry.data('jg.loaded', undefined); + $entry.removeClass('jg-entry'); + + // Reset image style + var $img = this.imgFromEntry($entry); + $img.css('width', ''); + $img.css('height', ''); + $img.css('margin-left', ''); + $img.css('margin-top', ''); + $img.attr('src', $img.data('jg.originalSrc')); + $img.data('jg.originalSrc', undefined); + + // Remove caption + this.removeCaptionEventsHandlers($entry); + var $caption = this.captionFromEntry($entry); + if ($entry.data('jg.createdCaption')) { + // remove also the caption element (if created by jg) + $entry.data('jg.createdCaption', undefined); + if ($caption != null) $caption.remove(); + } else { + if ($caption != null) $caption.fadeTo(0, 1); } - if (settings.fixedHeight && minHeight > settings.rowHeight) - minHeight = settings.rowHeight; + }, this)); - return {minHeight: minHeight, justify: justify}; - } + this.$gallery.css('height', ''); + this.$gallery.removeClass('justified-gallery'); + this.$gallery.data('jg.controller', undefined); + }; - function rewind(context) { - context.lastAnalyzedIndex = -1; - context.buildingRow.entriesBuff = []; - context.buildingRow.aspectRatio = 0; - context.buildingRow.width = 0; - context.offY = context.border; - } + /** + * Analyze the images and builds the rows. It returns if it found an image that is not loaded. + * + * @param isForResize if the image analyzer is called for resizing or not, to call a different callback at the end + */ + JustifiedGallery.prototype.analyzeImages = function (isForResize) { + for (var i = this.lastAnalyzedIndex + 1; i < this.entries.length; i++) { + var $entry = $(this.entries[i]); + if ($entry.data('jg.loaded') === true || $entry.data('jg.loaded') === 'skipped') { + var availableWidth = this.galleryWidth - 2 * this.border - ( + (this.buildingRow.entriesBuff.length - 1) * this.settings.margins); + var imgAspectRatio = $entry.data('jg.width') / $entry.data('jg.height'); + if (availableWidth / (this.buildingRow.aspectRatio + imgAspectRatio) < this.settings.rowHeight) { + this.flushRow(false); + if(++this.yield.flushed >= this.yield.every) { + this.startImgAnalyzer(isForResize); + return; + } + } - function flushRow(context, isLastRow) { - var settings = context.settings; - var $entry, $image, minHeight, buildingRowRes, offX = context.border; + this.buildingRow.entriesBuff.push($entry); + this.buildingRow.aspectRatio += imgAspectRatio; + this.buildingRow.width += imgAspectRatio * this.settings.rowHeight; + this.lastAnalyzedIndex = i; - //DEBUG// console.log('flush (isLastRow: ' + isLastRow + ')'); - - buildingRowRes = prepareBuildingRow(context, isLastRow); - minHeight = buildingRowRes.minHeight; - if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) { - context.buildingRow.entriesBuff = []; - context.buildingRow.aspectRatio = 0; - context.buildingRow.width = 0; + } else if ($entry.data('jg.loaded') !== 'error') { return; } - - if (settings.maxRowHeight > 0 && settings.maxRowHeight < minHeight) - minHeight = settings.maxRowHeight; - else if (settings.maxRowHeight === 0 && (1.5 * settings.rowHeight) < minHeight) - minHeight = 1.5 * settings.rowHeight; - - for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) { - $entry = context.buildingRow.entriesBuff[i]; - $image = imgFromEntry($entry); - displayEntry($entry, offX, context.offY, $image.data('jg.jimgw'), - $image.data('jg.jimgh'), minHeight, context); - offX += $image.data('jg.jimgw') + settings.margins; - } - - //Gallery Height - context.$gallery.height(context.offY + minHeight + context.border + - (context.spinner.active ? context.spinner.$el.innerHeight() : 0) - ); - - if (!isLastRow || (minHeight <= context.settings.rowHeight && buildingRowRes.justify)) { - //Ready for a new row - context.offY += minHeight + context.settings.margins; - - //DEBUG// console.log('minHeight: ' + minHeight + ' offY: ' + context.offY); - - context.buildingRow.entriesBuff = []; //clear the array creating a new one - context.buildingRow.aspectRatio = 0; - context.buildingRow.width = 0; - context.$gallery.trigger('jg.rowflush'); - } } - function checkWidth(context) { - context.checkWidthIntervalId = setInterval(function () { - var galleryWidth = parseInt(context.$gallery.width(), 10); - if (context.galleryWidth !== galleryWidth) { - //DEBUG// console.log("resize. old: " + context.galleryWidth + " new: " + galleryWidth); - - context.galleryWidth = galleryWidth; - rewind(context); + // Last row flush (the row is not full) + if (this.buildingRow.entriesBuff.length > 0) this.flushRow(true); - // Restart to analyze - startImgAnalyzer(context, true); - } - }, context.settings.refreshTime); - } - - function startLoadingSpinnerAnimation(spinnerContext) { - clearInterval(spinnerContext.intervalId); - spinnerContext.intervalId = setInterval(function () { - if (spinnerContext.phase < spinnerContext.$points.length) - spinnerContext.$points.eq(spinnerContext.phase).fadeTo(spinnerContext.timeslot, 1); - else - spinnerContext.$points.eq(spinnerContext.phase - spinnerContext.$points.length) - .fadeTo(spinnerContext.timeslot, 0); - spinnerContext.phase = (spinnerContext.phase + 1) % (spinnerContext.$points.length * 2); - }, spinnerContext.timeslot); + if (this.isSpinnerActive()) { + this.stopLoadingSpinnerAnimation(); } - function stopLoadingSpinnerAnimation(spinnerContext) { - clearInterval(spinnerContext.intervalId); - spinnerContext.intervalId = null; + /* Stop, if there is, the timeout to start the analyzeImages. + This is because an image can be set loaded, and the timeout can be set, + but this image can be analyzed yet. + */ + this.stopImgAnalyzerStarter(); + + //On complete callback + this.$gallery.trigger(isForResize ? 'jg.resize' : 'jg.complete'); + }; + + /** + * Stops any ImgAnalyzer starter (that has an assigned timeout) + */ + JustifiedGallery.prototype.stopImgAnalyzerStarter = function () { + this.yield.flushed = 0; + if (this.imgAnalyzerTimeout !== null) clearTimeout(this.imgAnalyzerTimeout); + }; + + /** + * Starts the image analyzer. It is not immediately called to let the browser to update the view + * + * @param isForResize specifies if the image analyzer must be called for resizing or not + */ + JustifiedGallery.prototype.startImgAnalyzer = function (isForResize) { + var that = this; + this.stopImgAnalyzerStarter(); + this.imgAnalyzerTimeout = setTimeout(function () { + that.analyzeImages(isForResize); + }, 0.001); // we can't start it immediately due to a IE different behaviour + }; + + /** + * Checks if the image is loaded or not using another image object. We cannot use the 'complete' image property, + * because some browsers, with a 404 set complete = true. + * + * @param imageSrc the image src to load + * @param onLoad callback that is called when the image has been loaded + * @param onError callback that is called in case of an error + */ + JustifiedGallery.prototype.onImageEvent = function (imageSrc, onLoad, onError) { + if (!onLoad && !onError) return; + + var memImage = new Image(); + var $memImage = $(memImage); + if (onLoad) { + $memImage.one('load', function () { + $memImage.off('load error'); + onLoad(memImage); + }); } - - function stopImgAnalyzerStarter(context) { - context.yield.flushed = 0; - if (context.imgAnalyzerTimeout !== null) clearTimeout(context.imgAnalyzerTimeout); + if (onError) { + $memImage.one('error', function() { + $memImage.off('load error'); + onError(memImage); + }); } + memImage.src = imageSrc; + }; - function startImgAnalyzer(context, isForResize) { - stopImgAnalyzerStarter(context); - context.imgAnalyzerTimeout = setTimeout(function () { - analyzeImages(context, isForResize); - }, 0.001); - analyzeImages(context, isForResize); - } + /** + * Init of Justified Gallery controlled + * It analyzes all the entries starting theirs loading and calling the image analyzer (that works with loaded images) + */ + JustifiedGallery.prototype.init = function () { + var imagesToLoad = false, skippedImages = false, that = this; + $.each(this.entries, function (index, entry) { + var $entry = $(entry); + var $image = that.imgFromEntry($entry); - function analyzeImages(context, isForResize) { - - /* //DEBUG// - var rnd = parseInt(Math.random() * 10000, 10); - console.log('analyzeImages ' + rnd + ' start'); - console.log('images status: '); - for (var i = 0; i < context.entries.length; i++) { - var $entry = $(context.entries[i]); - var $image = imgFromEntry($entry); - console.log(i + ' (alt: ' + $image.attr('alt') + 'loaded: ' + $image.data('jg.loaded') + ')'); - }*/ + $entry.addClass('jg-entry'); - /* The first row */ - var settings = context.settings; - var isLastRow; - - for (var i = context.lastAnalyzedIndex + 1; i < context.entries.length; i++) { - var $entry = $(context.entries[i]); - var $image = imgFromEntry($entry); + if ($entry.data('jg.loaded') !== true && $entry.data('jg.loaded') !== 'skipped') { - if ($image.data('jg.loaded') === true || $image.data('jg.loaded') === 'skipped') { - isLastRow = i >= context.entries.length - 1; + // Link Rel global overwrite + if (that.settings.rel !== null) $entry.attr('rel', that.settings.rel); - var availableWidth = context.galleryWidth - 2 * context.border - ( - (context.buildingRow.entriesBuff.length - 1) * settings.margins); - var imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh'); - if (availableWidth / (context.buildingRow.aspectRatio + imgAspectRatio) < settings.rowHeight) { - flushRow(context, isLastRow); - if(++context.yield.flushed >= context.yield.every) { - //DEBUG// console.log("yield"); - startImgAnalyzer(context, isForResize); - return; + // Link Target global overwrite + if (that.settings.target !== null) $entry.attr('target', that.settings.target); + + if ($image !== null) { + + // Image src + var imageSrc = that.extractImgSrcFromImage($image); + $image.attr('src', imageSrc); + + /* If we have the height and the width, we don't wait that the image is loaded, but we start directly + * with the justification */ + if (that.settings.waitThumbnailsLoad === false) { + var width = parseInt($image.attr('width'), 10); + var height = parseInt($image.attr('height'), 10); + if (!isNaN(width) && !isNaN(height)) { + $entry.data('jg.width', width); + $entry.data('jg.height', height); + $entry.data('jg.loaded', 'skipped'); + skippedImages = true; + that.startImgAnalyzer(false); + return true; // continue } } - context.buildingRow.entriesBuff.push($entry); - context.buildingRow.aspectRatio += imgAspectRatio; - context.buildingRow.width += imgAspectRatio * settings.rowHeight; - context.lastAnalyzedIndex = i; + $entry.data('jg.loaded', false); + imagesToLoad = true; - } else if ($image.data('jg.loaded') !== 'error') { - return; - } - } + // Spinner start + if (!that.isSpinnerActive()) { + that.startLoadingSpinnerAnimation(); + } - // Last row flush (the row is not full) - if (context.buildingRow.entriesBuff.length > 0) flushRow(context, true); + that.onImageEvent(imageSrc, function (loadImg) { // image loaded + $entry.data('jg.width', loadImg.width); + $entry.data('jg.height', loadImg.height); + $entry.data('jg.loaded', true); + that.startImgAnalyzer(false); + }, function () { // image load error + $entry.data('jg.loaded', 'error'); + that.startImgAnalyzer(false); + }); - if (context.spinner.active) { - context.spinner.active = false; - context.$gallery.height(context.$gallery.height() - context.spinner.$el.innerHeight()); - context.spinner.$el.detach(); - stopLoadingSpinnerAnimation(context.spinner); - } - - /* Stop, if there is, the timeout to start the analyzeImages. - This is because an image can be set loaded, and the timeout can be set, - but this image can be analyzed yet. - */ - stopImgAnalyzerStarter(context); - - //On complete callback - if (!isForResize) - context.$gallery.trigger('jg.complete'); - else - context.$gallery.trigger('jg.resize'); - - //DEBUG// console.log('analyzeImages ' + rnd + ' end'); - } - - function checkSettings (context) { - var settings = context.settings; - - function checkSuffixesRange(range) { - if (typeof settings.sizeRangeSuffixes[range] !== 'string') - throw 'sizeRangeSuffixes.' + range + ' must be a string'; - } - - function checkOrConvertNumber(parent, settingName) { - if (typeof parent[settingName] === 'string') { - parent[settingName] = parseFloat(parent[settingName], 10); - if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName; - } else if (typeof parent[settingName] === 'number') { - if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName; } else { - throw settingName + ' must be a number'; + $entry.data('jg.loaded', true); + $entry.data('jg.width', $entry.width() | $entry.css('width') | 1); + $entry.data('jg.height', $entry.height() | $entry.css('height') | 1); } + } - if (typeof settings.sizeRangeSuffixes !== 'object') - throw 'sizeRangeSuffixes must be defined and must be an object'; + }); - checkSuffixesRange('lt100'); - checkSuffixesRange('lt240'); - checkSuffixesRange('lt320'); - checkSuffixesRange('lt500'); - checkSuffixesRange('lt640'); - checkSuffixesRange('lt1024'); - - checkOrConvertNumber(settings, 'rowHeight'); - checkOrConvertNumber(settings, 'maxRowHeight'); - - if (settings.maxRowHeight > 0 && - settings.maxRowHeight < settings.rowHeight) { - settings.maxRowHeight = settings.rowHeight; - } - - checkOrConvertNumber(settings, 'margins'); - checkOrConvertNumber(settings, 'border'); - - if (settings.lastRow !== 'nojustify' && - settings.lastRow !== 'justify' && - settings.lastRow !== 'hide') { - throw 'lastRow must be "nojustify", "justify" or "hide"'; - } - - checkOrConvertNumber(settings, 'justifyThreshold'); - if (settings.justifyThreshold < 0 || settings.justifyThreshold > 1) - throw 'justifyThreshold must be in the interval [0,1]'; - if (typeof settings.cssAnimation !== 'boolean') { - throw 'cssAnimation must be a boolean'; - } - - checkOrConvertNumber(settings.captionSettings, 'animationDuration'); - checkOrConvertNumber(settings, 'imagesAnimationDuration'); - - checkOrConvertNumber(settings.captionSettings, 'visibleOpacity'); - if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1) - throw 'captionSettings.visibleOpacity must be in the interval [0, 1]'; - - checkOrConvertNumber(settings.captionSettings, 'nonVisibleOpacity'); - if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1) - throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]'; - - if (typeof settings.fixedHeight !== 'boolean') { - throw 'fixedHeight must be a boolean'; - } - - if (typeof settings.captions !== 'boolean') { - throw 'captions must be a boolean'; - } - - checkOrConvertNumber(settings, 'refreshTime'); - - if (typeof settings.randomize !== 'boolean') { - throw 'randomize must be a boolean'; - } + if (!imagesToLoad && !skippedImages) this.startImgAnalyzer(false); + this.checkWidth(); + }; + /** + * Checks that it is a valid number. If a string is passed it is converted to a number + * + * @param settingContainer the object that contains the setting (to allow the conversion) + * @param settingName the setting name + */ + JustifiedGallery.prototype.checkOrConvertNumber = function (settingContainer, settingName) { + if ($.type(settingContainer[settingName]) === 'string') { + settingContainer[settingName] = parseFloat(settingContainer[settingName]); } - function onImageEvent(imageSrc, onLoad, onError) { - if (!onLoad && !onError) { - return; - } - /* Check if the image is loaded or not using another image object. - We cannot use the 'complete' image property, because some browsers, - with a 404 set complete = true */ - var memImage = new Image(); - var $memImage = $(memImage); - if (onLoad) { - $memImage.one('load', function () { - $memImage.off('load error'); - onLoad(memImage); - }); - } - if (onError) { - $memImage.one('error', function() { - $memImage.off('load error'); - onError(memImage); - }); - } - memImage.src = imageSrc; + if ($.type(settingContainer[settingName]) === 'number') { + if (isNaN(settingContainer[settingName])) throw 'invalid number for ' + settingName; + } else { + throw settingName + ' must be a number'; + } + }; + + /** + * Checks the sizeRangeSuffixes and, if necessary, converts + * its keys from string (e.g. old settings with 'lt100') to int. + */ + JustifiedGallery.prototype.checkSizeRangesSuffixes = function () { + if ($.type(this.settings.sizeRangeSuffixes) !== 'object') { + throw 'sizeRangeSuffixes must be defined and must be an object'; } + var suffixRanges = []; + for (var rangeIdx in this.settings.sizeRangeSuffixes) { + if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(rangeIdx); + } + + var newSizeRngSuffixes = {0: ''}; + for (var i = 0; i < suffixRanges.length; i++) { + if ($.type(suffixRanges[i]) === 'string') { + try { + var numIdx = parseInt(suffixRanges[i].replace(/^[a-z]+/, ''), 10); + newSizeRngSuffixes[numIdx] = this.settings.sizeRangeSuffixes[suffixRanges[i]]; + } catch (e) { + throw 'sizeRangeSuffixes keys must contains correct numbers (' + e + ')'; + } + } else { + newSizeRngSuffixes[suffixRanges[i]] = this.settings.sizeRangeSuffixes[suffixRanges[i]]; + } + } + + this.settings.sizeRangeSuffixes = newSizeRngSuffixes; + }; + + /** + * check and convert the maxRowHeight setting + */ + JustifiedGallery.prototype.retrieveMaxRowHeight = function () { + var newMaxRowHeight = { }; + + if ($.type(this.settings.maxRowHeight) === 'string') { + if (this.settings.maxRowHeight.match(/^[0-9]+%$/)) { + newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight.match(/^([0-9])+%$/)[1]) / 100; + newMaxRowHeight.percentage = false; + } else { + newMaxRowHeight.value = parseFloat(this.settings.maxRowHeight); + newMaxRowHeight.percentage = true; + } + } else if ($.type(this.settings.maxRowHeight) === 'number') { + newMaxRowHeight.value = this.settings.maxRowHeight; + newMaxRowHeight.percentage = false; + } else { + throw 'maxRowHeight must be a number or a percentage'; + } + + // check if the converted value is not a number + if (isNaN(newMaxRowHeight.value)) throw 'invalid number for maxRowHeight'; + + // check values + if (newMaxRowHeight.percentage) { + if (newMaxRowHeight.value < 100) newMaxRowHeight.value = 100; + } else { + if (newMaxRowHeight.value > 0 && newMaxRowHeight.value < this.settings.rowHeight) { + newMaxRowHeight.value = this.settings.rowHeight; + } + } + + return newMaxRowHeight; + + }; + + /** + * Checks the settings + */ + JustifiedGallery.prototype.checkSettings = function () { + this.checkSizeRangesSuffixes(); + + this.checkOrConvertNumber(this.settings, 'rowHeight'); + this.checkOrConvertNumber(this.settings, 'margins'); + this.checkOrConvertNumber(this.settings, 'border'); + + if (this.settings.lastRow !== 'nojustify' && + this.settings.lastRow !== 'justify' && + this.settings.lastRow !== 'hide') { + throw 'lastRow must be "nojustify", "justify" or "hide"'; + } + + this.checkOrConvertNumber(this.settings, 'justifyThreshold'); + if (this.settings.justifyThreshold < 0 || this.settings.justifyThreshold > 1) { + throw 'justifyThreshold must be in the interval [0,1]'; + } + if ($.type(this.settings.cssAnimation) !== 'boolean') { + throw 'cssAnimation must be a boolean'; + } + + if ($.type(this.settings.captions) !== 'boolean') throw 'captions must be a boolean'; + this.checkOrConvertNumber(this.settings.captionSettings, 'animationDuration'); + + this.checkOrConvertNumber(this.settings.captionSettings, 'visibleOpacity'); + if (this.settings.captionSettings.visibleOpacity < 0 || + this.settings.captionSettings.visibleOpacity > 1) { + throw 'captionSettings.visibleOpacity must be in the interval [0, 1]'; + } + + this.checkOrConvertNumber(this.settings.captionSettings, 'nonVisibleOpacity'); + if (this.settings.captionSettings.nonVisibleOpacity < 0 || + this.settings.captionSettings.nonVisibleOpacity > 1) { + throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]'; + } + + if ($.type(this.settings.fixedHeight) !== 'boolean') throw 'fixedHeight must be a boolean'; + this.checkOrConvertNumber(this.settings, 'imagesAnimationDuration'); + this.checkOrConvertNumber(this.settings, 'refreshTime'); + if ($.type(this.settings.randomize) !== 'boolean') throw 'randomize must be a boolean'; + if ($.type(this.settings.selector) !== 'string') throw 'selector must be a string'; + + if (this.settings.sort !== false && !$.isFunction(this.settings.sort)) { + throw 'sort must be false or a comparison function'; + } + + if (this.settings.filter !== false && !$.isFunction(this.settings.sort) && + $.type(this.settings.filter) !== 'string') { + throw 'filter must be false, a string or a filter function'; + } + }; + + /** + * It brings all the indexes from the sizeRangeSuffixes and it orders them. They are then sorted and returned. + * @returns {Array} sorted suffix ranges + */ + JustifiedGallery.prototype.retrieveSuffixRanges = function () { + var suffixRanges = []; + for (var rangeIdx in this.settings.sizeRangeSuffixes) { + if (this.settings.sizeRangeSuffixes.hasOwnProperty(rangeIdx)) suffixRanges.push(parseInt(rangeIdx, 10)); + } + suffixRanges.sort(function (a, b) { return a > b ? 1 : a < b ? -1 : 0; }); + return suffixRanges; + }; + + /** + * Update the existing settings only changing some of them + * + * @param newSettings the new settings (or a subgroup of them) + */ + JustifiedGallery.prototype.updateSettings = function (newSettings) { + // In this case Justified Gallery has been called again changing only some options + this.settings = $.extend({}, this.settings, newSettings); + this.checkSettings(); + + // As reported in the settings: negative value = same as margins, 0 = disabled + this.border = this.settings.border >= 0 ? this.settings.border : this.settings.margins; + + this.maxRowHeight = this.retrieveMaxRowHeight(); + this.suffixRanges = this.retrieveSuffixRanges(); + }; + + /** + * Justified Gallery plugin for jQuery + * + * Events + * - jg.complete : called when all the gallery has been created + * - jg.resize : called when the gallery has been resized + * - jg.rowflush : when a new row appears + * + * @param arg the action (or the settings) passed when the plugin is called + * @returns {*} the object itself + */ + $.fn.justifiedGallery = function (arg) { return this.each(function (index, gallery) { var $gallery = $(gallery); $gallery.addClass('justified-gallery'); - var context = $gallery.data('jg.context'); - if (typeof context === 'undefined') { - - if (typeof arg !== 'undefined' && arg !== null && typeof arg !== 'object') + var controller = $gallery.data('jg.controller'); + if (typeof controller === 'undefined') { + // Create controller and assign it to the object data + if (typeof arg !== 'undefined' && arg !== null && $.type(arg) !== 'object') { throw 'The argument must be an object'; - - // Spinner init - var $spinner = $('
'); - var extendedSettings = $.extend({}, defaults, arg); - - var border = extendedSettings.border >= 0 ? extendedSettings.border : extendedSettings.margins; - - //Context init - context = { - settings : extendedSettings, - imgAnalyzerTimeout : null, - entries : null, - buildingRow : { - entriesBuff : [], - width : 0, - aspectRatio : 0 - }, - lastAnalyzedIndex : -1, - yield : { - every : 2, /* do a flush every context.yield.every flushes ( - * must be greater than 1, else the analyzeImages will loop */ - flushed : 0 //flushed rows without a yield - }, - border : border, - offY : border, - spinner : { - active : false, - phase : 0, - timeslot : 150, - $el : $spinner, - $points : $spinner.find('span'), - intervalId : null - }, - checkWidthIntervalId : null, - galleryWidth : $gallery.width(), - $gallery : $gallery - }; - - $gallery.data('jg.context', context); - + } + controller = new JustifiedGallery($gallery, $.extend({}, $.fn.justifiedGallery.defaults, arg)); + $gallery.data('jg.controller', controller); } else if (arg === 'norewind') { - /* Hide the image of the buildingRow to prevent strange effects when the row will be - re-justified again */ - for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) { - hideImgImmediately(context.buildingRow.entriesBuff[i], context); - } - // In this case we don't rewind, and analyze all the images + // In this case we don't rewind: we analyze only the latest images (e.g. to complete the last unfinished row + controller.hideBuildingRowImages(); + } else if (arg === 'destroy') { + controller.destroy(); + return; } else { - context.settings = $.extend({}, context.settings, arg); - context.border = context.settings.border >= 0 ? context.settings.border : context.settings.margins; - rewind(context); - } - - checkSettings(context); - - context.entries = $gallery.find('> a, > div:not(.spinner, #page-end)').toArray(); - if (context.entries.length === 0) return; - - // Randomize - if (context.settings.randomize) { - context.entries.sort(function () { return Math.random() * 2 - 1; }); - $.each(context.entries, function () { - $(this).appendTo($gallery); - }); + // In this case Justified Gallery has been called again changing only some options + controller.updateSettings(arg); + controller.rewind(); } - var imagesToLoad = false; - var skippedImages = false; - $.each(context.entries, function (index, entry) { - var $entry = $(entry); - var $image = imgFromEntry($entry); + // Update the entries list + if (!controller.updateEntries(arg === 'norewind')) return; - $entry.addClass('jg-entry'); + // Init justified gallery + controller.init(); - if ($image.data('jg.loaded') !== true && $image.data('jg.loaded') !== 'skipped') { - - // Link Rel global overwrite - if (context.settings.rel !== null) $entry.attr('rel', context.settings.rel); - - // Link Target global overwrite - if (context.settings.target !== null) $entry.attr('target', context.settings.target); - - // Image src - var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ? - $image.data('safe-src') : $image.attr('src'); - $image.data('jg.originalSrc', imageSrc); - $image.attr('src', imageSrc); - - var width = parseInt($image.attr('width'), 10); - var height = parseInt($image.attr('height'), 10); - if(context.settings.waitThumbnailsLoad !== true && !isNaN(width) && !isNaN(height)) { - $image.data('jg.imgw', width); - $image.data('jg.imgh', height); - $image.data('jg.loaded', 'skipped'); - skippedImages = true; - startImgAnalyzer(context, false); - return true; - } - - $image.data('jg.loaded', false); - imagesToLoad = true; - - // Spinner start - if (context.spinner.active === false) { - context.spinner.active = true; - $gallery.append(context.spinner.$el); - $gallery.height(context.offY + context.spinner.$el.innerHeight()); - startLoadingSpinnerAnimation(context.spinner); - } - - onImageEvent(imageSrc, function imgLoaded (loadImg) { - //DEBUG// console.log('img load (alt: ' + $image.attr('alt') + ')'); - $image.data('jg.imgw', loadImg.width); - $image.data('jg.imgh', loadImg.height); - $image.data('jg.loaded', true); - startImgAnalyzer(context, false); - }, function imgLoadError () { - //DEBUG// console.log('img error (alt: ' + $image.attr('alt') + ')'); - $image.data('jg.loaded', 'error'); - startImgAnalyzer(context, false); - }); - - } - - }); - - if (!imagesToLoad && !skippedImages) startImgAnalyzer(context, false); - checkWidth(context); }); - }; - + + // Default options + $.fn.justifiedGallery.defaults = { + sizeRangeSuffixes: { }, /* e.g. Flickr configuration + { + 100: '_t', // used when longest is less than 100px + 240: '_m', // used when longest is between 101px and 240px + 320: '_n', // ... + 500: '', + 640: '_z', + 1024: '_b' // used as else case because it is the last + } + */ + rowHeight: 120, + maxRowHeight: '200%', // negative value = no limits, number to express the value in pixels, + // '[0-9]+%' to express in percentage (e.g. 200% means that the row height + // can't exceed 2 * rowHeight) + margins: 1, + border: -1, // negative value = same as margins, 0 = disabled, any other value to set the border + + lastRow: 'nojustify', // or can be 'justify' or 'hide' + justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified + * (i.e. lastRow setting is not considered) */ + fixedHeight: false, + waitThumbnailsLoad: true, + captions: true, + cssAnimation: false, + imagesAnimationDuration: 500, // ignored with css animations + captionSettings: { // ignored with css animations + animationDuration: 500, + visibleOpacity: 0.7, + nonVisibleOpacity: 0.0 + }, + rel: null, // rewrite the rel of each analyzed links + target: null, // rewrite the target of all links + extension: /\.[^.\\/]+$/, // regexp to capture the extension of an image + refreshTime: 100, // time interval (in ms) to check if the page changes its width + randomize: false, + sort: false, /* + - false: to do not sort + - function: to sort them using the function as comparator (see Array.prototype.sort()) + */ + filter: false, /* + - false: for a disabled filter + - a string: an entry is kept if entry.is(filter string) returns true + see jQuery's .is() function for further information + - a function: invoked with arguments (entry, index, array). Return true to keep the entry, false otherwise. + see Array.prototype.filter for further information. + */ + selector: '> a, > div:not(.spinner)' // The selector that is used to know what are the entries of the gallery + }; + }(jQuery)); diff --git a/library/justifiedGallery/jquery.justifiedGallery.min.js b/library/justifiedGallery/jquery.justifiedGallery.min.js new file mode 100644 index 000000000..74f333208 --- /dev/null +++ b/library/justifiedGallery/jquery.justifiedGallery.min.js @@ -0,0 +1,7 @@ +/*! + * Justified Gallery - v3.6.0 + * http://miromannino.github.io/Justified-Gallery/ + * Copyright (c) 2015 Miro Mannino + * Licensed under the MIT license. + */ +!function(a){var b=function(b,c){this.settings=c,this.checkSettings(),this.imgAnalyzerTimeout=null,this.entries=null,this.buildingRow={entriesBuff:[],width:0,aspectRatio:0},this.lastAnalyzedIndex=-1,this.yield={every:2,flushed:0},this.border=c.border>=0?c.border:c.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges(),this.offY=this.border,this.spinner={phase:0,timeSlot:150,$el:a('
'),intervalId:null},this.checkWidthIntervalId=null,this.galleryWidth=b.width(),this.$gallery=b};b.prototype.getSuffix=function(a,b){var c,d;for(c=a>b?a:b,d=0;d img");return 0===b.length&&(b=a.find("> a > img")),0===b.length?null:b},b.prototype.captionFromEntry=function(a){var b=a.find("> .caption");return 0===b.length?null:b},b.prototype.displayEntry=function(b,c,d,e,f,g){b.width(e),b.height(g),b.css("top",d),b.css("left",c);var h=this.imgFromEntry(b);if(null!==h){h.css("width",e),h.css("height",f),h.css("margin-left",-e/2),h.css("margin-top",-f/2);var i=h.attr("src"),j=this.newSrc(i,e,f);h.one("error",function(){h.attr("src",h.data("jg.originalSrc"))});var k=function(){i!==j&&h.attr("src",j)};"skipped"===b.data("jg.loaded")?this.onImageEvent(i,a.proxy(function(){this.showImg(b,k),b.data("jg.loaded",!0)},this)):this.showImg(b,k)}else this.showImg(b);this.displayEntryCaption(b)},b.prototype.displayEntryCaption=function(b){var c=this.imgFromEntry(b);if(null!==c&&this.settings.captions){var d=this.captionFromEntry(b);if(null==d){var e=c.attr("alt");"undefined"==typeof e&&(e=b.attr("title")),"undefined"!=typeof e&&(d=a('
'+e+"
"),b.append(d),b.data("jg.createdCaption",!0))}null!==d&&(this.settings.cssAnimation||d.stop().fadeTo(0,this.settings.captionSettings.nonVisibleOpacity),this.addCaptionEventsHandlers(b))}else this.removeCaptionEventsHandlers(b)},b.prototype.onEntryMouseEnterForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.addClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.visibleOpacity)},b.prototype.onEntryMouseLeaveForCaption=function(b){var c=this.captionFromEntry(a(b.currentTarget));this.settings.cssAnimation?c.removeClass("caption-visible").removeClass("caption-hidden"):c.stop().fadeTo(this.settings.captionSettings.animationDuration,this.settings.captionSettings.nonVisibleOpacity)},b.prototype.addCaptionEventsHandlers=function(b){var c=b.data("jg.captionMouseEvents");"undefined"==typeof c&&(c={mouseenter:a.proxy(this.onEntryMouseEnterForCaption,this),mouseleave:a.proxy(this.onEntryMouseLeaveForCaption,this)},b.on("mouseenter",void 0,void 0,c.mouseenter),b.on("mouseleave",void 0,void 0,c.mouseleave),b.data("jg.captionMouseEvents",c))},b.prototype.removeCaptionEventsHandlers=function(a){var b=a.data("jg.captionMouseEvents");"undefined"!=typeof b&&(a.off("mouseenter",void 0,b.mouseenter),a.off("mouseleave",void 0,b.mouseleave),a.removeData("jg.captionMouseEvents"))},b.prototype.prepareBuildingRow=function(a){var b,c,d,e,f,g=!0,h=0,i=this.galleryWidth-2*this.border-(this.buildingRow.entriesBuff.length-1)*this.settings.margins,j=i/this.buildingRow.aspectRatio,k=this.buildingRow.width/i>this.settings.justifyThreshold;if(a&&"hide"===this.settings.lastRow&&!k){for(b=0;bf)&&(h=f);return this.settings.fixedHeight&&h>this.settings.rowHeight&&(h=this.settings.rowHeight),{minHeight:h,justify:g}},b.prototype.clearBuildingRow=function(){this.buildingRow.entriesBuff=[],this.buildingRow.aspectRatio=0,this.buildingRow.width=0},b.prototype.flushRow=function(a){var b,c,d,e=this.settings,f=this.border;if(d=this.prepareBuildingRow(a),c=d.minHeight,a&&"hide"===e.lastRow&&-1===c)return void this.clearBuildingRow();this.maxRowHeight.percentage?this.maxRowHeight.value*e.rowHeight0&&this.maxRowHeight.value0;b--)c=Math.floor(Math.random()*(b+1)),d=a[b],a[b]=a[c],a[c]=d;return this.insertToGallery(a),a},b.prototype.sortArray=function(a){return a.sort(this.settings.sort),this.insertToGallery(a),a},b.prototype.resetFilters=function(b){for(var c=0;c=this.yield.every))return void this.startImgAnalyzer(b);this.buildingRow.entriesBuff.push(d),this.buildingRow.aspectRatio+=f,this.buildingRow.width+=f*this.settings.rowHeight,this.lastAnalyzedIndex=c}else if("error"!==d.data("jg.loaded"))return}this.buildingRow.entriesBuff.length>0&&this.flushRow(!0),this.isSpinnerActive()&&this.stopLoadingSpinnerAnimation(),this.stopImgAnalyzerStarter(),this.$gallery.trigger(b?"jg.resize":"jg.complete")},b.prototype.stopImgAnalyzerStarter=function(){this.yield.flushed=0,null!==this.imgAnalyzerTimeout&&clearTimeout(this.imgAnalyzerTimeout)},b.prototype.startImgAnalyzer=function(a){var b=this;this.stopImgAnalyzerStarter(),this.imgAnalyzerTimeout=setTimeout(function(){b.analyzeImages(a)},.001)},b.prototype.onImageEvent=function(b,c,d){if(c||d){var e=new Image,f=a(e);c&&f.one("load",function(){f.off("load error"),c(e)}),d&&f.one("error",function(){f.off("load error"),d(e)}),e.src=b}},b.prototype.init=function(){var b=!1,c=!1,d=this;a.each(this.entries,function(e,f){var g=a(f),h=d.imgFromEntry(g);if(g.addClass("jg-entry"),g.data("jg.loaded")!==!0&&"skipped"!==g.data("jg.loaded"))if(null!==d.settings.rel&&g.attr("rel",d.settings.rel),null!==d.settings.target&&g.attr("target",d.settings.target),null!==h){var i=d.extractImgSrcFromImage(h);if(h.attr("src",i),d.settings.waitThumbnailsLoad===!1){var j=parseInt(h.attr("width"),10),k=parseInt(h.attr("height"),10);if(!isNaN(j)&&!isNaN(k))return g.data("jg.width",j),g.data("jg.height",k),g.data("jg.loaded","skipped"),c=!0,d.startImgAnalyzer(!1),!0}g.data("jg.loaded",!1),b=!0,d.isSpinnerActive()||d.startLoadingSpinnerAnimation(),d.onImageEvent(i,function(a){g.data("jg.width",a.width),g.data("jg.height",a.height),g.data("jg.loaded",!0),d.startImgAnalyzer(!1)},function(){g.data("jg.loaded","error"),d.startImgAnalyzer(!1)})}else g.data("jg.loaded",!0),g.data("jg.width",g.width()|g.css("width")|1),g.data("jg.height",g.height()|g.css("height")|1)}),b||c||this.startImgAnalyzer(!1),this.checkWidth()},b.prototype.checkOrConvertNumber=function(b,c){if("string"===a.type(b[c])&&(b[c]=parseFloat(b[c])),"number"!==a.type(b[c]))throw c+" must be a number";if(isNaN(b[c]))throw"invalid number for "+c},b.prototype.checkSizeRangesSuffixes=function(){if("object"!==a.type(this.settings.sizeRangeSuffixes))throw"sizeRangeSuffixes must be defined and must be an object";var b=[];for(var c in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(c)&&b.push(c);for(var d={0:""},e=0;e0&&b.value1)throw"justifyThreshold must be in the interval [0,1]";if("boolean"!==a.type(this.settings.cssAnimation))throw"cssAnimation must be a boolean";if("boolean"!==a.type(this.settings.captions))throw"captions must be a boolean";if(this.checkOrConvertNumber(this.settings.captionSettings,"animationDuration"),this.checkOrConvertNumber(this.settings.captionSettings,"visibleOpacity"),this.settings.captionSettings.visibleOpacity<0||this.settings.captionSettings.visibleOpacity>1)throw"captionSettings.visibleOpacity must be in the interval [0, 1]";if(this.checkOrConvertNumber(this.settings.captionSettings,"nonVisibleOpacity"),this.settings.captionSettings.nonVisibleOpacity<0||this.settings.captionSettings.nonVisibleOpacity>1)throw"captionSettings.nonVisibleOpacity must be in the interval [0, 1]";if("boolean"!==a.type(this.settings.fixedHeight))throw"fixedHeight must be a boolean";if(this.checkOrConvertNumber(this.settings,"imagesAnimationDuration"),this.checkOrConvertNumber(this.settings,"refreshTime"),"boolean"!==a.type(this.settings.randomize))throw"randomize must be a boolean";if("string"!==a.type(this.settings.selector))throw"selector must be a string";if(this.settings.sort!==!1&&!a.isFunction(this.settings.sort))throw"sort must be false or a comparison function";if(this.settings.filter!==!1&&!a.isFunction(this.settings.sort)&&"string"!==a.type(this.settings.filter))throw"filter must be false, a string or a filter function"},b.prototype.retrieveSuffixRanges=function(){var a=[];for(var b in this.settings.sizeRangeSuffixes)this.settings.sizeRangeSuffixes.hasOwnProperty(b)&&a.push(parseInt(b,10));return a.sort(function(a,b){return a>b?1:b>a?-1:0}),a},b.prototype.updateSettings=function(b){this.settings=a.extend({},this.settings,b),this.checkSettings(),this.border=this.settings.border>=0?this.settings.border:this.settings.margins,this.maxRowHeight=this.retrieveMaxRowHeight(),this.suffixRanges=this.retrieveSuffixRanges()},a.fn.justifiedGallery=function(c){return this.each(function(d,e){var f=a(e);f.addClass("justified-gallery");var g=f.data("jg.controller");if("undefined"==typeof g){if("undefined"!=typeof c&&null!==c&&"object"!==a.type(c))throw"The argument must be an object";g=new b(f,a.extend({},a.fn.justifiedGallery.defaults,c)),f.data("jg.controller",g)}else if("norewind"===c)g.hideBuildingRowImages();else{if("destroy"===c)return void g.destroy();g.updateSettings(c),g.rewind()}g.updateEntries("norewind"===c)&&g.init()})},a.fn.justifiedGallery.defaults={sizeRangeSuffixes:{},rowHeight:120,maxRowHeight:"200%",margins:1,border:-1,lastRow:"nojustify",justifyThreshold:.75,fixedHeight:!1,waitThumbnailsLoad:!0,captions:!0,cssAnimation:!1,imagesAnimationDuration:500,captionSettings:{animationDuration:500,visibleOpacity:.7,nonVisibleOpacity:0},rel:null,target:null,extension:/\.[^.\\/]+$/,refreshTime:100,randomize:!1,sort:!1,filter:!1,selector:"> a, > div:not(.spinner)"}}(jQuery); \ No newline at end of file diff --git a/library/justifiedGallery/justifiedGallery.css b/library/justifiedGallery/justifiedGallery.css index 3a0d55ad5..0d45475ce 100644 --- a/library/justifiedGallery/justifiedGallery.css +++ b/library/justifiedGallery/justifiedGallery.css @@ -1,5 +1,5 @@ /*! - * Justified Gallery - v3.5.4 + * Justified Gallery - v3.6.0 * http://miromannino.github.io/Justified-Gallery/ * Copyright (c) 2015 Miro Mannino * Licensed under the MIT license. @@ -126,6 +126,9 @@ -moz-animation: justified-gallery-show-entry-animation 500ms 0 ease; -ms-animation: justified-gallery-show-entry-animation 500ms 0 ease; } +.justified-gallery > .jg-filtered { + display: none; +} .justified-gallery > .spinner { position: absolute; bottom: 0; diff --git a/library/justifiedGallery/justifiedGallery.min.css b/library/justifiedGallery/justifiedGallery.min.css new file mode 100644 index 000000000..d7b1c6726 --- /dev/null +++ b/library/justifiedGallery/justifiedGallery.min.css @@ -0,0 +1,7 @@ +/*! + * Justified Gallery - v3.6.0 + * http://miromannino.github.io/Justified-Gallery/ + * Copyright (c) 2015 Miro Mannino + * Licensed under the MIT license. + */ +@-webkit-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-moz-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-o-keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@keyframes justified-gallery-show-caption-animation{from{opacity:0}to{opacity:.7}}@-webkit-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@-moz-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@-o-keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}@keyframes justified-gallery-show-entry-animation{from{opacity:0}to{opacity:1}}.justified-gallery{width:100%;position:relative;overflow:hidden}.justified-gallery>a,.justified-gallery>div{position:absolute;display:inline-block;overflow:hidden;opacity:0;filter:alpha(opacity=0)}.justified-gallery>a>img,.justified-gallery>div>img,.justified-gallery>a>a>img,.justified-gallery>div>a>img{position:absolute;top:50%;left:50%;margin:0;padding:0;border:0}.justified-gallery>a>.caption,.justified-gallery>div>.caption{display:none;position:absolute;bottom:0;padding:5px;background-color:#000;left:0;right:0;margin:0;color:#fff;font-size:12px;font-weight:300;font-family:sans-serif}.justified-gallery>a>.caption.caption-visible,.justified-gallery>div>.caption.caption-visible{display:initial;opacity:.7;filter:"alpha(opacity=70)";-webkit-animation:justified-gallery-show-caption-animation 500ms 0 ease;-moz-animation:justified-gallery-show-caption-animation 500ms 0 ease;-ms-animation:justified-gallery-show-caption-animation 500ms 0 ease}.justified-gallery>.entry-visible{opacity:1;filter:alpha(opacity=100);-webkit-animation:justified-gallery-show-entry-animation 500ms 0 ease;-moz-animation:justified-gallery-show-entry-animation 500ms 0 ease;-ms-animation:justified-gallery-show-entry-animation 500ms 0 ease}.justified-gallery>.jg-filtered{display:none}.justified-gallery>.spinner{position:absolute;bottom:0;margin-left:-24px;padding:10px 0;left:50%;opacity:initial;filter:initial;overflow:initial}.justified-gallery>.spinner>span{display:inline-block;opacity:0;filter:alpha(opacity=0);width:8px;height:8px;margin:0 4px;background-color:#000;border-top-left-radius:6px;border-top-right-radius:6px;border-bottom-right-radius:6px;border-bottom-left-radius:6px} \ No newline at end of file diff --git a/version.inc b/version.inc index 14dff2773..e512ec77c 100644 --- a/version.inc +++ b/version.inc @@ -1 +1 @@ -2015-06-29.1078 +2015-07-01.1080 diff --git a/view/js/main.js b/view/js/main.js index 698e2d79c..5fe778488 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -741,6 +741,7 @@ function pageUpdate() { function justifyPhotos() { justifiedGalleryActive = true; $('#photo-album-contents').justifiedGallery({ + selector: '> a, > div:not(.spinner, #page-end)', margins: 3, border: 0, sizeRangeSuffixes: { diff --git a/view/php/theme_init.php b/view/php/theme_init.php index c1aeb4ab7..49b3511c9 100644 --- a/view/php/theme_init.php +++ b/view/php/theme_init.php @@ -9,11 +9,11 @@ head_add_css('library/jRange/jquery.range.css'); head_add_css('view/css/conversation.css'); head_add_css('view/css/widgets.css'); head_add_css('view/css/colorbox.css'); -head_add_css('library/justifiedGallery/justifiedGallery.css'); +head_add_css('library/justifiedGallery/justifiedGallery.min.css'); head_add_js('jquery.js'); //head_add_js('jquery-migrate-1.1.1.js'); -head_add_js('library/justifiedGallery/jquery.justifiedGallery.js'); +head_add_js('library/justifiedGallery/jquery.justifiedGallery.min.js'); head_add_js('library/sprintf.js/dist/sprintf.min.js'); //head_add_js('jquery-compat.js');