diff --git a/HISTORY.rst b/HISTORY.rst
index c738f495c2..a8ef5cf614 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -38,6 +38,8 @@ Next (2018-XX-XX)
- Display resolution settings are now specified as width and height and not a single resolution value.
- Printed pages are now full width.
- Move the invalid document markup to a separate HTML template.
+- Update to Fancybox 3.
+- Update to jQuery 3.3.1
2.8 (2018-02-27)
================
diff --git a/mayan/apps/appearance/static/appearance/js/mayan_image.js b/mayan/apps/appearance/static/appearance/js/mayan_image.js
index a3d7c47108..72c5527fe1 100644
--- a/mayan/apps/appearance/static/appearance/js/mayan_image.js
+++ b/mayan/apps/appearance/static/appearance/js/mayan_image.js
@@ -6,17 +6,13 @@ var MayanImage = function (options) {
}
MayanImage.intialize = function () {
- $('a.fancybox').fancybox({
- beforeShow : function(){
- this.title = $(this.element).data('caption');
- },
- openEffect : 'elastic',
- closeEffect : 'elastic',
- prevEffect : 'none',
- nextEffect : 'none',
- titleShow : true,
- type : 'image',
- autoResize : true,
+ this.fancybox = $().fancybox({
+ animationDuration : 400,
+ buttons : [
+ 'fullScreen',
+ 'close'
+ ],
+ selector: 'a.fancybox',
});
$('img.lazy-load').lazyload({
@@ -31,7 +27,7 @@ MayanImage.intialize = function () {
new MayanImage({element: $(this)});
},
container: $('#carousel-container'),
- threshold: 2000
+ threshold: 2000,
});
$('.lazy-load').on('load', function() {
@@ -51,27 +47,14 @@ MayanImage.intialize = function () {
MayanImage.templateInvalidDocument = $('#template-invalid-document').html();
-MayanImage.prototype.onImageError = function () {
- this.element.parent().parent().html(MayanImage.templateInvalidDocument);
-
- // Remove border to indicate non interactive image
- this.element.removeClass('thin_border');
-
- var container = this.element.parent().parent();
- // Save img HTML
- var html = this.element.parent().html();
- // Remove anchor
- this.element.parent().remove();
- // Place again img
- container.html(html);
-};
MayanImage.prototype.load = function () {
var self = this;
+ var container = this.element.parent().parent().parent();
- this.element.error(function(event) {
- self.onImageError();
- });
+ this.element.on('error', (function(event) {
+ container.html(MayanImage.templateInvalidDocument);
+ }));
this.element.attr('src', this.element.attr('data-url'));
$.fn.matchHeight._update();
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/.gitattributes b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/.gitattributes
deleted file mode 100644
index f6bb280a0b..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/.gitattributes
+++ /dev/null
@@ -1,7 +0,0 @@
-# Auto detect text files and perform LF normalization
-* text=auto
-
-# Denote all files that are truly binary and should not be modified.
-*.png binary
-*.jpg binary
-*.gif binary
\ No newline at end of file
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/CHANGELOG.md b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/CHANGELOG.md
deleted file mode 100644
index b0d847e256..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/CHANGELOG.md
+++ /dev/null
@@ -1,125 +0,0 @@
-fancyBox - Changelog
-=========
-
-### Version 2.1.5 - June 14, 2013
-* Fixed #493 - Broken slideshow
-* Fixed #556 - Parent option
-* Retina graphics (#564) and retina display support (#420)
-* Improved "lock" feature
-
-### Version 2.1.4 - January 10, 2013
-* Update to be compatible with jQuery v1.9
-* Small changes that should fix usability issues for certain users
-
-### Version 2.1.3 - October 23, 2012
-
-* Fixed #426 - Broken IE7
-* Fixed #423 - Background flickering on iOS
-* Fixed #418 - Automatically Grow/Shrink and Center
-* Updated the script to work with jQuery 1.6
-* Media helper supports YouTube video series
-
-### Version 2.1.2 - October 15, 2012
-
-* Fixed #414 - Don't allow nextClick if there is only one item
-* Fixed #397 - Button helper 'Menu' not visible in IE7
-* Overlay can be opened/closed manually:
-* $.fancybox.helpers.overlay.open();
-* $.fancybox.helpers.overlay.open({closeClick : false});
-* $.fancybox.helpers.overlay.close();
-* Optimized for Internet Explorer 10 (Windows 8)
-
-### Version 2.1.1 - October 01, 2012
-
-* Fixed #357 - Converting values like 'auto' in getScalar()
-* Fixed #358 - Updated overlay background image
-* New "fancybox-href" and "fancybox-title" HTML5 data-attributes (#317)
-* Improved helpers:
-* - now they can have a property 'defaults' that contains default settings
-* - updated vimeo and youtube parsers for media helper
-* Content locking now can be turned off
-
-### Version 2.1.0 - August 20, 2012
-
-* Fixed #103 - DOM element re-injection after closing
-* Fixed #188 - navigation keys inside editable content
-* New animation directions (see https://github.com/fancyapps/fancyBox/issues/233#issuecomment-5512453)
-* New option "iframe" - it is now possible to separate scrolling for iframe and wrapping element; choose to preload
-* New option "swf" - brings back functionality from fancyBox v1
-* Improved media helper - better support for vimeo and youtube; links are now configurable
-* Rewritten overlay helper:
-* - new option "showEarly" - toggles if should be open before of after content is loaded
-* - Facebook-style (https://github.com/fancyapps/fancyBox/issues/24) and therefore uses image for background
-* Option "padding" accepts array (e.g., padding: [15, 50, 10, 5])
-* One of dimensions (width or height) can now be set to "auto" (option "autoSize" needs to be "false")
-* Updated callbacks:
-* - "beforeClose" is now called only once
-* - "afterLoad" receives current and previous object as arguments
-* Method "$.fancybox.update();" recalculates content width/height
-* Updated to work with jQuery v1.8
-
-### Version 2.0.6 - April 16, 2012
-
-* Fixed #188 - keystrokes in contenteditable
-* Fixed #171 - non-images should not be preloaded
-* Fixed #158 - 'closeClick: true' breaks gallery navigation
-* New "media" helper - detects and displays various media types
-* New option "groupAttr" - name of group selector attribute, default is "data-fancybox-group"
-* New feature - selector expressions in URLs, see #170
-* Improved 'overlay' helper to use "position: fixed"
-* Improved autoSize, fixed wrong height in some cases
-* Improved centering and iframe scrolling for iOS
-* Updated markup, new element '.fancybox-skin' is now used for styling
-
-### Version 2.0.5 - February 21, 2012
-
-* Fixed #155 - easing for prev/next animations
-* Fixed #153 - overriding "keys" options
-* Fixed #147 - IE7 problem with #hash links
-* Fixed #130 - changing dynamically data-fancybox-group
-* Fixed #126 - obey minWidth/minHeight
-* Fixed #118 - placement of loading icon and navigation arrows
-* Fixed #101 - "index" option not working
-* Fixed #94 - "orig" option not working
-* Fixed #80 - does not work on IE6
-* Fixed #72 - can't set overlay opacity to 0
-* Fixed #63 - properly set gallery index
-* New option "autoCenter" - toggles centering on window resize or scroll, disabled for mobile devices by default
-* New option "autoResize" - toggles responsivity, disabled for mobile devices by default
-* New option "preload" - number of images to preload
-* New feature to target mobile/desktop browsers using CSS, see #108
-* Changed ajax option defaults to "{ dataType: 'html', headers: { 'X-fancyBox': true } }", see #150 and #128
-* Updated loading icon for IE7, IE8
-* Calculates height of the iframe if 'autoSize' is set to 'true' and the iframe is on the same domain as the main page
-
-### Version 2.0.4 - December 12, 2011
-
-* Fixed #47 - fix overriding properties
-* New option "position" to thumbnail and button helpers
-
-
-### Version 2.0.3 - November 29, 2011
-
-* Fixed #29 - broken elastic transitions
-
-
-### Version 2.0.2 - November 28, 2011
-
-* Fixed slideshow
-* Fixed scrollbars issue when displayed a very tall image
-* New option "nextClick" - navigate to next gallery item when user clicks the content
-* New option "modal" - to disable navigation and closing
-* Add 'metadata' plugin support
-* Add ability to create groups using 'data-fancybox-group' attribute
-* Updated manual usage to match earlier releases
-
-
-### Version 2.0.1 - November 23, 2011
-
-* Fixed keyboard events inside form elements
-* Fixed manual usage
-
-
-### Version 2.0.0 - November 21, 2011
-
-First release - completely rewritten, many new features and updated graphics.
\ No newline at end of file
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/README.md b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/README.md
deleted file mode 100644
index 943489378b..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/README.md
+++ /dev/null
@@ -1,217 +0,0 @@
-fancyBox
-========
-
-fancyBox is a tool that offers a nice and elegant way to add zooming functionality for images, html content and multi-media on your webpages.
-
-More information and examples: http://www.fancyapps.com/fancybox/
-
-License: http://www.fancyapps.com/fancybox/#license
-
-Copyright (c) 2012 Janis Skarnelis - janis@fancyapps.com
-
-
-How to use
-----------
-
-To get started, download the plugin, unzip it and copy files to your website/application directory.
-Load files in the
section of your HTML document. Make sure you also add the jQuery library.
-
-
-
-
-
-
-
-Create your links with a `title` if you want a title to be shown, and add a class:
-
-
-
-If you have a set of related items that you would like to group,
-additionally include a group name in the `rel` (or `data-fancybox-group`) attribute:
-
-
-
-
-Initialise the script like this:
-
-
-
-May also be passed an optional options object which will extend the default values. Example:
-
-
-
-Tip: Automatically group and apply fancyBox to all images:
-
- $("a[href$='.jpg'],a[href$='.jpeg'],a[href$='.png'],a[href$='.gif']").attr('rel', 'gallery').fancybox();
-
-Script uses the `href` attribute of the matched elements to obtain the location of the content and to figure out content type you want to display.
-You can specify type directly by adding classname (fancybox.image, fancybox.iframe, etc) or `data-fancybox-type` attribute:
-
- //Ajax:
- Example
- //or
- Example
-
- //Iframe:
- Example
-
- //Inline (will display an element with `id="example"`)
- Example
-
- //SWF:
- Example
-
- //Image:
- Example
-
-Note, ajax requests are subject to the [same origin policy](http://en.wikipedia.org/wiki/Same_origin_policy).
-If fancyBox will not be able to get content type, it will try to guess based on 'href' and will quit silently if would not succeed.
-(this is different from previsous versions where 'ajax' was used as default type or an error message was displayed).
-
-Advanced
---------
-
-### Helpers
-
-Helpers provide a simple mechanism to extend the capabilities of fancyBox. There are two built-in helpers - 'overlay' and 'title'.
-You can disable them, set custom options or enable other helpers. Examples:
-
- //Disable title helper
- $(".fancybox").fancybox({
- helpers: {
- title: null
- }
- });
-
- //Disable overlay helper
- $(".fancybox").fancybox({
- helpers: {
- overlay : null
- }
- });
-
- //Change title position and overlay color
- $(".fancybox").fancybox({
- helpers: {
- title : {
- type : 'inside'
- },
- overlay : {
- css : {
- 'background' : 'rgba(255,255,255,0.5)'
- }
- }
- }
- });
-
- //Enable thumbnail helper and set custom options
- $(".fancybox").fancybox({
- helpers: {
- thumbs : {
- width: 50,
- height: 50
- }
- }
- });
-
-
-### API
-
-Also available are event driven callback methods. The `this` keyword refers to the current or upcoming object (depends on callback method). Here is how you can change title:
-
- $(".fancybox").fancybox({
- beforeLoad : function() {
- this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
-
- /*
- "this.element" refers to current element, so you can, for example, use the "alt" attribute of the image to store the title:
- this.title = $(this.element).find('img').attr('alt');
- */
- }
- });
-
-It`s possible to open fancyBox programmatically in various ways:
-
- //HTML content:
- $.fancybox( '
Lorem Lipsum
Lorem lipsum
', {
- title : 'Custom Title'
- });
-
- //DOM element:
- $.fancybox( $("#inline"), {
- title : 'Custom Title'
- });
-
- //Custom object:
- $.fancybox({
- href: 'example.jpg',
- title : 'Custom Title'
- });
-
- //Array of objects:
- $.fancybox([
- {
- href: 'example1.jpg',
- title : 'Custom Title 1'
- },
- {
- href: 'example2.jpg',
- title : 'Custom Title 2'
- }
- ], {
- padding: 0
- });
-
-There are several methods that allow you to interact with and manipulate fancyBox, example:
-
- //Close fancybox:
- $.fancybox.close();
-
-There is a simply way to access wrapping elements using JS:
-
- $.fancybox.wrap
- $.fancybox.skin
- $.fancybox.outer
- $.fancybox.inner
-
-You can override CSS to customize the look. For example, make navigation arrows always visible,
-change width and move them outside of area (use this snippet after including fancybox.css):
-
- .fancybox-nav span {
- visibility: visible;
- }
-
- .fancybox-nav {
- width: 80px;
- }
-
- .fancybox-prev {
- left: -80px;
- }
-
- .fancybox-next {
- right: -80px;
- }
-
-In that case, you might want to increase space around box:
-
- $(".fancybox").fancybox({
- margin : [20, 60, 20, 60]
- });
-
-
-Bug tracker
------------
-
-Have a bug? Please create an issue on GitHub at https://github.com/fancyapps/fancyBox/issues
\ No newline at end of file
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/bower.json b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/bower.json
deleted file mode 100644
index 42527cdc73..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/bower.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "name": "fancybox",
- "version": "2.1.5",
- "main": [
- "source/jquery.fancybox.css",
- "source/jquery.fancybox.js",
- "source/blank.gif",
- "source/fancybox_loading.gif",
- "source/fancybox_loading@2x.gif",
- "source/fancybox_overlay.png",
- "source/fancybox_sprite.png",
- "source/fancybox_sprite@2x.png",
- "source/jquery.fancybox.pack.js"
- ],
- "ignore": [
- "**/.*",
- "fancybox.jquery.json",
- "demo"
- ],
- "dependencies": {
- "jquery": ">=1.10",
- "jquery-mousewheel": "~3.1.3"
- }
-}
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_b.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_b.jpg
deleted file mode 100644
index 0f662e341e..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_b.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_s.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_s.jpg
deleted file mode 100644
index ef0bd550d7..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/1_s.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_b.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_b.jpg
deleted file mode 100644
index 977cb6a99d..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_b.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_s.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_s.jpg
deleted file mode 100644
index 258cbcb657..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/2_s.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_b.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_b.jpg
deleted file mode 100644
index e007a51c3e..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_b.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_s.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_s.jpg
deleted file mode 100644
index 7206f24bbd..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/3_s.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_b.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_b.jpg
deleted file mode 100644
index a3a12a66f4..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_b.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_s.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_s.jpg
deleted file mode 100644
index b9ea423224..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/4_s.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_b.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_b.jpg
deleted file mode 100644
index ea1cfbc3fa..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_b.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_s.jpg b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_s.jpg
deleted file mode 100644
index 989975d11b..0000000000
Binary files a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/5_s.jpg and /dev/null differ
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/ajax.txt b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/ajax.txt
deleted file mode 100644
index 6f9788c843..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/ajax.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas fermentum ante et sapien dignissim in viverra magna feugiat. Donec tempus ipsum nec neque dignissim quis eleifend eros gravida. Praesent nisi massa, sodales quis tincidunt ac, semper quis risus. In suscipit nisl sed leo aliquet consequat. Integer vitae augue in risus porttitor pellentesque eu eget odio. Fusce ut sagittis quam. Morbi aliquam interdum blandit. Integer pharetra tempor velit, aliquam dictum justo tempus sed. Morbi congue fringilla justo a feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent quis metus et nisl consectetur pharetra. Nam bibendum turpis eu metus luctus eu volutpat sem molestie. Nam sollicitudin porttitor lorem, ac ultricies est venenatis eu. Ut dignissim elit et orci feugiat ac placerat purus euismod. Ut mi lorem, cursus et sagittis elementum, luctus ac massa.
-
-
- Phasellus et ligula vel diam ullamcorper volutpat. Integer rhoncus rhoncus aliquam. Aliquam erat volutpat. Aenean luctus vestibulum placerat. Quisque quam neque, lacinia aliquet eleifend ac, aliquet blandit felis. Curabitur porta ultricies dui, sit amet mattis quam euismod a. Ut eleifend scelerisque neque, sit amet accumsan odio consequat ut. Proin facilisis auctor elit sed accumsan. Cras dapibus nisl in nisi rhoncus laoreet. Nullam pellentesque tortor libero, eget facilisis ipsum. Donec ultricies tellus tellus, in tincidunt purus. Nullam in est aliquam velit scelerisque blandit. In tincidunt, magna a dapibus imperdiet, quam urna elementum leo, vitae rhoncus nisl velit cursus velit. In dignissim sem ac mauris rhoncus ornare.
-
-
- Duis imperdiet velit vel quam malesuada suscipit imperdiet tellus hendrerit. Mauris vestibulum odio mauris, ut placerat leo. Mauris quis neque at tellus feugiat congue id non enim. Nam vehicula posuere nulla eget vehicula. Donec pretium purus nec ligula porta eu laoreet sapien venenatis. Nulla facilisi. Phasellus eget mi enim. Phasellus molestie tincidunt ultrices. Aenean id sem a tellus lobortis tincidunt. Nam laoreet nulla vel velit tincidunt ac rutrum libero malesuada. Nulla consequat dolor quis nisl tempor fermentum. Integer sodales pretium varius. Aenean a leo vitae odio dictum dignissim malesuada nec dolor. Phasellus adipiscing viverra est, ac sagittis libero sagittis quis. Sed interdum dapibus nunc et fringilla. Nunc vel velit et urna laoreet bibendum.
-
-
\ No newline at end of file
diff --git a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/iframe.html b/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/iframe.html
deleted file mode 100644
index b586e15250..0000000000
--- a/mayan/apps/appearance/static/appearance/packages/fancyBox-master/demo/iframe.html
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- fancyBox - iframe demo
-
-
-
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam scelerisque justo ac eros consectetur bibendum. In hac habitasse platea dictumst. Nulla aliquam turpis et tellus elementum luctus. Duis sit amet rhoncus velit. Duis nisl ligula, mattis interdum blandit laoreet, mattis id ante. Cras pulvinar lacus vitae nisi egestas non euismod neque bibendum. Vestibulum faucibus libero id ante molestie ultricies. Vestibulum quis nibh felis. Vestibulum libero nisl, vehicula vel ullamcorper sit amet, tristique sit amet augue. Etiam urna neque, porttitor sed sodales lacinia, posuere a nisl. Vestibulum blandit neque in sapien volutpat ac condimentum sapien auctor. Ut imperdiet venenatis ultricies. Phasellus accumsan, sem eu placerat commodo, felis purus commodo ipsum, sit amet vulputate orci est viverra est.
-
-
-
- Aenean velit est, condimentum ut iaculis ut, accumsan at mi. Maecenas velit mi, venenatis ut condimentum at, ultrices vel tortor. Curabitur pharetra ornare dapibus. Ut volutpat cursus semper. In hac habitasse platea dictumst. Donec eu iaculis ipsum. Morbi eu dolor velit, a semper nunc.
-
- fancyBox will try to guess content type from href attribute but you can specify it directly by adding classname (fancybox.image, fancybox.iframe, etc).
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam quis mi eu elit tempor facilisis id et neque. Nulla sit amet sem sapien. Vestibulum imperdiet porta ante ac ornare. Nulla et lorem eu nibh adipiscing ultricies nec at lacus. Cras laoreet ultricies sem, at blandit mi eleifend aliquam. Nunc enim ipsum, vehicula non pretium varius, cursus ac tortor. Vivamus fringilla congue laoreet. Quisque ultrices sodales orci, quis rhoncus justo auctor in. Phasellus dui eros, bibendum eu feugiat ornare, faucibus eu mi. Nunc aliquet tempus sem, id aliquam diam varius ac. Maecenas nisl nunc, molestie vitae eleifend vel, iaculis sed magna. Aenean tempus lacus vitae orci posuere porttitor eget non felis. Donec lectus elit, aliquam nec eleifend sit amet, vestibulum sed nunc.
-
-
-
-
- Ajax example will not run from your local computer and requires a server to run.
-
-
-
Button helper
-
-
-
-
-
-
-
-
-
-
-
Thumbnail helper
-
-
-
-
-
-
-
-
-
-
-
Media helper
-
- Will not run from your local computer, requires a server to run.
-
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
',
+
+ btnTpl : {
+
+ download : '' +
+ '' +
+ '',
+
+ zoom : '',
+
+ close : '',
+
+ // This small close button will be appended to your html/inline/ajax content by default,
+ // if "smallBtn" option is not set to false
+ smallBtn : '',
+
+ // Arrows
+ arrowLeft : '',
+
+ arrowRight : ''
+ },
+
+ // Container is injected into this element
+ parentEl : 'body',
+
+
+ // Focus handling
+ // ==============
+
+ // Try to focus on the first focusable element after opening
+ autoFocus : false,
+
+ // Put focus back to active element after closing
+ backFocus : true,
+
+ // Do not let user to focus on element outside modal content
+ trapFocus : true,
+
+
+ // Module specific options
+ // =======================
+
+ fullScreen : {
+ autoStart : false,
+ },
+
+ // Set `touch: false` to disable dragging/swiping
+ touch : {
+ vertical : true, // Allow to drag content vertically
+ momentum : true // Continue movement after releasing mouse/touch when panning
+ },
+
+ // Hash value when initializing manually,
+ // set `false` to disable hash change
+ hash : null,
+
+ // Customize or add new media types
+ // Example:
+ /*
+ media : {
+ youtube : {
+ params : {
+ autoplay : 0
+ }
+ }
+ }
+ */
+ media : {},
+
+ slideShow : {
+ autoStart : false,
+ speed : 4000
+ },
+
+ thumbs : {
+ autoStart : false, // Display thumbnails on opening
+ hideOnClose : true, // Hide thumbnail grid when closing animation starts
+ parentEl : '.fancybox-container', // Container is injected into this element
+ axis : 'y' // Vertical (y) or horizontal (x) scrolling
+ },
+
+ // Use mousewheel to navigate gallery
+ // If 'auto' - enabled for images only
+ wheel : 'auto',
+
+ // Callbacks
+ //==========
+
+ // See Documentation/API/Events for more information
+ // Example:
+ /*
+ afterShow: function( instance, current ) {
+ console.info( 'Clicked element:' );
+ console.info( current.opts.$orig );
+ }
+ */
+
+ onInit : $.noop, // When instance has been initialized
+
+ beforeLoad : $.noop, // Before the content of a slide is being loaded
+ afterLoad : $.noop, // When the content of a slide is done loading
+
+ beforeShow : $.noop, // Before open animation starts
+ afterShow : $.noop, // When content is done loading and animating
+
+ beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close.
+ afterClose : $.noop, // After instance has been closed
+
+ onActivate : $.noop, // When instance is brought to front
+ onDeactivate : $.noop, // When other instance has been activated
+
+
+ // Interaction
+ // ===========
+
+ // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
+ // each option can be string or method that returns value.
+ //
+ // Possible values:
+ // "close" - close instance
+ // "next" - move to next gallery item
+ // "nextOrClose" - move to next gallery item or close if gallery has only one item
+ // "toggleControls" - show/hide controls
+ // "zoom" - zoom image (if loaded)
+ // false - do nothing
+
+ // Clicked on the content
+ clickContent : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ },
+
+ // Clicked on the slide
+ clickSlide : 'close',
+
+ // Clicked on the background (backdrop) element
+ clickOutside : 'close',
+
+ // Same as previous two, but for double click
+ dblclickContent : false,
+ dblclickSlide : false,
+ dblclickOutside : false,
+
+
+ // Custom options when mobile device is detected
+ // =============================================
+
+ mobile : {
+ idleTime : false,
+ margin : 0,
+
+ clickContent : function( current, event ) {
+ return current.type === 'image' ? 'toggleControls' : false;
+ },
+ clickSlide : function( current, event ) {
+ return current.type === 'image' ? 'toggleControls' : 'close';
+ },
+ dblclickContent : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ },
+ dblclickSlide : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ }
+ },
+
+
+ // Internationalization
+ // ============
+
+ lang : 'en',
+ i18n : {
+ 'en' : {
+ CLOSE : 'Close',
+ NEXT : 'Next',
+ PREV : 'Previous',
+ ERROR : 'The requested content cannot be loaded. Please try again later.',
+ PLAY_START : 'Start slideshow',
+ PLAY_STOP : 'Pause slideshow',
+ FULL_SCREEN : 'Full screen',
+ THUMBS : 'Thumbnails',
+ DOWNLOAD : 'Download',
+ SHARE : 'Share',
+ ZOOM : 'Zoom'
+ },
+ 'de' : {
+ CLOSE : 'Schliessen',
+ NEXT : 'Weiter',
+ PREV : 'Zurück',
+ ERROR : 'Die angeforderten Daten konnten nicht geladen werden. Bitte versuchen Sie es später nochmal.',
+ PLAY_START : 'Diaschau starten',
+ PLAY_STOP : 'Diaschau beenden',
+ FULL_SCREEN : 'Vollbild',
+ THUMBS : 'Vorschaubilder',
+ DOWNLOAD : 'Herunterladen',
+ SHARE : 'Teilen',
+ ZOOM : 'Maßstab'
+ }
+ }
+
+ };
+
+ // Few useful variables and methods
+ // ================================
+
+ var $W = $(window);
+ var $D = $(document);
+
+ var called = 0;
+
+
+ // Check if an object is a jQuery object and not a native JavaScript object
+ // ========================================================================
+
+ var isQuery = function ( obj ) {
+ return obj && obj.hasOwnProperty && obj instanceof $;
+ };
+
+
+ // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
+ // ===============================================================================
+
+ var requestAFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ // if all else fails, use setTimeout
+ function (callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+
+
+ // Detect the supported transition-end event property name
+ // =======================================================
+
+ var transitionEnd = (function () {
+ var t, el = document.createElement("fakeelement");
+
+ var transitions = {
+ "transition" : "transitionend",
+ "OTransition" : "oTransitionEnd",
+ "MozTransition" : "transitionend",
+ "WebkitTransition": "webkitTransitionEnd"
+ };
+
+ for (t in transitions) {
+ if (el.style[t] !== undefined){
+ return transitions[t];
+ }
+ }
+
+ return 'transitionend';
+ })();
+
+
+ // Force redraw on an element.
+ // This helps in cases where the browser doesn't redraw an updated element properly.
+ // =================================================================================
+
+ var forceRedraw = function( $el ) {
+ return ( $el && $el.length && $el[0].offsetHeight );
+ };
+
+
+ // Class definition
+ // ================
+
+ var FancyBox = function( content, opts, index ) {
+ var self = this;
+
+ self.opts = $.extend( true, { index : index }, $.fancybox.defaults, opts || {} );
+
+ if ( $.fancybox.isMobile ) {
+ self.opts = $.extend( true, {}, self.opts, self.opts.mobile );
+ }
+
+ // Exclude buttons option from deep merging
+ if ( opts && $.isArray( opts.buttons ) ) {
+ self.opts.buttons = opts.buttons;
+ }
+
+ self.id = self.opts.id || ++called;
+ self.group = [];
+
+ self.currIndex = parseInt( self.opts.index, 10 ) || 0;
+ self.prevIndex = null;
+
+ self.prevPos = null;
+ self.currPos = 0;
+
+ self.firstRun = null;
+
+ // Create group elements from original item collection
+ self.createGroup( content );
+
+ if ( !self.group.length ) {
+ return;
+ }
+
+ // Save last active element and current scroll position
+ self.$lastFocus = $(document.activeElement).blur();
+
+ // Collection of gallery objects
+ self.slides = {};
+
+ self.init();
+ };
+
+ $.extend(FancyBox.prototype, {
+
+ // Create DOM structure
+ // ====================
+
+ init : function() {
+ var self = this,
+ firstItem = self.group[ self.currIndex ],
+ firstItemOpts = firstItem.opts,
+ scrollbarWidth = $.fancybox.scrollbarWidth,
+ $scrollDiv,
+ $container,
+ buttonStr;
+
+ self.scrollTop = $D.scrollTop();
+ self.scrollLeft = $D.scrollLeft();
+
+
+ // Hide scrollbars
+ // ===============
+
+ if ( !$.fancybox.getInstance() ) {
+
+ $( 'body' ).addClass( 'fancybox-active' );
+
+ // iOS hack
+ if ( /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream ) {
+
+ // iOS has problems for input elements inside fixed containers,
+ // the workaround is to apply `position: fixed` to `` element,
+ // unfortunately, this makes it lose the scrollbars and forces address bar to appear.
+
+ if ( firstItem.type !== 'image' ) {
+ $( 'body' ).css( 'top', $( 'body' ).scrollTop() * -1 ).addClass( 'fancybox-iosfix' );
+ }
+
+ } else if ( !$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight ) {
+
+ if ( scrollbarWidth === undefined ) {
+ $scrollDiv = $('').appendTo( 'body' );
+
+ scrollbarWidth = $.fancybox.scrollbarWidth = $scrollDiv[0].offsetWidth - $scrollDiv[0].clientWidth;
+
+ $scrollDiv.remove();
+ }
+
+ $( 'head' ).append( '' );
+ $( 'body' ).addClass( 'compensate-for-scrollbar' );
+ }
+ }
+
+
+ // Build html markup and set references
+ // ====================================
+
+ // Build html code for buttons and insert into main template
+ buttonStr = '';
+
+ $.each( firstItemOpts.buttons, function( index, value ) {
+ buttonStr += ( firstItemOpts.btnTpl[ value ] || '' );
+ });
+
+ // Create markup from base template, it will be initially hidden to
+ // avoid unnecessary work like painting while initializing is not complete
+ $container = $(
+ self.translate( self,
+ firstItemOpts.baseTpl
+ .replace( '\{\{buttons\}\}', buttonStr )
+ .replace( '\{\{arrows\}\}', firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight )
+ )
+ )
+ .attr( 'id', 'fancybox-container-' + self.id )
+ .addClass( 'fancybox-is-hidden' )
+ .addClass( firstItemOpts.baseClass )
+ .data( 'FancyBox', self )
+ .appendTo( firstItemOpts.parentEl );
+
+ // Create object holding references to jQuery wrapped nodes
+ self.$refs = {
+ container : $container
+ };
+
+ [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption', 'navigation' ].forEach(function(item) {
+ self.$refs[ item ] = $container.find( '.fancybox-' + item );
+ });
+
+ self.trigger( 'onInit' );
+
+ // Enable events, deactive previous instances
+ self.activate();
+
+ // Build slides, load and reveal content
+ self.jumpTo( self.currIndex );
+ },
+
+
+ // Simple i18n support - replaces object keys found in template
+ // with corresponding values
+ // ============================================================
+
+ translate : function( obj, str ) {
+ var arr = obj.opts.i18n[ obj.opts.lang ];
+
+ return str.replace(/\{\{(\w+)\}\}/g, function(match, n) {
+ var value = arr[n];
+
+ if ( value === undefined ) {
+ return match;
+ }
+
+ return value;
+ });
+ },
+
+ // Create array of gally item objects
+ // Check if each object has valid type and content
+ // ===============================================
+
+ createGroup : function ( content ) {
+ var self = this;
+ var items = $.makeArray( content );
+
+ $.each(items, function( i, item ) {
+ var obj = {},
+ opts = {},
+ $item,
+ type,
+ found,
+ src,
+ srcParts;
+
+ // Step 1 - Make sure we have an object
+ // ====================================
+
+ if ( $.isPlainObject( item ) ) {
+
+ // We probably have manual usage here, something like
+ // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
+
+ obj = item;
+ opts = item.opts || item;
+
+ } else if ( $.type( item ) === 'object' && $( item ).length ) {
+
+ // Here we probably have jQuery collection returned by some selector
+ $item = $( item );
+
+ opts = $item.data();
+ opts = $.extend( {}, opts, opts.options || {} );
+
+ // Here we store clicked element
+ opts.$orig = $item;
+
+ obj.src = opts.src || $item.attr( 'href' );
+
+ // Assume that simple syntax is used, for example:
+ // `$.fancybox.open( $("#test"), {} );`
+ if ( !obj.type && !obj.src ) {
+ obj.type = 'inline';
+ obj.src = item;
+ }
+
+ } else {
+
+ // Assume we have a simple html code, for example:
+ // $.fancybox.open( '
Hi!
' );
+
+ obj = {
+ type : 'html',
+ src : item + ''
+ };
+
+ }
+
+ // Each gallery object has full collection of options
+ obj.opts = $.extend( true, {}, self.opts, opts );
+
+ // Do not merge buttons array
+ if ( $.isArray( opts.buttons ) ) {
+ obj.opts.buttons = opts.buttons;
+ }
+
+
+ // Step 2 - Make sure we have content type, if not - try to guess
+ // ==============================================================
+
+ type = obj.type || obj.opts.type;
+ src = obj.src || '';
+
+ if ( !type && src ) {
+ if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) {
+ type = 'image';
+
+ } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) {
+ type = 'pdf';
+
+ } else if ( found = src.match(/\.(mp4|mov|ogv)((\?|#).*)?$/i) ) {
+ type = 'video';
+
+ if ( !obj.opts.videoFormat ) {
+ obj.opts.videoFormat = 'video/' + ( found[1] === 'ogv' ? 'ogg' : found[1] );
+ }
+
+ } else if ( src.charAt(0) === '#' ) {
+ type = 'inline';
+ }
+ }
+
+ if ( type ) {
+ obj.type = type;
+
+ } else {
+ self.trigger( 'objectNeedsType', obj );
+ }
+
+
+ // Step 3 - Some adjustments
+ // =========================
+
+ obj.index = self.group.length;
+
+ // Check if $orig and $thumb objects exist
+ if ( obj.opts.$orig && !obj.opts.$orig.length ) {
+ delete obj.opts.$orig;
+ }
+
+ if ( !obj.opts.$thumb && obj.opts.$orig ) {
+ obj.opts.$thumb = obj.opts.$orig.find( 'img:first' );
+ }
+
+ if ( obj.opts.$thumb && !obj.opts.$thumb.length ) {
+ delete obj.opts.$thumb;
+ }
+
+ // "caption" is a "special" option, it can be used to customize caption per gallery item ..
+ if ( $.type( obj.opts.caption ) === 'function' ) {
+ obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] );
+ }
+
+ if ( $.type( self.opts.caption ) === 'function' ) {
+ obj.opts.caption = self.opts.caption.apply( item, [ self, obj ] );
+ }
+
+ // Make sure we have caption as a string or jQuery object
+ if ( !( obj.opts.caption instanceof $ ) ) {
+ obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + '';
+ }
+
+ // Check if url contains "filter" used to filter the content
+ // Example: "ajax.html #something"
+ if ( type === 'ajax' ) {
+ srcParts = src.split(/\s+/, 2);
+
+ if ( srcParts.length > 1 ) {
+ obj.src = srcParts.shift();
+
+ obj.opts.filter = srcParts.shift();
+ }
+ }
+
+ if ( obj.opts.smallBtn == 'auto' ) {
+
+ if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) {
+ obj.opts.toolbar = false;
+ obj.opts.smallBtn = true;
+
+ } else {
+ obj.opts.smallBtn = false;
+ }
+
+ }
+
+ // If the type is "pdf", then simply load file into iframe
+ if ( type === 'pdf' ) {
+ obj.type = 'iframe';
+
+ obj.opts.iframe.preload = false;
+ }
+
+ // Hide all buttons and disable interactivity for modal items
+ if ( obj.opts.modal ) {
+
+ obj.opts = $.extend(true, obj.opts, {
+ // Remove buttons
+ infobar : 0,
+ toolbar : 0,
+
+ smallBtn : 0,
+
+ // Disable keyboard navigation
+ keyboard : 0,
+
+ // Disable some modules
+ slideShow : 0,
+ fullScreen : 0,
+ thumbs : 0,
+ touch : 0,
+
+ // Disable click event handlers
+ clickContent : false,
+ clickSlide : false,
+ clickOutside : false,
+ dblclickContent : false,
+ dblclickSlide : false,
+ dblclickOutside : false
+ });
+
+ }
+
+ // Step 4 - Add processed object to group
+ // ======================================
+
+ self.group.push( obj );
+
+ });
+
+ },
+
+
+ // Attach an event handler functions for:
+ // - navigation buttons
+ // - browser scrolling, resizing;
+ // - focusing
+ // - keyboard
+ // - detect idle
+ // ======================================
+
+ addEvents : function() {
+ var self = this;
+
+ self.removeEvents();
+
+ // Make navigation elements clickable
+ self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.close( e );
+
+ }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.previous();
+
+ }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.next();
+
+ }).on( 'click.fb', '[data-fancybox-zoom]', function(e) {
+ // Click handler for zoom button
+ self[ self.isScaledDown() ? 'scaleToActual' : 'scaleToFit' ]();
+ });
+
+
+ // Handle page scrolling and browser resizing
+ $W.on('orientationchange.fb resize.fb', function(e) {
+
+ if ( e && e.originalEvent && e.originalEvent.type === "resize" ) {
+
+ requestAFrame(function() {
+ self.update();
+ });
+
+ } else {
+
+ self.$refs.stage.hide();
+
+ setTimeout(function() {
+ self.$refs.stage.show();
+
+ self.update();
+ }, 600);
+
+ }
+
+ });
+
+ // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal
+ // (a.k.a. "escaping the modal")
+ $D.on('focusin.fb', function(e) {
+ var instance = $.fancybox ? $.fancybox.getInstance() : null;
+
+ if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) {
+ return;
+ }
+
+ if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) {
+ e.stopPropagation();
+
+ instance.focus();
+
+ // Sometimes page gets scrolled, set it back
+ $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft );
+ }
+ });
+
+
+ // Enable keyboard navigation
+ $D.on('keydown.fb', function (e) {
+ var current = self.current,
+ keycode = e.keyCode || e.which;
+
+ if ( !current || !current.opts.keyboard ) {
+ return;
+ }
+
+ if ( $(e.target).is('input') || $(e.target).is('textarea') ) {
+ return;
+ }
+
+ // Backspace and Esc keys
+ if ( keycode === 8 || keycode === 27 ) {
+ e.preventDefault();
+
+ self.close( e );
+
+ return;
+ }
+
+ // Left arrow and Up arrow
+ if ( keycode === 37 || keycode === 38 ) {
+ e.preventDefault();
+
+ self.previous();
+
+ return;
+ }
+
+ // Righ arrow and Down arrow
+ if ( keycode === 39 || keycode === 40 ) {
+ e.preventDefault();
+
+ self.next();
+
+ return;
+ }
+
+ self.trigger('afterKeydown', e, keycode);
+ });
+
+
+ // Hide controls after some inactivity period
+ if ( self.group[ self.currIndex ].opts.idleTime ) {
+ self.idleSecondsCounter = 0;
+
+ $D.on('mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function(e) {
+ self.idleSecondsCounter = 0;
+
+ if ( self.isIdle ) {
+ self.showControls();
+ }
+
+ self.isIdle = false;
+ });
+
+ self.idleInterval = window.setInterval(function() {
+ self.idleSecondsCounter++;
+
+ if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime && !self.isDragging ) {
+ self.isIdle = true;
+ self.idleSecondsCounter = 0;
+
+ self.hideControls();
+ }
+
+ }, 1000);
+ }
+
+ },
+
+
+ // Remove events added by the core
+ // ===============================
+
+ removeEvents : function() {
+ var self = this;
+
+ $W.off( 'orientationchange.fb resize.fb' );
+ $D.off( 'focusin.fb keydown.fb .fb-idle' );
+
+ this.$refs.container.off( '.fb-close .fb-prev .fb-next' );
+
+ if ( self.idleInterval ) {
+ window.clearInterval( self.idleInterval );
+
+ self.idleInterval = null;
+ }
+ },
+
+
+ // Change to previous gallery item
+ // ===============================
+
+ previous : function( duration ) {
+ return this.jumpTo( this.currPos - 1, duration );
+ },
+
+
+ // Change to next gallery item
+ // ===========================
+
+ next : function( duration ) {
+ return this.jumpTo( this.currPos + 1, duration );
+ },
+
+
+ // Switch to selected gallery item
+ // ===============================
+
+ jumpTo : function ( pos, duration, slide ) {
+ var self = this,
+ firstRun,
+ loop,
+ current,
+ previous,
+ canvasWidth,
+ currentPos,
+ transitionProps;
+
+ var groupLen = self.group.length;
+
+ if ( self.isDragging || self.isClosing || ( self.isAnimating && self.firstRun ) ) {
+ return;
+ }
+
+ pos = parseInt( pos, 10 );
+ loop = self.current ? self.current.opts.loop : self.opts.loop;
+
+ if ( !loop && ( pos < 0 || pos >= groupLen ) ) {
+ return false;
+ }
+
+ firstRun = self.firstRun = ( self.firstRun === null );
+
+ if ( groupLen < 2 && !firstRun && !!self.isDragging ) {
+ return;
+ }
+
+ previous = self.current;
+
+ self.prevIndex = self.currIndex;
+ self.prevPos = self.currPos;
+
+ // Create slides
+ current = self.createSlide( pos );
+
+ if ( groupLen > 1 ) {
+ if ( loop || current.index > 0 ) {
+ self.createSlide( pos - 1 );
+ }
+
+ if ( loop || current.index < groupLen - 1 ) {
+ self.createSlide( pos + 1 );
+ }
+ }
+
+ self.current = current;
+ self.currIndex = current.index;
+ self.currPos = current.pos;
+
+ self.trigger( 'beforeShow', firstRun );
+
+ self.updateControls();
+
+ currentPos = $.fancybox.getTranslate( current.$slide );
+
+ current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' );
+ current.forcedDuration = undefined;
+
+ if ( $.isNumeric( duration ) ) {
+ current.forcedDuration = duration;
+ } else {
+ duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ];
+ }
+
+ duration = parseInt( duration, 10 );
+
+ // Fresh start - reveal container, current slide and start loading content
+ if ( firstRun ) {
+
+ if ( current.opts.animationEffect && duration ) {
+ self.$refs.container.css( 'transition-duration', duration + 'ms' );
+ }
+
+ self.$refs.container.removeClass( 'fancybox-is-hidden' );
+
+ forceRedraw( self.$refs.container );
+
+ self.$refs.container.addClass( 'fancybox-is-open' );
+
+ // Make first slide visible (to display loading icon, if needed)
+ current.$slide.addClass( 'fancybox-slide--current' );
+
+ self.loadSlide( current );
+
+ self.preload( 'image' );
+
+ return;
+ }
+
+ // Clean up
+ $.each(self.slides, function( index, slide ) {
+ $.fancybox.stop( slide.$slide );
+ });
+
+ // Make current that slide is visible even if content is still loading
+ current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' );
+
+ // If slides have been dragged, animate them to correct position
+ if ( current.isMoved ) {
+ canvasWidth = Math.round( current.$slide.width() );
+
+ $.each(self.slides, function( index, slide ) {
+ var pos = slide.pos - current.pos;
+
+ $.fancybox.animate( slide.$slide, {
+ top : 0,
+ left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter )
+ }, duration, function() {
+
+ slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' );
+
+ if ( slide.pos === self.currPos ) {
+ current.isMoved = false;
+
+ self.complete();
+ }
+ });
+ });
+
+ } else {
+ self.$refs.stage.children().removeAttr( 'style' );
+ }
+
+ // Start transition that reveals current content
+ // or wait when it will be loaded
+
+ if ( current.isLoaded ) {
+ self.revealContent( current );
+
+ } else {
+ self.loadSlide( current );
+ }
+
+ self.preload( 'image' );
+
+ if ( previous.pos === current.pos ) {
+ return;
+ }
+
+ // Handle previous slide
+ // =====================
+
+ transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' );
+
+ previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' );
+
+ previous.isComplete = false;
+
+ if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) {
+ return;
+ }
+
+ if ( current.isMoved ) {
+ previous.$slide.addClass( transitionProps );
+
+ } else {
+
+ transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect;
+
+ $.fancybox.animate( previous.$slide, transitionProps, duration, function() {
+ previous.$slide.removeClass( transitionProps ).removeAttr( 'style' );
+ });
+
+ }
+
+ },
+
+
+ // Create new "slide" element
+ // These are gallery items that are actually added to DOM
+ // =======================================================
+
+ createSlide : function( pos ) {
+
+ var self = this;
+ var $slide;
+ var index;
+
+ index = pos % self.group.length;
+ index = index < 0 ? self.group.length + index : index;
+
+ if ( !self.slides[ pos ] && self.group[ index ] ) {
+ $slide = $('').appendTo( self.$refs.stage );
+
+ self.slides[ pos ] = $.extend( true, {}, self.group[ index ], {
+ pos : pos,
+ $slide : $slide,
+ isLoaded : false,
+ });
+
+ self.updateSlide( self.slides[ pos ] );
+ }
+
+ return self.slides[ pos ];
+ },
+
+
+ // Scale image to the actual size of the image
+ // ===========================================
+
+ scaleToActual : function( x, y, duration ) {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var imgPos, posX, posY, scaleX, scaleY;
+
+ var canvasWidth = parseInt( current.$slide.width(), 10 );
+ var canvasHeight = parseInt( current.$slide.height(), 10 );
+
+ var newImgWidth = current.width;
+ var newImgHeight = current.height;
+
+ if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) {
+ return;
+ }
+
+ $.fancybox.stop( $what );
+
+ self.isAnimating = true;
+
+ x = x === undefined ? canvasWidth * 0.5 : x;
+ y = y === undefined ? canvasHeight * 0.5 : y;
+
+ imgPos = $.fancybox.getTranslate( $what );
+
+ scaleX = newImgWidth / imgPos.width;
+ scaleY = newImgHeight / imgPos.height;
+
+ // Get center position for original image
+ posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 );
+ posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 );
+
+ // Make sure image does not move away from edges
+ if ( newImgWidth > canvasWidth ) {
+ posX = imgPos.left * scaleX - ( ( x * scaleX ) - x );
+
+ if ( posX > 0 ) {
+ posX = 0;
+ }
+
+ if ( posX < canvasWidth - newImgWidth ) {
+ posX = canvasWidth - newImgWidth;
+ }
+ }
+
+ if ( newImgHeight > canvasHeight) {
+ posY = imgPos.top * scaleY - ( ( y * scaleY ) - y );
+
+ if ( posY > 0 ) {
+ posY = 0;
+ }
+
+ if ( posY < canvasHeight - newImgHeight ) {
+ posY = canvasHeight - newImgHeight;
+ }
+ }
+
+ self.updateCursor( newImgWidth, newImgHeight );
+
+ $.fancybox.animate( $what, {
+ top : posY,
+ left : posX,
+ scaleX : scaleX,
+ scaleY : scaleY
+ }, duration || 330, function() {
+ self.isAnimating = false;
+ });
+
+ // Stop slideshow
+ if ( self.SlideShow && self.SlideShow.isActive ) {
+ self.SlideShow.stop();
+ }
+ },
+
+
+ // Scale image to fit inside parent element
+ // ========================================
+
+ scaleToFit : function( duration ) {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+ var end;
+
+ if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) {
+ return;
+ }
+
+ $.fancybox.stop( $what );
+
+ self.isAnimating = true;
+
+ end = self.getFitPos( current );
+
+ self.updateCursor( end.width, end.height );
+
+ $.fancybox.animate( $what, {
+ top : end.top,
+ left : end.left,
+ scaleX : end.width / $what.width(),
+ scaleY : end.height / $what.height()
+ }, duration || 330, function() {
+ self.isAnimating = false;
+ });
+
+ },
+
+ // Calculate image size to fit inside viewport
+ // ===========================================
+
+ getFitPos : function( slide ) {
+ var self = this;
+ var $what = slide.$content;
+
+ var imgWidth = slide.width;
+ var imgHeight = slide.height;
+
+ var margin = slide.opts.margin;
+
+ var canvasWidth, canvasHeight, minRatio, width, height;
+
+ if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) {
+ return false;
+ }
+
+ // Convert "margin to CSS style: [ top, right, bottom, left ]
+ if ( $.type( margin ) === "number" ) {
+ margin = [ margin, margin ];
+ }
+
+ if ( margin.length == 2 ) {
+ margin = [ margin[0], margin[1], margin[0], margin[1] ];
+ }
+
+ // We can not use $slide width here, because it can have different diemensions while in transiton
+ canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] );
+ canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] );
+
+ minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight );
+
+ width = Math.floor( minRatio * imgWidth );
+ height = Math.floor( minRatio * imgHeight );
+
+ // Use floor rounding to make sure it really fits
+ return {
+ top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ],
+ left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ],
+ width : width,
+ height : height
+ };
+
+ },
+
+
+ // Update content size and position for all slides
+ // ==============================================
+
+ update : function() {
+ var self = this;
+
+ $.each( self.slides, function( key, slide ) {
+ self.updateSlide( slide );
+ });
+ },
+
+
+ // Update slide content position and size
+ // ======================================
+
+ updateSlide : function( slide, duration ) {
+ var self = this,
+ $what = slide && slide.$content;
+
+ if ( $what && ( slide.width || slide.height ) ) {
+ self.isAnimating = false;
+
+ $.fancybox.stop( $what );
+
+ $.fancybox.setTranslate( $what, self.getFitPos( slide ) );
+
+ if ( slide.pos === self.currPos ) {
+ self.updateCursor();
+ }
+ }
+
+ slide.$slide.trigger( 'refresh' );
+
+ self.trigger( 'onUpdate', slide );
+
+ },
+
+
+ // Horizontally center slide
+ // =========================
+
+ centerSlide : function( slide, duration ) {
+ var self = this, canvasWidth, pos;
+
+ if ( self.current ) {
+ canvasWidth = Math.round( slide.$slide.width() );
+ pos = slide.pos - self.current.pos;
+
+ $.fancybox.animate( slide.$slide, {
+ top : 0,
+ left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ),
+ opacity : 1
+ }, duration === undefined ? 0 : duration, null, false);
+ }
+ },
+
+
+ // Update cursor style depending if content can be zoomed
+ // ======================================================
+
+ updateCursor : function( nextWidth, nextHeight ) {
+
+ var self = this;
+ var isScaledDown;
+
+ var $container = self.$refs.container.removeClass( 'fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut' );
+
+ if ( !self.current || self.isClosing ) {
+ return;
+ }
+
+ if ( self.isZoomable() ) {
+
+ $container.addClass( 'fancybox-is-zoomable' );
+
+ if ( nextWidth !== undefined && nextHeight !== undefined ) {
+ isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height;
+
+ } else {
+ isScaledDown = self.isScaledDown();
+ }
+
+ if ( isScaledDown ) {
+
+ // If image is scaled down, then, obviously, it can be zoomed to full size
+ $container.addClass( 'fancybox-can-zoomIn' );
+
+ } else {
+
+ if ( self.current.opts.touch ) {
+
+ // If image size ir largen than available available and touch module is not disable,
+ // then user can do panning
+ $container.addClass( 'fancybox-can-drag' );
+
+ } else {
+ $container.addClass( 'fancybox-can-zoomOut' );
+ }
+
+ }
+
+ } else if ( self.current.opts.touch ) {
+ $container.addClass( 'fancybox-can-drag' );
+ }
+
+ },
+
+
+ // Check if current slide is zoomable
+ // ==================================
+
+ isZoomable : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var fitPos;
+
+ if ( !current || self.isClosing ) {
+ return;
+ }
+
+ // Assume that slide is zoomable if
+ // - image is loaded successfuly
+ // - click action is "zoom"
+ // - actual size of the image is smaller than available area
+ if ( current.type === 'image' && current.isLoaded && !current.hasError &&
+ ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) )
+ ) {
+
+ fitPos = self.getFitPos( current );
+
+ if ( current.width > fitPos.width || current.height > fitPos.height ) {
+ return true;
+ }
+
+ }
+
+ return false;
+
+ },
+
+
+ // Check if current image dimensions are smaller than actual
+ // =========================================================
+
+ isScaledDown : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var rez = false;
+
+ if ( $what ) {
+ rez = $.fancybox.getTranslate( $what );
+ rez = rez.width < current.width || rez.height < current.height;
+ }
+
+ return rez;
+
+ },
+
+
+ // Check if image dimensions exceed parent element
+ // ===============================================
+
+ canPan : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var rez = false;
+
+ if ( $what ) {
+ rez = self.getFitPos( current );
+ rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1;
+ }
+
+ return rez;
+
+ },
+
+
+ // Load content into the slide
+ // ===========================
+
+ loadSlide : function( slide ) {
+
+ var self = this, type, $slide;
+ var ajaxLoad;
+
+ if ( slide.isLoading ) {
+ return;
+ }
+
+ if ( slide.isLoaded ) {
+ return;
+ }
+
+ slide.isLoading = true;
+
+ self.trigger( 'beforeLoad', slide );
+
+ type = slide.type;
+ $slide = slide.$slide;
+
+ $slide
+ .off( 'refresh' )
+ .trigger( 'onReset' )
+ .addClass( 'fancybox-slide--' + ( type || 'unknown' ) )
+ .addClass( slide.opts.slideClass );
+
+ // Create content depending on the type
+
+ switch ( type ) {
+
+ case 'image':
+
+ self.setImage( slide );
+
+ break;
+
+ case 'iframe':
+
+ self.setIframe( slide );
+
+ break;
+
+ case 'html':
+
+ self.setContent( slide, slide.src || slide.content );
+
+ break;
+
+ case 'inline':
+
+ if ( $( slide.src ).length ) {
+ self.setContent( slide, $( slide.src ) );
+
+ } else {
+ self.setError( slide );
+ }
+
+ break;
+
+ case 'ajax':
+
+ self.showLoading( slide );
+
+ ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, {
+ url : slide.src,
+ success : function ( data, textStatus ) {
+
+ if ( textStatus === 'success' ) {
+ self.setContent( slide, data );
+ }
+
+ },
+ error : function ( jqXHR, textStatus ) {
+
+ if ( jqXHR && textStatus !== 'abort' ) {
+ self.setError( slide );
+ }
+
+ }
+ }));
+
+ $slide.one( 'onReset', function () {
+ ajaxLoad.abort();
+ });
+
+ break;
+
+ case 'video' :
+
+ self.setContent( slide,
+ ''
+ );
+
+ break;
+
+ default:
+
+ self.setError( slide );
+
+ break;
+
+ }
+
+ return true;
+
+ },
+
+
+ // Use thumbnail image, if possible
+ // ================================
+
+ setImage : function( slide ) {
+
+ var self = this;
+ var srcset = slide.opts.srcset || slide.opts.image.srcset;
+
+ var found, temp, pxRatio, windowWidth;
+
+ // If we have "srcset", then we need to find matching "src" value.
+ // This is necessary, because when you set an src attribute, the browser will preload the image
+ // before any javascript or even CSS is applied.
+ if ( srcset ) {
+ pxRatio = window.devicePixelRatio || 1;
+ windowWidth = window.innerWidth * pxRatio;
+
+ temp = srcset.split(',').map(function ( el ) {
+ var ret = {};
+
+ el.trim().split(/\s+/).forEach(function ( el, i ) {
+ var value = parseInt( el.substring(0, el.length - 1), 10 );
+
+ if ( i === 0 ) {
+ return ( ret.url = el );
+ }
+
+ if ( value ) {
+ ret.value = value;
+ ret.postfix = el[ el.length - 1 ];
+ }
+
+ });
+
+ return ret;
+ });
+
+ // Sort by value
+ temp.sort(function (a, b) {
+ return a.value - b.value;
+ });
+
+ // Ok, now we have an array of all srcset values
+ for ( var j = 0; j < temp.length; j++ ) {
+ var el = temp[ j ];
+
+ if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) {
+ found = el;
+ break;
+ }
+ }
+
+ // If not found, take the last one
+ if ( !found && temp.length ) {
+ found = temp[ temp.length - 1 ];
+ }
+
+ if ( found ) {
+ slide.src = found.url;
+
+ // If we have default width/height values, we can calculate height for matching source
+ if ( slide.width && slide.height && found.postfix == 'w' ) {
+ slide.height = ( slide.width / slide.height ) * found.value;
+ slide.width = found.value;
+ }
+ }
+ }
+
+ // This will be wrapper containing both ghost and actual image
+ slide.$content = $('')
+ .addClass( 'fancybox-is-hidden' )
+ .appendTo( slide.$slide );
+
+
+ // If we have a thumbnail, we can display it while actual image is loading
+ // Users will not stare at black screen and actual image will appear gradually
+ if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) {
+
+ slide.width = slide.opts.width;
+ slide.height = slide.opts.height;
+
+ slide.$ghost = $('')
+ .one('error', function() {
+
+ $(this).remove();
+
+ slide.$ghost = null;
+
+ self.setBigImage( slide );
+
+ })
+ .one('load', function() {
+
+ self.afterLoad( slide );
+
+ self.setBigImage( slide );
+
+ })
+ .addClass( 'fancybox-image' )
+ .appendTo( slide.$content )
+ .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) );
+
+ } else {
+
+ self.setBigImage( slide );
+
+ }
+
+ },
+
+
+ // Create full-size image
+ // ======================
+
+ setBigImage : function ( slide ) {
+ var self = this;
+ var $img = $('');
+
+ slide.$image = $img
+ .one('error', function() {
+
+ self.setError( slide );
+
+ })
+ .one('load', function() {
+
+ // Clear timeout that checks if loading icon needs to be displayed
+ clearTimeout( slide.timouts );
+
+ slide.timouts = null;
+
+ if ( self.isClosing ) {
+ return;
+ }
+
+ slide.width = slide.opts.width || this.naturalWidth;
+ slide.height = slide.opts.height || this.naturalHeight;
+
+ if ( slide.opts.image.srcset ) {
+ $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset );
+ }
+
+ self.hideLoading( slide );
+
+ if ( slide.$ghost ) {
+
+ slide.timouts = setTimeout(function() {
+ slide.timouts = null;
+
+ slide.$ghost.hide();
+
+ }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) );
+
+ } else {
+ self.afterLoad( slide );
+ }
+
+ })
+ .addClass( 'fancybox-image' )
+ .attr('src', slide.src)
+ .appendTo( slide.$content );
+
+ if ( ( $img[0].complete || $img[0].readyState == "complete" ) && $img[0].naturalWidth && $img[0].naturalHeight ) {
+ $img.trigger( 'load' );
+
+ } else if( $img[0].error ) {
+ $img.trigger( 'error' );
+
+ } else {
+
+ slide.timouts = setTimeout(function() {
+ if ( !$img[0].complete && !slide.hasError ) {
+ self.showLoading( slide );
+ }
+
+ }, 100);
+
+ }
+
+ },
+
+
+ // Create iframe wrapper, iframe and bindings
+ // ==========================================
+
+ setIframe : function( slide ) {
+ var self = this,
+ opts = slide.opts.iframe,
+ $slide = slide.$slide,
+ $iframe;
+
+ slide.$content = $('')
+ .css( opts.css )
+ .appendTo( $slide );
+
+ $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) )
+ .attr( opts.attr )
+ .appendTo( slide.$content );
+
+ if ( opts.preload ) {
+
+ self.showLoading( slide );
+
+ // Unfortunately, it is not always possible to determine if iframe is successfully loaded
+ // (due to browser security policy)
+
+ $iframe.on('load.fb error.fb', function(e) {
+ this.isReady = 1;
+
+ slide.$slide.trigger( 'refresh' );
+
+ self.afterLoad( slide );
+ });
+
+ // Recalculate iframe content size
+ // ===============================
+
+ $slide.on('refresh.fb', function() {
+ var $wrap = slide.$content,
+ frameWidth = opts.css.width,
+ frameHeight = opts.css.height,
+ scrollWidth,
+ $contents,
+ $body;
+
+ if ( $iframe[0].isReady !== 1 ) {
+ return;
+ }
+
+ // Check if content is accessible,
+ // it will fail if frame is not with the same origin
+
+ try {
+ $contents = $iframe.contents();
+ $body = $contents.find('body');
+
+ } catch (ignore) {}
+
+ // Calculate dimensions for the wrapper
+ if ( $body && $body.length ) {
+
+ if ( frameWidth === undefined ) {
+ scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth;
+
+ frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) );
+ frameWidth += $wrap.outerWidth() - $wrap.innerWidth();
+ }
+
+ if ( frameHeight === undefined ) {
+ frameHeight = Math.ceil( $body.outerHeight(true) );
+ frameHeight += $wrap.outerHeight() - $wrap.innerHeight();
+ }
+
+ // Resize wrapper to fit iframe content
+ if ( frameWidth ) {
+ $wrap.width( frameWidth );
+ }
+
+ if ( frameHeight ) {
+ $wrap.height( frameHeight );
+ }
+ }
+
+ $wrap.removeClass( 'fancybox-is-hidden' );
+
+ });
+
+ } else {
+
+ this.afterLoad( slide );
+
+ }
+
+ $iframe.attr( 'src', slide.src );
+
+ if ( slide.opts.smallBtn === true ) {
+ slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) );
+ }
+
+ // Remove iframe if closing or changing gallery item
+ $slide.one( 'onReset', function () {
+
+ // This helps IE not to throw errors when closing
+ try {
+
+ $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' );
+
+ } catch ( ignore ) {}
+
+ $( this ).empty();
+
+ slide.isLoaded = false;
+
+ });
+
+ },
+
+
+ // Wrap and append content to the slide
+ // ======================================
+
+ setContent : function ( slide, content ) {
+
+ var self = this;
+
+ if ( self.isClosing ) {
+ return;
+ }
+
+ self.hideLoading( slide );
+
+ slide.$slide.empty();
+
+ if ( isQuery( content ) && content.parent().length ) {
+
+ // If content is a jQuery object, then it will be moved to the slide.
+ // The placeholder is created so we will know where to put it back.
+ // If user is navigating gallery fast, then the content might be already inside fancyBox
+ // =====================================================================================
+
+ // Make sure content is not already moved to fancyBox
+ content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' );
+
+ // Create temporary element marking original place of the content
+ slide.$placeholder = $( '' ).hide().insertAfter( content );
+
+ // Make sure content is visible
+ content.css('display', 'inline-block');
+
+ } else if ( !slide.hasError ) {
+
+ // If content is just a plain text, try to convert it to html
+ if ( $.type( content ) === 'string' ) {
+ content = $('
').append( $.trim( content ) ).contents();
+
+ // If we have text node, then add wrapping element to make vertical alignment work
+ if ( content[0].nodeType === 3 ) {
+ content = $('
').html( content );
+ }
+ }
+
+ // If "filter" option is provided, then filter content
+ if ( slide.opts.filter ) {
+ content = $('
+ fancyBox is a JavaScript library used to present images, videos and any html content in an elegant way.
+ It has all features you would expect - touch enabled, responsive and fully customizable.
+
+
+
+
+
+ Dependencies
+
+
+
+ jQuery 3+ is preferred, but fancyBox works with jQuery 1.9.1+ and jQuery 2+
+
+
+
Important
+
+
+ If you experience issues with image zooming, then update jQuery to the latest (at least v3.2.1).
+
+
+
Compatibility
+
+
+ fancyBox includes support for touch gestures and even supports pinch gestures for zooming.
+ It is perfectly suited for both mobile and desktop browsers.
+
+
+
+ fancyBox has been tested in following browsers/devices:
+
+
+
+
Chrome
+
Firefox
+
IE10/11
+
Edge
+
iOS Safari
+
Nexus 7 Chrome
+
+
+
Setup
+
+
+ You can install fancyBox by linking .css and .js to your html file.
+
+ Make sure you also load the jQuery library.
+ Below is a basic HTML template to use as an example:
+
If you already have jQuery on your page, you shouldn't include it second time
+
Do not include both fancybox.js and fancybox.min.js files
+
+ Some functionality (ajax, iframes, etc) will not work
+ when you're opening local file directly on your browser,
+ the code must be running on a web server
+
+ The most basic way to use fancyBox is by adding the data-fancybox attribute to a link.
+ A caption can be added using the data-caption attribute. Example:
+
+ If you choose this method, default settings will be applied.
+ See Options section for examples how to customize by changing defaults or using data-options attribute.
+
+
+
Initialize with JavaScript
+
+
+ Select elements with a jQuery selector and call the fancybox method:
+
+
+
<script type="text/javascript">
+ $("[data-fancybox]").fancybox({
+ // Options will go here
+ });
+</script>
+ Using this method, click event handler is attached only to the currently selected elements.
+
+ To attach click event listener for elements that exist now or in the future, use selector option. Example:
+
+ fancyBox can be activated at any point within Javascript and therefore does not necessarily need a trigger element.
+
+ Example of displaying a simple message:
+
+
+
$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');
+ See API section for more information and examples.
+
+
+
Grouping
+
+
+ If you have a group of items, you can use the same attribute data-fancybox value for each of them to create a gallery.
+ Each group should have a unique value:
+
+ fancyBox attempts to automatically detect the type of content based on the given url.
+
+ If it cannot be detected, the type can also be set manually using data-type attribute:
+
+
<a href="images.php?id=123" data-type="image" data-caption="Caption">
+ Show image
+</a>
+
+
+
+
+
+
Media types
+
+
+
Images
+
+
+ The standard way of using fancyBox is with a number of thumbnail images that link to larger images:
+
+ By default, fancyBox fully preloads an image before displaying it.
+ You can choose to display the image right away.
+ It will render and show the full size image while the data is being received.
+ To do so, some attributes are necessary:
+
+ fancyBox supports "scrset" so I can display different images based on viewport width. You can use this to improve download times for mobile users and over time save bandwidth.
+ Example:
+
+ It is also possible to protect images from downloading by right-click.
+ While this does not protect from truly determined users, it should discourage the vast majority from ripping off your files.
+
+ And then simply create a link having data-src attribute that matches ID of the element you want to open (preceded by a hash mark (#); in this example - #hidden-content):
+
+
+
<a data-fancybox data-src="#hidden-content" href="javascript:;">
+ Trigger the fancyBox
+</a>
+ The script will append small close button (if you have not disabled by smallBtn:false)
+ and will not apply any styles except for centering. Therefore you can easily set custom dimensions using CSS.
+
+
+
Ajax
+
+
+ To load content via AJAX, you need to add a data-type="ajax" attribute to your link:
+
+ Additionally it is possible to define a selector with the data-filter attribute to show only a part of the response. The selector can be any string, that is a valid jQuery selector:
+
+ If the content can be shown on a page, and placement in an iframe is not blocked by script or security configuration of that page,
+ it can be presented in a fancyBox:
+
+ To access and control fancyBox in parent window from inside an iframe:
+
+
+
// Adjust iframe height according to the contents
+parent.jQuery.fancybox.getInstance().update();
+
+// Close current fancyBox instance
+parent.jQuery.fancybox.getInstance().close();
+ If you have not disabled iframe preloading (using preload option), then the script will atempt to
+ calculate content dimensions and will adjust width/height of iframe to fit with content in it.
+ Keep in mind, that due to same origin policy,
+ there are some limitations.
+
+
+
+ This example will disable iframe preloading and will display small close button next to iframe instead of the toolbar:
+
+ Supported sites can be used with fancyBox by just providing the page URL:
+
+
+
<a data-fancybox href="https://www.youtube.com/watch?v=_sI_Ps7JSEk">
+ YouTube video
+</a>
+
+<a data-fancybox href="https://vimeo.com/191947042">
+ Vimeo video
+</a>
+
+<a data-fancybox href="https://www.google.com/maps/search/Empire+State+Building/">
+ Google Map
+</a>
+
+<a data-fancybox href="https://www.instagram.com/p/BNXYW8-goPI/?taken-by=jamesrelfdyer" data-caption="<span title="Edited">balloon rides at dawn ✨🎈<br>was such a magical experience floating over napa valley as the golden light hit the hills.<br><a href="https://www.instagram.com/jamesrelfdyer/">@jamesrelfdyer</a></span>">
+ Instagram photo
+</a>
+ Obviously, you can choose any size you like, any combination with min/max values.
+
+ Aspect ratio lock for videos is not implemented yet, but if you wish, you can use this snippet.
+
+
+
+
+
Video parameters
+
+
+ Controlling a video via URL parameters:
+
+
+
<a data-fancybox href="https://www.youtube.com/watch?v=_sI_Ps7JSEk&autoplay=1&rel=0&controls=0&showinfo=0">
+ YouTube video - hide controls and info
+</a>
+
+<a data-fancybox href="https://vimeo.com/191947042?color=f00">
+ Vimeo video - custom color
+</a>
+ Plugin options / defaults are exposed in $.fancybox.defaults namespace so you can easily adjust them globally:
+
+
+
+
$.fancybox.defaults.animationEffect = "fade";
+
+
+ Custom options for each element individually can be set by adding a data-options
+ attribute to the element.
+
+ This attribute should contain the properly formatted JSON object:
+
+
+
<a data-fancybox data-options='{"caption" : "My caption", "src" : "https://codepen.io/about/", "type" : "iframe"}' href="javascript:;" class="btn">
+ Open external page
+</a>
+
+ The fancyBox API offers a couple of methods to control fancyBox.
+
+ This gives you the ability to extend the plugin and to integrate it with other web application components.
+
+
+
Core methods
+
+
+ Core methods are methods which affect/handle instances:
+
+
+
+
// Close only the currently active or all fancyBox instances
+$.fancybox.close( true );
+
+// Open the fancyBox right away
+$.fancybox.open( items, opts, index );
+
+
+
+
+ Gallery items can be collection of jQuery objects or array containing plain objects. This can be used, for example, to create custom click event.
+
+ If you wish to display some html content (for example, a message), then you can use a simpler syntax.
+ It is advised to use a wrapper around your content.
+
+
+
$.fancybox.open('<div class="message"><h2>Hello!</h2><p>You are awesome!</p></div>');
+ Once you have a reference to fancyBox instance the following methods are available:
+
+
+
+
// Go to next gallery item
+instance.next( duration );
+
+// Go to previous gallery item
+instance.previous( duration );
+
+// Switch to selected gallery item
+instance.jumpTo( index, duration );
+
+// Check if current image dimensions are smaller than actual
+instance.isScaledDown();
+
+// Scale image to the actual size of the image
+instance.scaleToActual( x, y, duration );
+
+// Check if image dimensions exceed parent element
+instance.canPan();
+
+// Scale image to fit inside parent element
+instance.scaleToFit( duration );
+
+// Update position and content of all slides
+instance.update();
+
+// Update slide position and scale content to fit
+instance.updateSlide( slide );
+
+// Update infobar values, navigation button states and reveal caption
+instance.updateControls( force );
+
+// Load custom content into the slide
+instance.setContent( slide, content );
+
+// Show loading icon inside the slide
+instance.showLoading( slide );
+
+// Remove loading icon from the slide
+instance.hideLoading( slide );
+
+// Try to find and focus on the first focusable element
+instance.focus();
+
+// Activates current instance, brings it to the front
+instance.activate();
+
+// Close instance
+instance.close();
+
+
+
+
+ You can also do something like this:
+
+
+
$.fancybox.getInstance().jumpTo(1);
+
+
+ or simply:
+
+
+
$.fancybox.getInstance('jumpTo', 1);
+
+
+
+
+
Events
+
+
+ fancyBox fires several events:
+
+
+
beforeLoad : Before the content of a slide is being loaded
+afterLoad : When the content of a slide is done loading
+
+beforeShow : Before open animation starts
+afterShow : When content is done loading and animating
+
+beforeClose : Before the instance attempts to close. Return false to cancel the close.
+afterClose : After instance has been closed
+
+onInit : When instance has been initialized
+onActivate : When instance is brought to front
+onDeactivate : When other instance has been activated
+
+
+
+ Event callbacks can be set as function properties of the options object passed to fancyBox initialization function:
+
+
+
<script type="text/javascript">
+ $("[data-fancybox]").fancybox({
+ afterShow: function( instance, slide ) {
+
+ // Tip: Each event passes useful information within the event object:
+
+ // Object containing references to interface elements
+ // (background, buttons, caption, etc)
+ // console.info( instance.$refs );
+
+ // Current slide options
+ // console.info( slide.opts );
+
+ // Clicked element
+ // console.info( slide.opts.$orig );
+
+ // Reference to DOM element of the slide
+ // console.info( slide.$slide );
+
+ }
+ });
+</script>
+
+
+ Each callback receives two parameters - current fancyBox instance and current gallery object, if exists.
+
+
+
+ It is also possible to attach event handler for all instances.
+ To prevent interfering with other scripts, these events have been namespaced to .fb.
+ These handlers receive 3 parameters - event, current fancyBox instance and current gallery object.
+
+
+ Here is an example of binding to the afterShow event:
+
+
+
$(document).on('afterShow.fb', function( e, instance, slide ) {
+ // Your code goes here
+});
+
+
+
+ If you wish to prevent closing of the modal (for example, after form submit), you can use beforeClose
+ callback. Simply return false:
+
+ fancyBox code is split into several files (modules) that extend core functionality.
+ You can build your own fancyBox version by excluding unnecessary modules, if needed.
+ Each one has their own js and/or css files.
+
+
+
+ Some modules can be customized and controlled programmatically.
+ List of all possible options:
+
+
+
fullScreen : {
+ autoStart : false,
+},
+
+touch : {
+ vertical : true, // Allow to drag content vertically
+ momentum : true // Continuous movement when panning
+},
+
+// Hash value when initializing manually,
+// set `false` to disable hash change
+hash : null,
+
+// Customize or add new media types
+// Example:
+/*
+media : {
+ youtube : {
+ params : {
+ autoplay : 0
+ }
+ }
+}
+*/
+media : {},
+
+slideShow : {
+ autoStart : false,
+ speed : 4000
+},
+
+thumbs : {
+ autoStart : false, // Display thumbnails on opening
+ hideOnClose : true, // Hide thumbnail grid when closing animation starts
+ parentEl : '.fancybox-container', // Container is injected into this element
+ axis : 'y' // Vertical (y) or horizontal (x) scrolling
+}
+
+
+ If you would inspect fancyBox instance object, you would find that same keys ar captialized - these are references for each module object.
+ Also, you would notice that fancyBox uses common naming convention to prefix jQuery objects with $.
+
+
+
+ This is how you, for example, can access thumbnail grid element:
+
+
+
$.fancybox.getInstance().Thumbs.$grid
+
+
+ This example shows how to call method that toggles thumbnails:
+
+ The script measures width of the scrollbar and creates compensate-for-scrollbar CSS class
+ that uses this value for margin-right property.
+ Therefore, if your element has width:100%, you should positon it using left and right properties instead. Example:
+
+ There is currenty no JS option to change thumbnail grid position.
+ But fancyBox is designed so that you can use CSS to change position or dimension for each block
+ (e.g., content area, caption or thumbnail grid).
+ This gives you freedom to completely change the look and feel of the modal window, if needed.
+
+ View demo on CodePen
+
+
+
5. How to disable touch gestures/swiping
+
+
+ When you want to make your content selectable or clickable, you have two options:
+
+
+
+
+ disable touch gestures completely by setting touch:false
+
+
+ add data-selectable="true" attribute to your html element
+
',
+
+ btnTpl : {
+
+ download : '' +
+ '' +
+ '',
+
+ zoom : '',
+
+ close : '',
+
+ // This small close button will be appended to your html/inline/ajax content by default,
+ // if "smallBtn" option is not set to false
+ smallBtn : '',
+
+ // Arrows
+ arrowLeft : '',
+
+ arrowRight : ''
+ },
+
+ // Container is injected into this element
+ parentEl : 'body',
+
+
+ // Focus handling
+ // ==============
+
+ // Try to focus on the first focusable element after opening
+ autoFocus : false,
+
+ // Put focus back to active element after closing
+ backFocus : true,
+
+ // Do not let user to focus on element outside modal content
+ trapFocus : true,
+
+
+ // Module specific options
+ // =======================
+
+ fullScreen : {
+ autoStart : false,
+ },
+
+ // Set `touch: false` to disable dragging/swiping
+ touch : {
+ vertical : true, // Allow to drag content vertically
+ momentum : true // Continue movement after releasing mouse/touch when panning
+ },
+
+ // Hash value when initializing manually,
+ // set `false` to disable hash change
+ hash : null,
+
+ // Customize or add new media types
+ // Example:
+ /*
+ media : {
+ youtube : {
+ params : {
+ autoplay : 0
+ }
+ }
+ }
+ */
+ media : {},
+
+ slideShow : {
+ autoStart : false,
+ speed : 4000
+ },
+
+ thumbs : {
+ autoStart : false, // Display thumbnails on opening
+ hideOnClose : true, // Hide thumbnail grid when closing animation starts
+ parentEl : '.fancybox-container', // Container is injected into this element
+ axis : 'y' // Vertical (y) or horizontal (x) scrolling
+ },
+
+ // Use mousewheel to navigate gallery
+ // If 'auto' - enabled for images only
+ wheel : 'auto',
+
+ // Callbacks
+ //==========
+
+ // See Documentation/API/Events for more information
+ // Example:
+ /*
+ afterShow: function( instance, current ) {
+ console.info( 'Clicked element:' );
+ console.info( current.opts.$orig );
+ }
+ */
+
+ onInit : $.noop, // When instance has been initialized
+
+ beforeLoad : $.noop, // Before the content of a slide is being loaded
+ afterLoad : $.noop, // When the content of a slide is done loading
+
+ beforeShow : $.noop, // Before open animation starts
+ afterShow : $.noop, // When content is done loading and animating
+
+ beforeClose : $.noop, // Before the instance attempts to close. Return false to cancel the close.
+ afterClose : $.noop, // After instance has been closed
+
+ onActivate : $.noop, // When instance is brought to front
+ onDeactivate : $.noop, // When other instance has been activated
+
+
+ // Interaction
+ // ===========
+
+ // Use options below to customize taken action when user clicks or double clicks on the fancyBox area,
+ // each option can be string or method that returns value.
+ //
+ // Possible values:
+ // "close" - close instance
+ // "next" - move to next gallery item
+ // "nextOrClose" - move to next gallery item or close if gallery has only one item
+ // "toggleControls" - show/hide controls
+ // "zoom" - zoom image (if loaded)
+ // false - do nothing
+
+ // Clicked on the content
+ clickContent : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ },
+
+ // Clicked on the slide
+ clickSlide : 'close',
+
+ // Clicked on the background (backdrop) element
+ clickOutside : 'close',
+
+ // Same as previous two, but for double click
+ dblclickContent : false,
+ dblclickSlide : false,
+ dblclickOutside : false,
+
+
+ // Custom options when mobile device is detected
+ // =============================================
+
+ mobile : {
+ idleTime : false,
+ margin : 0,
+
+ clickContent : function( current, event ) {
+ return current.type === 'image' ? 'toggleControls' : false;
+ },
+ clickSlide : function( current, event ) {
+ return current.type === 'image' ? 'toggleControls' : 'close';
+ },
+ dblclickContent : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ },
+ dblclickSlide : function( current, event ) {
+ return current.type === 'image' ? 'zoom' : false;
+ }
+ },
+
+
+ // Internationalization
+ // ============
+
+ lang : 'en',
+ i18n : {
+ 'en' : {
+ CLOSE : 'Close',
+ NEXT : 'Next',
+ PREV : 'Previous',
+ ERROR : 'The requested content cannot be loaded. Please try again later.',
+ PLAY_START : 'Start slideshow',
+ PLAY_STOP : 'Pause slideshow',
+ FULL_SCREEN : 'Full screen',
+ THUMBS : 'Thumbnails',
+ DOWNLOAD : 'Download',
+ SHARE : 'Share',
+ ZOOM : 'Zoom'
+ },
+ 'de' : {
+ CLOSE : 'Schliessen',
+ NEXT : 'Weiter',
+ PREV : 'Zurück',
+ ERROR : 'Die angeforderten Daten konnten nicht geladen werden. Bitte versuchen Sie es später nochmal.',
+ PLAY_START : 'Diaschau starten',
+ PLAY_STOP : 'Diaschau beenden',
+ FULL_SCREEN : 'Vollbild',
+ THUMBS : 'Vorschaubilder',
+ DOWNLOAD : 'Herunterladen',
+ SHARE : 'Teilen',
+ ZOOM : 'Maßstab'
+ }
+ }
+
+ };
+
+ // Few useful variables and methods
+ // ================================
+
+ var $W = $(window);
+ var $D = $(document);
+
+ var called = 0;
+
+
+ // Check if an object is a jQuery object and not a native JavaScript object
+ // ========================================================================
+
+ var isQuery = function ( obj ) {
+ return obj && obj.hasOwnProperty && obj instanceof $;
+ };
+
+
+ // Handle multiple browsers for "requestAnimationFrame" and "cancelAnimationFrame"
+ // ===============================================================================
+
+ var requestAFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ // if all else fails, use setTimeout
+ function (callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+
+
+ // Detect the supported transition-end event property name
+ // =======================================================
+
+ var transitionEnd = (function () {
+ var t, el = document.createElement("fakeelement");
+
+ var transitions = {
+ "transition" : "transitionend",
+ "OTransition" : "oTransitionEnd",
+ "MozTransition" : "transitionend",
+ "WebkitTransition": "webkitTransitionEnd"
+ };
+
+ for (t in transitions) {
+ if (el.style[t] !== undefined){
+ return transitions[t];
+ }
+ }
+
+ return 'transitionend';
+ })();
+
+
+ // Force redraw on an element.
+ // This helps in cases where the browser doesn't redraw an updated element properly.
+ // =================================================================================
+
+ var forceRedraw = function( $el ) {
+ return ( $el && $el.length && $el[0].offsetHeight );
+ };
+
+
+ // Class definition
+ // ================
+
+ var FancyBox = function( content, opts, index ) {
+ var self = this;
+
+ self.opts = $.extend( true, { index : index }, $.fancybox.defaults, opts || {} );
+
+ if ( $.fancybox.isMobile ) {
+ self.opts = $.extend( true, {}, self.opts, self.opts.mobile );
+ }
+
+ // Exclude buttons option from deep merging
+ if ( opts && $.isArray( opts.buttons ) ) {
+ self.opts.buttons = opts.buttons;
+ }
+
+ self.id = self.opts.id || ++called;
+ self.group = [];
+
+ self.currIndex = parseInt( self.opts.index, 10 ) || 0;
+ self.prevIndex = null;
+
+ self.prevPos = null;
+ self.currPos = 0;
+
+ self.firstRun = null;
+
+ // Create group elements from original item collection
+ self.createGroup( content );
+
+ if ( !self.group.length ) {
+ return;
+ }
+
+ // Save last active element and current scroll position
+ self.$lastFocus = $(document.activeElement).blur();
+
+ // Collection of gallery objects
+ self.slides = {};
+
+ self.init();
+ };
+
+ $.extend(FancyBox.prototype, {
+
+ // Create DOM structure
+ // ====================
+
+ init : function() {
+ var self = this,
+ firstItem = self.group[ self.currIndex ],
+ firstItemOpts = firstItem.opts,
+ scrollbarWidth = $.fancybox.scrollbarWidth,
+ $scrollDiv,
+ $container,
+ buttonStr;
+
+ self.scrollTop = $D.scrollTop();
+ self.scrollLeft = $D.scrollLeft();
+
+
+ // Hide scrollbars
+ // ===============
+
+ if ( !$.fancybox.getInstance() ) {
+
+ $( 'body' ).addClass( 'fancybox-active' );
+
+ // iOS hack
+ if ( /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream ) {
+
+ // iOS has problems for input elements inside fixed containers,
+ // the workaround is to apply `position: fixed` to `` element,
+ // unfortunately, this makes it lose the scrollbars and forces address bar to appear.
+
+ if ( firstItem.type !== 'image' ) {
+ $( 'body' ).css( 'top', $( 'body' ).scrollTop() * -1 ).addClass( 'fancybox-iosfix' );
+ }
+
+ } else if ( !$.fancybox.isMobile && document.body.scrollHeight > window.innerHeight ) {
+
+ if ( scrollbarWidth === undefined ) {
+ $scrollDiv = $('').appendTo( 'body' );
+
+ scrollbarWidth = $.fancybox.scrollbarWidth = $scrollDiv[0].offsetWidth - $scrollDiv[0].clientWidth;
+
+ $scrollDiv.remove();
+ }
+
+ $( 'head' ).append( '' );
+ $( 'body' ).addClass( 'compensate-for-scrollbar' );
+ }
+ }
+
+
+ // Build html markup and set references
+ // ====================================
+
+ // Build html code for buttons and insert into main template
+ buttonStr = '';
+
+ $.each( firstItemOpts.buttons, function( index, value ) {
+ buttonStr += ( firstItemOpts.btnTpl[ value ] || '' );
+ });
+
+ // Create markup from base template, it will be initially hidden to
+ // avoid unnecessary work like painting while initializing is not complete
+ $container = $(
+ self.translate( self,
+ firstItemOpts.baseTpl
+ .replace( '\{\{buttons\}\}', buttonStr )
+ .replace( '\{\{arrows\}\}', firstItemOpts.btnTpl.arrowLeft + firstItemOpts.btnTpl.arrowRight )
+ )
+ )
+ .attr( 'id', 'fancybox-container-' + self.id )
+ .addClass( 'fancybox-is-hidden' )
+ .addClass( firstItemOpts.baseClass )
+ .data( 'FancyBox', self )
+ .appendTo( firstItemOpts.parentEl );
+
+ // Create object holding references to jQuery wrapped nodes
+ self.$refs = {
+ container : $container
+ };
+
+ [ 'bg', 'inner', 'infobar', 'toolbar', 'stage', 'caption', 'navigation' ].forEach(function(item) {
+ self.$refs[ item ] = $container.find( '.fancybox-' + item );
+ });
+
+ self.trigger( 'onInit' );
+
+ // Enable events, deactive previous instances
+ self.activate();
+
+ // Build slides, load and reveal content
+ self.jumpTo( self.currIndex );
+ },
+
+
+ // Simple i18n support - replaces object keys found in template
+ // with corresponding values
+ // ============================================================
+
+ translate : function( obj, str ) {
+ var arr = obj.opts.i18n[ obj.opts.lang ];
+
+ return str.replace(/\{\{(\w+)\}\}/g, function(match, n) {
+ var value = arr[n];
+
+ if ( value === undefined ) {
+ return match;
+ }
+
+ return value;
+ });
+ },
+
+ // Create array of gally item objects
+ // Check if each object has valid type and content
+ // ===============================================
+
+ createGroup : function ( content ) {
+ var self = this;
+ var items = $.makeArray( content );
+
+ $.each(items, function( i, item ) {
+ var obj = {},
+ opts = {},
+ $item,
+ type,
+ found,
+ src,
+ srcParts;
+
+ // Step 1 - Make sure we have an object
+ // ====================================
+
+ if ( $.isPlainObject( item ) ) {
+
+ // We probably have manual usage here, something like
+ // $.fancybox.open( [ { src : "image.jpg", type : "image" } ] )
+
+ obj = item;
+ opts = item.opts || item;
+
+ } else if ( $.type( item ) === 'object' && $( item ).length ) {
+
+ // Here we probably have jQuery collection returned by some selector
+ $item = $( item );
+
+ opts = $item.data();
+ opts = $.extend( {}, opts, opts.options || {} );
+
+ // Here we store clicked element
+ opts.$orig = $item;
+
+ obj.src = opts.src || $item.attr( 'href' );
+
+ // Assume that simple syntax is used, for example:
+ // `$.fancybox.open( $("#test"), {} );`
+ if ( !obj.type && !obj.src ) {
+ obj.type = 'inline';
+ obj.src = item;
+ }
+
+ } else {
+
+ // Assume we have a simple html code, for example:
+ // $.fancybox.open( '
Hi!
' );
+
+ obj = {
+ type : 'html',
+ src : item + ''
+ };
+
+ }
+
+ // Each gallery object has full collection of options
+ obj.opts = $.extend( true, {}, self.opts, opts );
+
+ // Do not merge buttons array
+ if ( $.isArray( opts.buttons ) ) {
+ obj.opts.buttons = opts.buttons;
+ }
+
+
+ // Step 2 - Make sure we have content type, if not - try to guess
+ // ==============================================================
+
+ type = obj.type || obj.opts.type;
+ src = obj.src || '';
+
+ if ( !type && src ) {
+ if ( src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i) ) {
+ type = 'image';
+
+ } else if ( src.match(/\.(pdf)((\?|#).*)?$/i) ) {
+ type = 'pdf';
+
+ } else if ( found = src.match(/\.(mp4|mov|ogv)((\?|#).*)?$/i) ) {
+ type = 'video';
+
+ if ( !obj.opts.videoFormat ) {
+ obj.opts.videoFormat = 'video/' + ( found[1] === 'ogv' ? 'ogg' : found[1] );
+ }
+
+ } else if ( src.charAt(0) === '#' ) {
+ type = 'inline';
+ }
+ }
+
+ if ( type ) {
+ obj.type = type;
+
+ } else {
+ self.trigger( 'objectNeedsType', obj );
+ }
+
+
+ // Step 3 - Some adjustments
+ // =========================
+
+ obj.index = self.group.length;
+
+ // Check if $orig and $thumb objects exist
+ if ( obj.opts.$orig && !obj.opts.$orig.length ) {
+ delete obj.opts.$orig;
+ }
+
+ if ( !obj.opts.$thumb && obj.opts.$orig ) {
+ obj.opts.$thumb = obj.opts.$orig.find( 'img:first' );
+ }
+
+ if ( obj.opts.$thumb && !obj.opts.$thumb.length ) {
+ delete obj.opts.$thumb;
+ }
+
+ // "caption" is a "special" option, it can be used to customize caption per gallery item ..
+ if ( $.type( obj.opts.caption ) === 'function' ) {
+ obj.opts.caption = obj.opts.caption.apply( item, [ self, obj ] );
+ }
+
+ if ( $.type( self.opts.caption ) === 'function' ) {
+ obj.opts.caption = self.opts.caption.apply( item, [ self, obj ] );
+ }
+
+ // Make sure we have caption as a string or jQuery object
+ if ( !( obj.opts.caption instanceof $ ) ) {
+ obj.opts.caption = obj.opts.caption === undefined ? '' : obj.opts.caption + '';
+ }
+
+ // Check if url contains "filter" used to filter the content
+ // Example: "ajax.html #something"
+ if ( type === 'ajax' ) {
+ srcParts = src.split(/\s+/, 2);
+
+ if ( srcParts.length > 1 ) {
+ obj.src = srcParts.shift();
+
+ obj.opts.filter = srcParts.shift();
+ }
+ }
+
+ if ( obj.opts.smallBtn == 'auto' ) {
+
+ if ( $.inArray( type, ['html', 'inline', 'ajax'] ) > -1 ) {
+ obj.opts.toolbar = false;
+ obj.opts.smallBtn = true;
+
+ } else {
+ obj.opts.smallBtn = false;
+ }
+
+ }
+
+ // If the type is "pdf", then simply load file into iframe
+ if ( type === 'pdf' ) {
+ obj.type = 'iframe';
+
+ obj.opts.iframe.preload = false;
+ }
+
+ // Hide all buttons and disable interactivity for modal items
+ if ( obj.opts.modal ) {
+
+ obj.opts = $.extend(true, obj.opts, {
+ // Remove buttons
+ infobar : 0,
+ toolbar : 0,
+
+ smallBtn : 0,
+
+ // Disable keyboard navigation
+ keyboard : 0,
+
+ // Disable some modules
+ slideShow : 0,
+ fullScreen : 0,
+ thumbs : 0,
+ touch : 0,
+
+ // Disable click event handlers
+ clickContent : false,
+ clickSlide : false,
+ clickOutside : false,
+ dblclickContent : false,
+ dblclickSlide : false,
+ dblclickOutside : false
+ });
+
+ }
+
+ // Step 4 - Add processed object to group
+ // ======================================
+
+ self.group.push( obj );
+
+ });
+
+ },
+
+
+ // Attach an event handler functions for:
+ // - navigation buttons
+ // - browser scrolling, resizing;
+ // - focusing
+ // - keyboard
+ // - detect idle
+ // ======================================
+
+ addEvents : function() {
+ var self = this;
+
+ self.removeEvents();
+
+ // Make navigation elements clickable
+ self.$refs.container.on('click.fb-close', '[data-fancybox-close]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.close( e );
+
+ }).on( 'click.fb-prev touchend.fb-prev', '[data-fancybox-prev]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.previous();
+
+ }).on( 'click.fb-next touchend.fb-next', '[data-fancybox-next]', function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ self.next();
+
+ }).on( 'click.fb', '[data-fancybox-zoom]', function(e) {
+ // Click handler for zoom button
+ self[ self.isScaledDown() ? 'scaleToActual' : 'scaleToFit' ]();
+ });
+
+
+ // Handle page scrolling and browser resizing
+ $W.on('orientationchange.fb resize.fb', function(e) {
+
+ if ( e && e.originalEvent && e.originalEvent.type === "resize" ) {
+
+ requestAFrame(function() {
+ self.update();
+ });
+
+ } else {
+
+ self.$refs.stage.hide();
+
+ setTimeout(function() {
+ self.$refs.stage.show();
+
+ self.update();
+ }, 600);
+
+ }
+
+ });
+
+ // Trap keyboard focus inside of the modal, so the user does not accidentally tab outside of the modal
+ // (a.k.a. "escaping the modal")
+ $D.on('focusin.fb', function(e) {
+ var instance = $.fancybox ? $.fancybox.getInstance() : null;
+
+ if ( instance.isClosing || !instance.current || !instance.current.opts.trapFocus || $( e.target ).hasClass( 'fancybox-container' ) || $( e.target ).is( document ) ) {
+ return;
+ }
+
+ if ( instance && $( e.target ).css( 'position' ) !== 'fixed' && !instance.$refs.container.has( e.target ).length ) {
+ e.stopPropagation();
+
+ instance.focus();
+
+ // Sometimes page gets scrolled, set it back
+ $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft );
+ }
+ });
+
+
+ // Enable keyboard navigation
+ $D.on('keydown.fb', function (e) {
+ var current = self.current,
+ keycode = e.keyCode || e.which;
+
+ if ( !current || !current.opts.keyboard ) {
+ return;
+ }
+
+ if ( $(e.target).is('input') || $(e.target).is('textarea') ) {
+ return;
+ }
+
+ // Backspace and Esc keys
+ if ( keycode === 8 || keycode === 27 ) {
+ e.preventDefault();
+
+ self.close( e );
+
+ return;
+ }
+
+ // Left arrow and Up arrow
+ if ( keycode === 37 || keycode === 38 ) {
+ e.preventDefault();
+
+ self.previous();
+
+ return;
+ }
+
+ // Righ arrow and Down arrow
+ if ( keycode === 39 || keycode === 40 ) {
+ e.preventDefault();
+
+ self.next();
+
+ return;
+ }
+
+ self.trigger('afterKeydown', e, keycode);
+ });
+
+
+ // Hide controls after some inactivity period
+ if ( self.group[ self.currIndex ].opts.idleTime ) {
+ self.idleSecondsCounter = 0;
+
+ $D.on('mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle', function(e) {
+ self.idleSecondsCounter = 0;
+
+ if ( self.isIdle ) {
+ self.showControls();
+ }
+
+ self.isIdle = false;
+ });
+
+ self.idleInterval = window.setInterval(function() {
+ self.idleSecondsCounter++;
+
+ if ( self.idleSecondsCounter >= self.group[ self.currIndex ].opts.idleTime && !self.isDragging ) {
+ self.isIdle = true;
+ self.idleSecondsCounter = 0;
+
+ self.hideControls();
+ }
+
+ }, 1000);
+ }
+
+ },
+
+
+ // Remove events added by the core
+ // ===============================
+
+ removeEvents : function() {
+ var self = this;
+
+ $W.off( 'orientationchange.fb resize.fb' );
+ $D.off( 'focusin.fb keydown.fb .fb-idle' );
+
+ this.$refs.container.off( '.fb-close .fb-prev .fb-next' );
+
+ if ( self.idleInterval ) {
+ window.clearInterval( self.idleInterval );
+
+ self.idleInterval = null;
+ }
+ },
+
+
+ // Change to previous gallery item
+ // ===============================
+
+ previous : function( duration ) {
+ return this.jumpTo( this.currPos - 1, duration );
+ },
+
+
+ // Change to next gallery item
+ // ===========================
+
+ next : function( duration ) {
+ return this.jumpTo( this.currPos + 1, duration );
+ },
+
+
+ // Switch to selected gallery item
+ // ===============================
+
+ jumpTo : function ( pos, duration, slide ) {
+ var self = this,
+ firstRun,
+ loop,
+ current,
+ previous,
+ canvasWidth,
+ currentPos,
+ transitionProps;
+
+ var groupLen = self.group.length;
+
+ if ( self.isDragging || self.isClosing || ( self.isAnimating && self.firstRun ) ) {
+ return;
+ }
+
+ pos = parseInt( pos, 10 );
+ loop = self.current ? self.current.opts.loop : self.opts.loop;
+
+ if ( !loop && ( pos < 0 || pos >= groupLen ) ) {
+ return false;
+ }
+
+ firstRun = self.firstRun = ( self.firstRun === null );
+
+ if ( groupLen < 2 && !firstRun && !!self.isDragging ) {
+ return;
+ }
+
+ previous = self.current;
+
+ self.prevIndex = self.currIndex;
+ self.prevPos = self.currPos;
+
+ // Create slides
+ current = self.createSlide( pos );
+
+ if ( groupLen > 1 ) {
+ if ( loop || current.index > 0 ) {
+ self.createSlide( pos - 1 );
+ }
+
+ if ( loop || current.index < groupLen - 1 ) {
+ self.createSlide( pos + 1 );
+ }
+ }
+
+ self.current = current;
+ self.currIndex = current.index;
+ self.currPos = current.pos;
+
+ self.trigger( 'beforeShow', firstRun );
+
+ self.updateControls();
+
+ currentPos = $.fancybox.getTranslate( current.$slide );
+
+ current.isMoved = ( currentPos.left !== 0 || currentPos.top !== 0 ) && !current.$slide.hasClass( 'fancybox-animated' );
+ current.forcedDuration = undefined;
+
+ if ( $.isNumeric( duration ) ) {
+ current.forcedDuration = duration;
+ } else {
+ duration = current.opts[ firstRun ? 'animationDuration' : 'transitionDuration' ];
+ }
+
+ duration = parseInt( duration, 10 );
+
+ // Fresh start - reveal container, current slide and start loading content
+ if ( firstRun ) {
+
+ if ( current.opts.animationEffect && duration ) {
+ self.$refs.container.css( 'transition-duration', duration + 'ms' );
+ }
+
+ self.$refs.container.removeClass( 'fancybox-is-hidden' );
+
+ forceRedraw( self.$refs.container );
+
+ self.$refs.container.addClass( 'fancybox-is-open' );
+
+ // Make first slide visible (to display loading icon, if needed)
+ current.$slide.addClass( 'fancybox-slide--current' );
+
+ self.loadSlide( current );
+
+ self.preload( 'image' );
+
+ return;
+ }
+
+ // Clean up
+ $.each(self.slides, function( index, slide ) {
+ $.fancybox.stop( slide.$slide );
+ });
+
+ // Make current that slide is visible even if content is still loading
+ current.$slide.removeClass( 'fancybox-slide--next fancybox-slide--previous' ).addClass( 'fancybox-slide--current' );
+
+ // If slides have been dragged, animate them to correct position
+ if ( current.isMoved ) {
+ canvasWidth = Math.round( current.$slide.width() );
+
+ $.each(self.slides, function( index, slide ) {
+ var pos = slide.pos - current.pos;
+
+ $.fancybox.animate( slide.$slide, {
+ top : 0,
+ left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter )
+ }, duration, function() {
+
+ slide.$slide.removeAttr('style').removeClass( 'fancybox-slide--next fancybox-slide--previous' );
+
+ if ( slide.pos === self.currPos ) {
+ current.isMoved = false;
+
+ self.complete();
+ }
+ });
+ });
+
+ } else {
+ self.$refs.stage.children().removeAttr( 'style' );
+ }
+
+ // Start transition that reveals current content
+ // or wait when it will be loaded
+
+ if ( current.isLoaded ) {
+ self.revealContent( current );
+
+ } else {
+ self.loadSlide( current );
+ }
+
+ self.preload( 'image' );
+
+ if ( previous.pos === current.pos ) {
+ return;
+ }
+
+ // Handle previous slide
+ // =====================
+
+ transitionProps = 'fancybox-slide--' + ( previous.pos > current.pos ? 'next' : 'previous' );
+
+ previous.$slide.removeClass( 'fancybox-slide--complete fancybox-slide--current fancybox-slide--next fancybox-slide--previous' );
+
+ previous.isComplete = false;
+
+ if ( !duration || ( !current.isMoved && !current.opts.transitionEffect ) ) {
+ return;
+ }
+
+ if ( current.isMoved ) {
+ previous.$slide.addClass( transitionProps );
+
+ } else {
+
+ transitionProps = 'fancybox-animated ' + transitionProps + ' fancybox-fx-' + current.opts.transitionEffect;
+
+ $.fancybox.animate( previous.$slide, transitionProps, duration, function() {
+ previous.$slide.removeClass( transitionProps ).removeAttr( 'style' );
+ });
+
+ }
+
+ },
+
+
+ // Create new "slide" element
+ // These are gallery items that are actually added to DOM
+ // =======================================================
+
+ createSlide : function( pos ) {
+
+ var self = this;
+ var $slide;
+ var index;
+
+ index = pos % self.group.length;
+ index = index < 0 ? self.group.length + index : index;
+
+ if ( !self.slides[ pos ] && self.group[ index ] ) {
+ $slide = $('').appendTo( self.$refs.stage );
+
+ self.slides[ pos ] = $.extend( true, {}, self.group[ index ], {
+ pos : pos,
+ $slide : $slide,
+ isLoaded : false,
+ });
+
+ self.updateSlide( self.slides[ pos ] );
+ }
+
+ return self.slides[ pos ];
+ },
+
+
+ // Scale image to the actual size of the image
+ // ===========================================
+
+ scaleToActual : function( x, y, duration ) {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var imgPos, posX, posY, scaleX, scaleY;
+
+ var canvasWidth = parseInt( current.$slide.width(), 10 );
+ var canvasHeight = parseInt( current.$slide.height(), 10 );
+
+ var newImgWidth = current.width;
+ var newImgHeight = current.height;
+
+ if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) {
+ return;
+ }
+
+ $.fancybox.stop( $what );
+
+ self.isAnimating = true;
+
+ x = x === undefined ? canvasWidth * 0.5 : x;
+ y = y === undefined ? canvasHeight * 0.5 : y;
+
+ imgPos = $.fancybox.getTranslate( $what );
+
+ scaleX = newImgWidth / imgPos.width;
+ scaleY = newImgHeight / imgPos.height;
+
+ // Get center position for original image
+ posX = ( canvasWidth * 0.5 - newImgWidth * 0.5 );
+ posY = ( canvasHeight * 0.5 - newImgHeight * 0.5 );
+
+ // Make sure image does not move away from edges
+ if ( newImgWidth > canvasWidth ) {
+ posX = imgPos.left * scaleX - ( ( x * scaleX ) - x );
+
+ if ( posX > 0 ) {
+ posX = 0;
+ }
+
+ if ( posX < canvasWidth - newImgWidth ) {
+ posX = canvasWidth - newImgWidth;
+ }
+ }
+
+ if ( newImgHeight > canvasHeight) {
+ posY = imgPos.top * scaleY - ( ( y * scaleY ) - y );
+
+ if ( posY > 0 ) {
+ posY = 0;
+ }
+
+ if ( posY < canvasHeight - newImgHeight ) {
+ posY = canvasHeight - newImgHeight;
+ }
+ }
+
+ self.updateCursor( newImgWidth, newImgHeight );
+
+ $.fancybox.animate( $what, {
+ top : posY,
+ left : posX,
+ scaleX : scaleX,
+ scaleY : scaleY
+ }, duration || 330, function() {
+ self.isAnimating = false;
+ });
+
+ // Stop slideshow
+ if ( self.SlideShow && self.SlideShow.isActive ) {
+ self.SlideShow.stop();
+ }
+ },
+
+
+ // Scale image to fit inside parent element
+ // ========================================
+
+ scaleToFit : function( duration ) {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+ var end;
+
+ if ( !( current.type == 'image' && !current.hasError) || !$what || self.isAnimating ) {
+ return;
+ }
+
+ $.fancybox.stop( $what );
+
+ self.isAnimating = true;
+
+ end = self.getFitPos( current );
+
+ self.updateCursor( end.width, end.height );
+
+ $.fancybox.animate( $what, {
+ top : end.top,
+ left : end.left,
+ scaleX : end.width / $what.width(),
+ scaleY : end.height / $what.height()
+ }, duration || 330, function() {
+ self.isAnimating = false;
+ });
+
+ },
+
+ // Calculate image size to fit inside viewport
+ // ===========================================
+
+ getFitPos : function( slide ) {
+ var self = this;
+ var $what = slide.$content;
+
+ var imgWidth = slide.width;
+ var imgHeight = slide.height;
+
+ var margin = slide.opts.margin;
+
+ var canvasWidth, canvasHeight, minRatio, width, height;
+
+ if ( !$what || !$what.length || ( !imgWidth && !imgHeight) ) {
+ return false;
+ }
+
+ // Convert "margin to CSS style: [ top, right, bottom, left ]
+ if ( $.type( margin ) === "number" ) {
+ margin = [ margin, margin ];
+ }
+
+ if ( margin.length == 2 ) {
+ margin = [ margin[0], margin[1], margin[0], margin[1] ];
+ }
+
+ // We can not use $slide width here, because it can have different diemensions while in transiton
+ canvasWidth = parseInt( self.$refs.stage.width(), 10 ) - ( margin[ 1 ] + margin[ 3 ] );
+ canvasHeight = parseInt( self.$refs.stage.height(), 10 ) - ( margin[ 0 ] + margin[ 2 ] );
+
+ minRatio = Math.min(1, canvasWidth / imgWidth, canvasHeight / imgHeight );
+
+ width = Math.floor( minRatio * imgWidth );
+ height = Math.floor( minRatio * imgHeight );
+
+ // Use floor rounding to make sure it really fits
+ return {
+ top : Math.floor( ( canvasHeight - height ) * 0.5 ) + margin[ 0 ],
+ left : Math.floor( ( canvasWidth - width ) * 0.5 ) + margin[ 3 ],
+ width : width,
+ height : height
+ };
+
+ },
+
+
+ // Update content size and position for all slides
+ // ==============================================
+
+ update : function() {
+ var self = this;
+
+ $.each( self.slides, function( key, slide ) {
+ self.updateSlide( slide );
+ });
+ },
+
+
+ // Update slide content position and size
+ // ======================================
+
+ updateSlide : function( slide, duration ) {
+ var self = this,
+ $what = slide && slide.$content;
+
+ if ( $what && ( slide.width || slide.height ) ) {
+ self.isAnimating = false;
+
+ $.fancybox.stop( $what );
+
+ $.fancybox.setTranslate( $what, self.getFitPos( slide ) );
+
+ if ( slide.pos === self.currPos ) {
+ self.updateCursor();
+ }
+ }
+
+ slide.$slide.trigger( 'refresh' );
+
+ self.trigger( 'onUpdate', slide );
+
+ },
+
+
+ // Horizontally center slide
+ // =========================
+
+ centerSlide : function( slide, duration ) {
+ var self = this, canvasWidth, pos;
+
+ if ( self.current ) {
+ canvasWidth = Math.round( slide.$slide.width() );
+ pos = slide.pos - self.current.pos;
+
+ $.fancybox.animate( slide.$slide, {
+ top : 0,
+ left : ( pos * canvasWidth ) + ( pos * slide.opts.gutter ),
+ opacity : 1
+ }, duration === undefined ? 0 : duration, null, false);
+ }
+ },
+
+
+ // Update cursor style depending if content can be zoomed
+ // ======================================================
+
+ updateCursor : function( nextWidth, nextHeight ) {
+
+ var self = this;
+ var isScaledDown;
+
+ var $container = self.$refs.container.removeClass( 'fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-drag fancybox-can-zoomOut' );
+
+ if ( !self.current || self.isClosing ) {
+ return;
+ }
+
+ if ( self.isZoomable() ) {
+
+ $container.addClass( 'fancybox-is-zoomable' );
+
+ if ( nextWidth !== undefined && nextHeight !== undefined ) {
+ isScaledDown = nextWidth < self.current.width && nextHeight < self.current.height;
+
+ } else {
+ isScaledDown = self.isScaledDown();
+ }
+
+ if ( isScaledDown ) {
+
+ // If image is scaled down, then, obviously, it can be zoomed to full size
+ $container.addClass( 'fancybox-can-zoomIn' );
+
+ } else {
+
+ if ( self.current.opts.touch ) {
+
+ // If image size ir largen than available available and touch module is not disable,
+ // then user can do panning
+ $container.addClass( 'fancybox-can-drag' );
+
+ } else {
+ $container.addClass( 'fancybox-can-zoomOut' );
+ }
+
+ }
+
+ } else if ( self.current.opts.touch ) {
+ $container.addClass( 'fancybox-can-drag' );
+ }
+
+ },
+
+
+ // Check if current slide is zoomable
+ // ==================================
+
+ isZoomable : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var fitPos;
+
+ if ( !current || self.isClosing ) {
+ return;
+ }
+
+ // Assume that slide is zoomable if
+ // - image is loaded successfuly
+ // - click action is "zoom"
+ // - actual size of the image is smaller than available area
+ if ( current.type === 'image' && current.isLoaded && !current.hasError &&
+ ( current.opts.clickContent === 'zoom' || ( $.isFunction( current.opts.clickContent ) && current.opts.clickContent( current ) === "zoom" ) )
+ ) {
+
+ fitPos = self.getFitPos( current );
+
+ if ( current.width > fitPos.width || current.height > fitPos.height ) {
+ return true;
+ }
+
+ }
+
+ return false;
+
+ },
+
+
+ // Check if current image dimensions are smaller than actual
+ // =========================================================
+
+ isScaledDown : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var rez = false;
+
+ if ( $what ) {
+ rez = $.fancybox.getTranslate( $what );
+ rez = rez.width < current.width || rez.height < current.height;
+ }
+
+ return rez;
+
+ },
+
+
+ // Check if image dimensions exceed parent element
+ // ===============================================
+
+ canPan : function() {
+
+ var self = this;
+
+ var current = self.current;
+ var $what = current.$content;
+
+ var rez = false;
+
+ if ( $what ) {
+ rez = self.getFitPos( current );
+ rez = Math.abs( $what.width() - rez.width ) > 1 || Math.abs( $what.height() - rez.height ) > 1;
+ }
+
+ return rez;
+
+ },
+
+
+ // Load content into the slide
+ // ===========================
+
+ loadSlide : function( slide ) {
+
+ var self = this, type, $slide;
+ var ajaxLoad;
+
+ if ( slide.isLoading ) {
+ return;
+ }
+
+ if ( slide.isLoaded ) {
+ return;
+ }
+
+ slide.isLoading = true;
+
+ self.trigger( 'beforeLoad', slide );
+
+ type = slide.type;
+ $slide = slide.$slide;
+
+ $slide
+ .off( 'refresh' )
+ .trigger( 'onReset' )
+ .addClass( 'fancybox-slide--' + ( type || 'unknown' ) )
+ .addClass( slide.opts.slideClass );
+
+ // Create content depending on the type
+
+ switch ( type ) {
+
+ case 'image':
+
+ self.setImage( slide );
+
+ break;
+
+ case 'iframe':
+
+ self.setIframe( slide );
+
+ break;
+
+ case 'html':
+
+ self.setContent( slide, slide.src || slide.content );
+
+ break;
+
+ case 'inline':
+
+ if ( $( slide.src ).length ) {
+ self.setContent( slide, $( slide.src ) );
+
+ } else {
+ self.setError( slide );
+ }
+
+ break;
+
+ case 'ajax':
+
+ self.showLoading( slide );
+
+ ajaxLoad = $.ajax( $.extend( {}, slide.opts.ajax.settings, {
+ url : slide.src,
+ success : function ( data, textStatus ) {
+
+ if ( textStatus === 'success' ) {
+ self.setContent( slide, data );
+ }
+
+ },
+ error : function ( jqXHR, textStatus ) {
+
+ if ( jqXHR && textStatus !== 'abort' ) {
+ self.setError( slide );
+ }
+
+ }
+ }));
+
+ $slide.one( 'onReset', function () {
+ ajaxLoad.abort();
+ });
+
+ break;
+
+ case 'video' :
+
+ self.setContent( slide,
+ ''
+ );
+
+ break;
+
+ default:
+
+ self.setError( slide );
+
+ break;
+
+ }
+
+ return true;
+
+ },
+
+
+ // Use thumbnail image, if possible
+ // ================================
+
+ setImage : function( slide ) {
+
+ var self = this;
+ var srcset = slide.opts.srcset || slide.opts.image.srcset;
+
+ var found, temp, pxRatio, windowWidth;
+
+ // If we have "srcset", then we need to find matching "src" value.
+ // This is necessary, because when you set an src attribute, the browser will preload the image
+ // before any javascript or even CSS is applied.
+ if ( srcset ) {
+ pxRatio = window.devicePixelRatio || 1;
+ windowWidth = window.innerWidth * pxRatio;
+
+ temp = srcset.split(',').map(function ( el ) {
+ var ret = {};
+
+ el.trim().split(/\s+/).forEach(function ( el, i ) {
+ var value = parseInt( el.substring(0, el.length - 1), 10 );
+
+ if ( i === 0 ) {
+ return ( ret.url = el );
+ }
+
+ if ( value ) {
+ ret.value = value;
+ ret.postfix = el[ el.length - 1 ];
+ }
+
+ });
+
+ return ret;
+ });
+
+ // Sort by value
+ temp.sort(function (a, b) {
+ return a.value - b.value;
+ });
+
+ // Ok, now we have an array of all srcset values
+ for ( var j = 0; j < temp.length; j++ ) {
+ var el = temp[ j ];
+
+ if ( ( el.postfix === 'w' && el.value >= windowWidth ) || ( el.postfix === 'x' && el.value >= pxRatio ) ) {
+ found = el;
+ break;
+ }
+ }
+
+ // If not found, take the last one
+ if ( !found && temp.length ) {
+ found = temp[ temp.length - 1 ];
+ }
+
+ if ( found ) {
+ slide.src = found.url;
+
+ // If we have default width/height values, we can calculate height for matching source
+ if ( slide.width && slide.height && found.postfix == 'w' ) {
+ slide.height = ( slide.width / slide.height ) * found.value;
+ slide.width = found.value;
+ }
+ }
+ }
+
+ // This will be wrapper containing both ghost and actual image
+ slide.$content = $('')
+ .addClass( 'fancybox-is-hidden' )
+ .appendTo( slide.$slide );
+
+
+ // If we have a thumbnail, we can display it while actual image is loading
+ // Users will not stare at black screen and actual image will appear gradually
+ if ( slide.opts.preload !== false && slide.opts.width && slide.opts.height && ( slide.opts.thumb || slide.opts.$thumb ) ) {
+
+ slide.width = slide.opts.width;
+ slide.height = slide.opts.height;
+
+ slide.$ghost = $('')
+ .one('error', function() {
+
+ $(this).remove();
+
+ slide.$ghost = null;
+
+ self.setBigImage( slide );
+
+ })
+ .one('load', function() {
+
+ self.afterLoad( slide );
+
+ self.setBigImage( slide );
+
+ })
+ .addClass( 'fancybox-image' )
+ .appendTo( slide.$content )
+ .attr( 'src', slide.opts.thumb || slide.opts.$thumb.attr( 'src' ) );
+
+ } else {
+
+ self.setBigImage( slide );
+
+ }
+
+ },
+
+
+ // Create full-size image
+ // ======================
+
+ setBigImage : function ( slide ) {
+ var self = this;
+ var $img = $('');
+
+ slide.$image = $img
+ .one('error', function() {
+
+ self.setError( slide );
+
+ })
+ .one('load', function() {
+
+ // Clear timeout that checks if loading icon needs to be displayed
+ clearTimeout( slide.timouts );
+
+ slide.timouts = null;
+
+ if ( self.isClosing ) {
+ return;
+ }
+
+ slide.width = slide.opts.width || this.naturalWidth;
+ slide.height = slide.opts.height || this.naturalHeight;
+
+ if ( slide.opts.image.srcset ) {
+ $img.attr( 'sizes', '100vw' ).attr( 'srcset', slide.opts.image.srcset );
+ }
+
+ self.hideLoading( slide );
+
+ if ( slide.$ghost ) {
+
+ slide.timouts = setTimeout(function() {
+ slide.timouts = null;
+
+ slide.$ghost.hide();
+
+ }, Math.min( 300, Math.max( 1000, slide.height / 1600 ) ) );
+
+ } else {
+ self.afterLoad( slide );
+ }
+
+ })
+ .addClass( 'fancybox-image' )
+ .attr('src', slide.src)
+ .appendTo( slide.$content );
+
+ if ( ( $img[0].complete || $img[0].readyState == "complete" ) && $img[0].naturalWidth && $img[0].naturalHeight ) {
+ $img.trigger( 'load' );
+
+ } else if( $img[0].error ) {
+ $img.trigger( 'error' );
+
+ } else {
+
+ slide.timouts = setTimeout(function() {
+ if ( !$img[0].complete && !slide.hasError ) {
+ self.showLoading( slide );
+ }
+
+ }, 100);
+
+ }
+
+ },
+
+
+ // Create iframe wrapper, iframe and bindings
+ // ==========================================
+
+ setIframe : function( slide ) {
+ var self = this,
+ opts = slide.opts.iframe,
+ $slide = slide.$slide,
+ $iframe;
+
+ slide.$content = $('')
+ .css( opts.css )
+ .appendTo( $slide );
+
+ $iframe = $( opts.tpl.replace(/\{rnd\}/g, new Date().getTime()) )
+ .attr( opts.attr )
+ .appendTo( slide.$content );
+
+ if ( opts.preload ) {
+
+ self.showLoading( slide );
+
+ // Unfortunately, it is not always possible to determine if iframe is successfully loaded
+ // (due to browser security policy)
+
+ $iframe.on('load.fb error.fb', function(e) {
+ this.isReady = 1;
+
+ slide.$slide.trigger( 'refresh' );
+
+ self.afterLoad( slide );
+ });
+
+ // Recalculate iframe content size
+ // ===============================
+
+ $slide.on('refresh.fb', function() {
+ var $wrap = slide.$content,
+ frameWidth = opts.css.width,
+ frameHeight = opts.css.height,
+ scrollWidth,
+ $contents,
+ $body;
+
+ if ( $iframe[0].isReady !== 1 ) {
+ return;
+ }
+
+ // Check if content is accessible,
+ // it will fail if frame is not with the same origin
+
+ try {
+ $contents = $iframe.contents();
+ $body = $contents.find('body');
+
+ } catch (ignore) {}
+
+ // Calculate dimensions for the wrapper
+ if ( $body && $body.length ) {
+
+ if ( frameWidth === undefined ) {
+ scrollWidth = $iframe[0].contentWindow.document.documentElement.scrollWidth;
+
+ frameWidth = Math.ceil( $body.outerWidth(true) + ( $wrap.width() - scrollWidth ) );
+ frameWidth += $wrap.outerWidth() - $wrap.innerWidth();
+ }
+
+ if ( frameHeight === undefined ) {
+ frameHeight = Math.ceil( $body.outerHeight(true) );
+ frameHeight += $wrap.outerHeight() - $wrap.innerHeight();
+ }
+
+ // Resize wrapper to fit iframe content
+ if ( frameWidth ) {
+ $wrap.width( frameWidth );
+ }
+
+ if ( frameHeight ) {
+ $wrap.height( frameHeight );
+ }
+ }
+
+ $wrap.removeClass( 'fancybox-is-hidden' );
+
+ });
+
+ } else {
+
+ this.afterLoad( slide );
+
+ }
+
+ $iframe.attr( 'src', slide.src );
+
+ if ( slide.opts.smallBtn === true ) {
+ slide.$content.prepend( self.translate( slide, slide.opts.btnTpl.smallBtn ) );
+ }
+
+ // Remove iframe if closing or changing gallery item
+ $slide.one( 'onReset', function () {
+
+ // This helps IE not to throw errors when closing
+ try {
+
+ $( this ).find( 'iframe' ).hide().attr( 'src', '//about:blank' );
+
+ } catch ( ignore ) {}
+
+ $( this ).empty();
+
+ slide.isLoaded = false;
+
+ });
+
+ },
+
+
+ // Wrap and append content to the slide
+ // ======================================
+
+ setContent : function ( slide, content ) {
+
+ var self = this;
+
+ if ( self.isClosing ) {
+ return;
+ }
+
+ self.hideLoading( slide );
+
+ slide.$slide.empty();
+
+ if ( isQuery( content ) && content.parent().length ) {
+
+ // If content is a jQuery object, then it will be moved to the slide.
+ // The placeholder is created so we will know where to put it back.
+ // If user is navigating gallery fast, then the content might be already inside fancyBox
+ // =====================================================================================
+
+ // Make sure content is not already moved to fancyBox
+ content.parent( '.fancybox-slide--inline' ).trigger( 'onReset' );
+
+ // Create temporary element marking original place of the content
+ slide.$placeholder = $( '' ).hide().insertAfter( content );
+
+ // Make sure content is visible
+ content.css('display', 'inline-block');
+
+ } else if ( !slide.hasError ) {
+
+ // If content is just a plain text, try to convert it to html
+ if ( $.type( content ) === 'string' ) {
+ content = $('
').append( $.trim( content ) ).contents();
+
+ // If we have text node, then add wrapping element to make vertical alignment work
+ if ( content[0].nodeType === 3 ) {
+ content = $('
').html( content );
+ }
+ }
+
+ // If "filter" option is provided, then filter content
+ if ( slide.opts.filter ) {
+ content = $('
').html( content ).find( slide.opts.filter );
+ }
+
+ }
+
+ slide.$slide.one('onReset', function () {
+
+ // Pause all html5 video/audio
+ $( this ).find( 'video,audio' ).trigger( 'pause' );
+
+ // Put content back
+ if ( slide.$placeholder ) {
+ slide.$placeholder.after( content.hide() ).remove();
+
+ slide.$placeholder = null;
+ }
+
+ // Remove custom close button
+ if ( slide.$smallBtn ) {
+ slide.$smallBtn.remove();
+
+ slide.$smallBtn = null;
+ }
+
+ // Remove content and mark slide as not loaded
+ if ( !slide.hasError ) {
+ $(this).empty();
+
+ slide.isLoaded = false;
+ }
+
+ });
+
+ slide.$content = $( content ).appendTo( slide.$slide );
+
+ this.afterLoad( slide );
+ },
+
+ // Display error message
+ // =====================
+
+ setError : function ( slide ) {
+
+ slide.hasError = true;
+
+ slide.$slide.removeClass( 'fancybox-slide--' + slide.type );
+
+ this.setContent( slide, this.translate( slide, slide.opts.errorTpl ) );
+
+ },
+
+
+ // Show loading icon inside the slide
+ // ==================================
+
+ showLoading : function( slide ) {
+
+ var self = this;
+
+ slide = slide || self.current;
+
+ if ( slide && !slide.$spinner ) {
+ slide.$spinner = $( self.opts.spinnerTpl ).appendTo( slide.$slide );
+ }
+
+ },
+
+ // Remove loading icon from the slide
+ // ==================================
+
+ hideLoading : function( slide ) {
+
+ var self = this;
+
+ slide = slide || self.current;
+
+ if ( slide && slide.$spinner ) {
+ slide.$spinner.remove();
+
+ delete slide.$spinner;
+ }
+
+ },
+
+
+ // Adjustments after slide content has been loaded
+ // ===============================================
+
+ afterLoad : function( slide ) {
+
+ var self = this;
+
+ if ( self.isClosing ) {
+ return;
+ }
+
+ slide.isLoading = false;
+ slide.isLoaded = true;
+
+ self.trigger( 'afterLoad', slide );
+
+ self.hideLoading( slide );
+
+ if ( slide.opts.smallBtn && !slide.$smallBtn ) {
+ slide.$smallBtn = $( self.translate( slide, slide.opts.btnTpl.smallBtn ) ).appendTo( slide.$content.filter('div,form').first() );
+ }
+
+ if ( slide.opts.protect && slide.$content && !slide.hasError ) {
+
+ // Disable right click
+ slide.$content.on( 'contextmenu.fb', function( e ) {
+ if ( e.button == 2 ) {
+ e.preventDefault();
+ }
+
+ return true;
+ });
+
+ // Add fake element on top of the image
+ // This makes a bit harder for user to select image
+ if ( slide.type === 'image' ) {
+ $( '' ).appendTo( slide.$content );
+ }
+
+ }
+
+ self.revealContent( slide );
+
+ },
+
+
+ // Make content visible
+ // This method is called right after content has been loaded or
+ // user navigates gallery and transition should start
+ // ============================================================
+
+ revealContent : function( slide ) {
+
+ var self = this;
+ var $slide = slide.$slide;
+
+ var effect, effectClassName, duration, opacity, end, start = false;
+
+ effect = slide.opts[ self.firstRun ? 'animationEffect' : 'transitionEffect' ];
+ duration = slide.opts[ self.firstRun ? 'animationDuration' : 'transitionDuration' ];
+
+ duration = parseInt( slide.forcedDuration === undefined ? duration : slide.forcedDuration, 10 );
+
+ if ( slide.isMoved || slide.pos !== self.currPos || !duration ) {
+ effect = false;
+ }
+
+ // Check if can zoom
+ if ( effect === 'zoom' && !( slide.pos === self.currPos && duration && slide.type === 'image' && !slide.hasError && ( start = self.getThumbPos( slide ) ) ) ) {
+ effect = 'fade';
+ }
+
+ // Zoom animation
+ // ==============
+
+ if ( effect === 'zoom' ) {
+ end = self.getFitPos( slide );
+
+ end.scaleX = end.width / start.width;
+ end.scaleY = end.height / start.height;
+
+ delete end.width;
+ delete end.height;
+
+ // Check if we need to animate opacity
+ opacity = slide.opts.zoomOpacity;
+
+ if ( opacity == 'auto' ) {
+ opacity = Math.abs( slide.width / slide.height - start.width / start.height ) > 0.1;
+ }
+
+ if ( opacity ) {
+ start.opacity = 0.1;
+ end.opacity = 1;
+ }
+
+ // Draw image at start position
+ $.fancybox.setTranslate( slide.$content.removeClass( 'fancybox-is-hidden' ), start );
+
+ forceRedraw( slide.$content );
+
+ // Start animation
+ $.fancybox.animate( slide.$content, end, duration, function() {
+ self.complete();
+ });
+
+ return;
+ }
+
+ self.updateSlide( slide );
+
+
+ // Simply show content
+ // ===================
+
+ if ( !effect ) {
+ forceRedraw( $slide );
+
+ slide.$content.removeClass( 'fancybox-is-hidden' );
+
+ if ( slide.pos === self.currPos ) {
+ self.complete();
+ }
+
+ return;
+ }
+
+ $.fancybox.stop( $slide );
+
+ effectClassName = 'fancybox-animated fancybox-slide--' + ( slide.pos >= self.prevPos ? 'next' : 'previous' ) + ' fancybox-fx-' + effect;
+
+ $slide.removeAttr( 'style' ).removeClass( 'fancybox-slide--current fancybox-slide--next fancybox-slide--previous' ).addClass( effectClassName );
+
+ slide.$content.removeClass( 'fancybox-is-hidden' );
+
+ //Force reflow for CSS3 transitions
+ forceRedraw( $slide );
+
+ $.fancybox.animate( $slide, 'fancybox-slide--current', duration, function(e) {
+ $slide.removeClass( effectClassName ).removeAttr( 'style' );
+
+ if ( slide.pos === self.currPos ) {
+ self.complete();
+ }
+
+ }, true);
+
+ },
+
+
+ // Check if we can and have to zoom from thumbnail
+ //================================================
+
+ getThumbPos : function( slide ) {
+
+ var self = this;
+ var rez = false;
+
+ // Check if element is inside the viewport by at least 1 pixel
+ var isElementVisible = function( $el ) {
+ var element = $el[0];
+
+ var elementRect = element.getBoundingClientRect();
+ var parentRects = [];
+
+ var visibleInAllParents;
+
+ while ( element.parentElement !== null ) {
+ if ( $(element.parentElement).css('overflow') === 'hidden' || $(element.parentElement).css('overflow') === 'auto' ) {
+ parentRects.push(element.parentElement.getBoundingClientRect());
+ }
+
+ element = element.parentElement;
+ }
+
+ visibleInAllParents = parentRects.every(function(parentRect){
+ var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
+ var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
+
+ return visiblePixelX > 0 && visiblePixelY > 0;
+ });
+
+ return visibleInAllParents &&
+ elementRect.bottom > 0 && elementRect.right > 0 &&
+ elementRect.left < $(window).width() && elementRect.top < $(window).height();
+ };
+
+ var $thumb = slide.opts.$thumb;
+ var thumbPos = $thumb ? $thumb.offset() : 0;
+ var slidePos;
+
+ if ( thumbPos && $thumb[0].ownerDocument === document && isElementVisible( $thumb ) ) {
+ slidePos = self.$refs.stage.offset();
+
+ rez = {
+ top : thumbPos.top - slidePos.top + parseFloat( $thumb.css( "border-top-width" ) || 0 ),
+ left : thumbPos.left - slidePos.left + parseFloat( $thumb.css( "border-left-width" ) || 0 ),
+ width : $thumb.width(),
+ height : $thumb.height(),
+ scaleX : 1,
+ scaleY : 1
+ };
+ }
+
+ return rez;
+ },
+
+
+ // Final adjustments after current gallery item is moved to position
+ // and it`s content is loaded
+ // ==================================================================
+
+ complete : function() {
+ var self = this,
+ current = self.current,
+ slides = {},
+ promise;
+
+ if ( current.isMoved || !current.isLoaded || current.isComplete ) {
+ return;
+ }
+
+ current.isComplete = true;
+
+ current.$slide.siblings().trigger( 'onReset' );
+
+ self.preload( 'inline' );
+
+ // Trigger any CSS3 transiton inside the slide
+ forceRedraw( current.$slide );
+
+ current.$slide.addClass( 'fancybox-slide--complete' );
+
+ // Remove unnecessary slides
+ $.each( self.slides, function( key, slide ) {
+ if ( slide.pos >= self.currPos - 1 && slide.pos <= self.currPos + 1 ) {
+ slides[ slide.pos ] = slide;
+
+ } else if ( slide ) {
+ $.fancybox.stop( slide.$slide );
+
+ slide.$slide.off().remove();
+ }
+ });
+
+ self.slides = slides;
+
+ self.updateCursor();
+
+ self.trigger( 'afterShow' );
+
+ // Play first html5 video/audio
+ current.$slide.find( 'video,audio' ).first().trigger( 'play' );
+
+ // Try to focus on the first focusable element
+ if ( $( document.activeElement ).is( '[disabled]' ) || ( current.opts.autoFocus && !( current.type == 'image' || current.type === 'iframe' ) ) ) {
+ self.focus();
+ }
+
+ },
+
+
+ // Preload next and previous slides
+ // ================================
+
+ preload : function( type ) {
+ var self = this,
+ next = self.slides[ self.currPos + 1 ],
+ prev = self.slides[ self.currPos - 1 ];
+
+ if ( next && next.type === type ) {
+ self.loadSlide( next );
+ }
+
+ if ( prev && prev.type === type ) {
+ self.loadSlide( prev );
+ }
+ },
+
+
+ // Try to find and focus on the first focusable element
+ // ====================================================
+
+ focus : function() {
+ var current = this.current;
+ var $el;
+
+ if ( this.isClosing ) {
+ return;
+ }
+
+ if ( current && current.isComplete ) {
+
+ // Look for first input with autofocus attribute
+ $el = current.$slide.find('input[autofocus]:enabled:visible:first');
+
+ if ( !$el.length ) {
+ $el = current.$slide.find('button,:input,[tabindex],a').filter(':enabled:visible:first');
+ }
+ }
+
+ $el = $el && $el.length ? $el : this.$refs.container;
+
+ $el.focus();
+ },
+
+
+ // Activates current instance - brings container to the front and enables keyboard,
+ // notifies other instances about deactivating
+ // =================================================================================
+
+ activate : function () {
+ var self = this;
+
+ // Deactivate all instances
+ $( '.fancybox-container' ).each(function () {
+ var instance = $(this).data( 'FancyBox' );
+
+ // Skip self and closing instances
+ if (instance && instance.id !== self.id && !instance.isClosing) {
+ instance.trigger( 'onDeactivate' );
+
+ instance.removeEvents();
+
+ instance.isVisible = false;
+ }
+
+ });
+
+ self.isVisible = true;
+
+ if ( self.current || self.isIdle ) {
+ self.update();
+
+ self.updateControls();
+ }
+
+ self.trigger( 'onActivate' );
+
+ self.addEvents();
+ },
+
+
+ // Start closing procedure
+ // This will start "zoom-out" animation if needed and clean everything up afterwards
+ // =================================================================================
+
+ close : function( e, d ) {
+
+ var self = this;
+ var current = self.current;
+
+ var effect, duration;
+ var $what, opacity, start, end;
+
+ var done = function() {
+ self.cleanUp( e );
+ };
+
+ if ( self.isClosing ) {
+ return false;
+ }
+
+ self.isClosing = true;
+
+ // If beforeClose callback prevents closing, make sure content is centered
+ if ( self.trigger( 'beforeClose', e ) === false ) {
+ self.isClosing = false;
+
+ requestAFrame(function() {
+ self.update();
+ });
+
+ return false;
+ }
+
+ // Remove all events
+ // If there are multiple instances, they will be set again by "activate" method
+ self.removeEvents();
+
+ if ( current.timouts ) {
+ clearTimeout( current.timouts );
+ }
+
+ $what = current.$content;
+ effect = current.opts.animationEffect;
+ duration = $.isNumeric( d ) ? d : ( effect ? current.opts.animationDuration : 0 );
+
+ // Remove other slides
+ current.$slide.off( transitionEnd ).removeClass( 'fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated' );
+
+ current.$slide.siblings().trigger( 'onReset' ).remove();
+
+ // Trigger animations
+ if ( duration ) {
+ self.$refs.container.removeClass( 'fancybox-is-open' ).addClass( 'fancybox-is-closing' );
+ }
+
+ // Clean up
+ self.hideLoading( current );
+
+ self.hideControls();
+
+ self.updateCursor();
+
+ // Check if possible to zoom-out
+ if ( effect === 'zoom' && !( e !== true && $what && duration && current.type === 'image' && !current.hasError && ( end = self.getThumbPos( current ) ) ) ) {
+ effect = 'fade';
+ }
+
+ if ( effect === 'zoom' ) {
+ $.fancybox.stop( $what );
+
+ start = $.fancybox.getTranslate( $what );
+
+ start.width = start.width * start.scaleX;
+ start.height = start.height * start.scaleY;
+
+ // Check if we need to animate opacity
+ opacity = current.opts.zoomOpacity;
+
+ if ( opacity == 'auto' ) {
+ opacity = Math.abs( current.width / current.height - end.width / end.height ) > 0.1;
+ }
+
+ if ( opacity ) {
+ end.opacity = 0;
+ }
+
+ start.scaleX = start.width / end.width;
+ start.scaleY = start.height / end.height;
+
+ start.width = end.width;
+ start.height = end.height;
+
+ $.fancybox.setTranslate( current.$content, start );
+
+ forceRedraw( current.$content );
+
+ $.fancybox.animate( current.$content, end, duration, done );
+
+ return true;
+ }
+
+ if ( effect && duration ) {
+
+ // If skip animation
+ if ( e === true ) {
+ setTimeout( done, duration );
+
+ } else {
+ $.fancybox.animate( current.$slide.removeClass( 'fancybox-slide--current' ), 'fancybox-animated fancybox-slide--previous fancybox-fx-' + effect, duration, done );
+ }
+
+ } else {
+ done();
+ }
+
+ return true;
+ },
+
+
+ // Final adjustments after removing the instance
+ // =============================================
+
+ cleanUp : function( e ) {
+ var self = this,
+ $body = $( 'body' ),
+ instance,
+ offset;
+
+ self.current.$slide.trigger( 'onReset' );
+
+ self.$refs.container.empty().remove();
+
+ self.trigger( 'afterClose', e );
+
+ // Place back focus
+ if ( self.$lastFocus && !!self.current.opts.backFocus ) {
+ self.$lastFocus.focus();
+ }
+
+ self.current = null;
+
+ // Check if there are other instances
+ instance = $.fancybox.getInstance();
+
+ if ( instance ) {
+ instance.activate();
+
+ } else {
+
+ $W.scrollTop( self.scrollTop ).scrollLeft( self.scrollLeft );
+
+ $body.removeClass( 'fancybox-active compensate-for-scrollbar' );
+
+ if ( $body.hasClass( 'fancybox-iosfix' ) ) {
+ offset = parseInt(document.body.style.top, 10);
+
+ $body.removeClass( 'fancybox-iosfix' ).css( 'top', '' ).scrollTop( offset * -1 );
+ }
+
+ $( '#fancybox-style-noscroll' ).remove();
+
+ }
+
+ },
+
+
+ // Call callback and trigger an event
+ // ==================================
+
+ trigger : function( name, slide ) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ self = this,
+ obj = slide && slide.opts ? slide : self.current,
+ rez;
+
+ if ( obj ) {
+ args.unshift( obj );
+
+ } else {
+ obj = self;
+ }
+
+ args.unshift( self );
+
+ if ( $.isFunction( obj.opts[ name ] ) ) {
+ rez = obj.opts[ name ].apply( obj, args );
+ }
+
+ if ( rez === false ) {
+ return rez;
+ }
+
+ if ( name === 'afterClose' || !self.$refs ) {
+ $D.trigger( name + '.fb', args );
+
+ } else {
+ self.$refs.container.trigger( name + '.fb', args );
+ }
+
+ },
+
+
+ // Update infobar values, navigation button states and reveal caption
+ // ==================================================================
+
+ updateControls : function ( force ) {
+
+ var self = this;
+
+ var current = self.current,
+ index = current.index,
+ caption = current.opts.caption,
+ $container = self.$refs.container,
+ $caption = self.$refs.caption;
+
+ // Recalculate content dimensions
+ current.$slide.trigger( 'refresh' );
+
+ self.$caption = caption && caption.length ? $caption.html( caption ) : null;
+
+ if ( !self.isHiddenControls && !self.isIdle ) {
+ self.showControls();
+ }
+
+ // Update info and navigation elements
+ $container.find('[data-fancybox-count]').html( self.group.length );
+ $container.find('[data-fancybox-index]').html( index + 1 );
+
+ $container.find('[data-fancybox-prev]').prop( 'disabled', ( !current.opts.loop && index <= 0 ) );
+ $container.find('[data-fancybox-next]').prop( 'disabled', ( !current.opts.loop && index >= self.group.length - 1 ) );
+
+ if ( current.type === 'image' ) {
+
+ // Update download button source
+ $container.find('[data-fancybox-download]').attr( 'href', current.opts.image.src || current.src ).show();
+
+ } else {
+ $container.find('[data-fancybox-download],[data-fancybox-zoom]').hide();
+ }
+ },
+
+ // Hide toolbar and caption
+ // ========================
+
+ hideControls : function () {
+
+ this.isHiddenControls = true;
+
+ this.$refs.container.removeClass( 'fancybox-show-infobar fancybox-show-toolbar fancybox-show-caption fancybox-show-nav' );
+
+ },
+
+ showControls : function() {
+ var self = this;
+ var opts = self.current ? self.current.opts : self.opts;
+ var $container = self.$refs.container;
+
+ self.isHiddenControls = false;
+ self.idleSecondsCounter = 0;
+
+ $container
+ .toggleClass( 'fancybox-show-toolbar', !!( opts.toolbar && opts.buttons ) )
+ .toggleClass( 'fancybox-show-infobar', !!( opts.infobar && self.group.length > 1 ) )
+ .toggleClass( 'fancybox-show-nav', !!( opts.arrows && self.group.length > 1 ) )
+ .toggleClass( 'fancybox-is-modal', !!opts.modal );
+
+ if ( self.$caption ) {
+ $container.addClass( 'fancybox-show-caption ');
+
+ } else {
+ $container.removeClass( 'fancybox-show-caption' );
+ }
+
+ },
+
+
+ // Toggle toolbar and caption
+ // ==========================
+
+ toggleControls : function() {
+ if ( this.isHiddenControls ) {
+ this.showControls();
+
+ } else {
+ this.hideControls();
+ }
+
+ },
+
+
+ });
+
+
+ $.fancybox = {
+
+ version : "{fancybox-version}",
+ defaults : defaults,
+
+
+ // Get current instance and execute a command.
+ //
+ // Examples of usage:
+ //
+ // $instance = $.fancybox.getInstance();
+ // $.fancybox.getInstance().jumpTo( 1 );
+ // $.fancybox.getInstance( 'jumpTo', 1 );
+ // $.fancybox.getInstance( function() {
+ // console.info( this.currIndex );
+ // });
+ // ======================================================
+
+ getInstance : function ( command ) {
+ var instance = $('.fancybox-container:not(".fancybox-is-closing"):last').data( 'FancyBox' );
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ if ( instance instanceof FancyBox ) {
+
+ if ( $.type( command ) === 'string' ) {
+ instance[ command ].apply( instance, args );
+
+ } else if ( $.type( command ) === 'function' ) {
+ command.apply( instance, args );
+ }
+
+ return instance;
+ }
+
+ return false;
+
+ },
+
+
+ // Create new instance
+ // ===================
+
+ open : function ( items, opts, index ) {
+ return new FancyBox( items, opts, index );
+ },
+
+
+ // Close current or all instances
+ // ==============================
+
+ close : function ( all ) {
+ var instance = this.getInstance();
+
+ if ( instance ) {
+ instance.close();
+
+ // Try to find and close next instance
+
+ if ( all === true ) {
+ this.close();
+ }
+ }
+
+ },
+
+ // Close instances and unbind all events
+ // ==============================
+
+ destroy : function() {
+
+ this.close( true );
+
+ $D.off( 'click.fb-start' );
+
+ },
+
+
+ // Try to detect mobile devices
+ // ============================
+
+ isMobile : document.createTouch !== undefined && /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
+
+
+ // Detect if 'translate3d' support is available
+ // ============================================
+
+ use3d : (function() {
+ var div = document.createElement('div');
+
+ return window.getComputedStyle && window.getComputedStyle( div ).getPropertyValue('transform') && !(document.documentMode && document.documentMode < 11);
+ }()),
+
+ // Helper function to get current visual state of an element
+ // returns array[ top, left, horizontal-scale, vertical-scale, opacity ]
+ // =====================================================================
+
+ getTranslate : function( $el ) {
+ var matrix;
+
+ if ( !$el || !$el.length ) {
+ return false;
+ }
+
+ matrix = $el.eq( 0 ).css('transform');
+
+ if ( matrix && matrix.indexOf( 'matrix' ) !== -1 ) {
+ matrix = matrix.split('(')[1];
+ matrix = matrix.split(')')[0];
+ matrix = matrix.split(',');
+ } else {
+ matrix = [];
+ }
+
+ if ( matrix.length ) {
+
+ // If IE
+ if ( matrix.length > 10 ) {
+ matrix = [ matrix[13], matrix[12], matrix[0], matrix[5] ];
+
+ } else {
+ matrix = [ matrix[5], matrix[4], matrix[0], matrix[3]];
+ }
+
+ matrix = matrix.map(parseFloat);
+
+ } else {
+ matrix = [ 0, 0, 1, 1 ];
+
+ var transRegex = /\.*translate\((.*)px,(.*)px\)/i;
+ var transRez = transRegex.exec( $el.eq( 0 ).attr('style') );
+
+ if ( transRez ) {
+ matrix[ 0 ] = parseFloat( transRez[2] );
+ matrix[ 1 ] = parseFloat( transRez[1] );
+ }
+ }
+
+ return {
+ top : matrix[ 0 ],
+ left : matrix[ 1 ],
+ scaleX : matrix[ 2 ],
+ scaleY : matrix[ 3 ],
+ opacity : parseFloat( $el.css('opacity') ),
+ width : $el.width(),
+ height : $el.height()
+ };
+
+ },
+
+
+ // Shortcut for setting "translate3d" properties for element
+ // Can set be used to set opacity, too
+ // ========================================================
+
+ setTranslate : function( $el, props ) {
+ var str = '';
+ var css = {};
+
+ if ( !$el || !props ) {
+ return;
+ }
+
+ if ( props.left !== undefined || props.top !== undefined ) {
+ str = ( props.left === undefined ? $el.position().left : props.left ) + 'px, ' + ( props.top === undefined ? $el.position().top : props.top ) + 'px';
+
+ if ( this.use3d ) {
+ str = 'translate3d(' + str + ', 0px)';
+
+ } else {
+ str = 'translate(' + str + ')';
+ }
+ }
+
+ if ( props.scaleX !== undefined && props.scaleY !== undefined ) {
+ str = (str.length ? str + ' ' : '') + 'scale(' + props.scaleX + ', ' + props.scaleY + ')';
+ }
+
+ if ( str.length ) {
+ css.transform = str;
+ }
+
+ if ( props.opacity !== undefined ) {
+ css.opacity = props.opacity;
+ }
+
+ if ( props.width !== undefined ) {
+ css.width = props.width;
+ }
+
+ if ( props.height !== undefined ) {
+ css.height = props.height;
+ }
+
+ return $el.css( css );
+ },
+
+
+ // Simple CSS transition handler
+ // =============================
+
+ animate : function ( $el, to, duration, callback, leaveAnimationName ) {
+ if ( $.isFunction( duration ) ) {
+ callback = duration;
+ duration = null;
+ }
+
+ if ( !$.isPlainObject( to ) ) {
+ $el.removeAttr( 'style' );
+ }
+
+ $el.on( transitionEnd, function(e) {
+
+ // Skip events from child elements and z-index change
+ if ( e && e.originalEvent && ( !$el.is( e.originalEvent.target ) || e.originalEvent.propertyName == 'z-index' ) ) {
+ return;
+ }
+
+ $.fancybox.stop( $el );
+
+ if ( $.isPlainObject( to ) ) {
+
+ if ( to.scaleX !== undefined && to.scaleY !== undefined ) {
+ $el.css( 'transition-duration', '' );
+
+ to.width = Math.round( $el.width() * to.scaleX );
+ to.height = Math.round( $el.height() * to.scaleY );
+
+ to.scaleX = 1;
+ to.scaleY = 1;
+
+ $.fancybox.setTranslate( $el, to );
+ }
+
+ if ( leaveAnimationName === false ) {
+ $el.removeAttr( 'style' );
+ }
+
+ } else if ( leaveAnimationName !== true ) {
+ $el.removeClass( to );
+ }
+
+ if ( $.isFunction( callback ) ) {
+ callback( e );
+ }
+
+ });
+
+ if ( $.isNumeric( duration ) ) {
+ $el.css( 'transition-duration', duration + 'ms' );
+ }
+
+ if ( $.isPlainObject( to ) ) {
+ $.fancybox.setTranslate( $el, to );
+
+ } else {
+ $el.addClass( to );
+ }
+
+ if ( to.scaleX && $el.hasClass( 'fancybox-image-wrap' ) ) {
+ $el.parent().addClass( 'fancybox-is-scaling' );
+ }
+
+ // Make sure that `transitionend` callback gets fired
+ $el.data("timer", setTimeout(function() {
+ $el.trigger( 'transitionend' );
+ }, duration + 16));
+
+ },
+
+ stop : function( $el ) {
+ clearTimeout( $el.data("timer") );
+
+ $el.off( 'transitionend' ).css( 'transition-duration', '' );
+
+ if ( $el.hasClass( 'fancybox-image-wrap' ) ) {
+ $el.parent().removeClass( 'fancybox-is-scaling' );
+ }
+ }
+
+ };
+
+
+ // Default click handler for "fancyboxed" links
+ // ============================================
+
+ function _run( e ) {
+ var $target = $( e.currentTarget ),
+ opts = e.data ? e.data.options : {},
+ value = $target.attr( 'data-fancybox' ) || '',
+ index = 0,
+ items = [];
+
+ // Avoid opening multiple times
+ if ( e.isDefaultPrevented() ) {
+ return;
+ }
+
+ e.preventDefault();
+
+ // Get all related items and find index for clicked one
+ if ( value ) {
+ items = opts.selector ? $( opts.selector ) : ( e.data ? e.data.items : [] );
+ items = items.length ? items.filter( '[data-fancybox="' + value + '"]' ) : $( '[data-fancybox="' + value + '"]' );
+
+ index = items.index( $target );
+
+ // Sometimes current item can not be found
+ // (for example, when slider clones items)
+ if ( index < 0 ) {
+ index = 0;
+ }
+
+ } else {
+ items = [ $target ];
+ }
+
+ $.fancybox.open( items, opts, index );
+ }
+
+
+ // Create a jQuery plugin
+ // ======================
+
+ $.fn.fancybox = function (options) {
+ var selector;
+
+ options = options || {};
+ selector = options.selector || false;
+
+ if ( selector ) {
+
+ $( 'body' ).off( 'click.fb-start', selector ).on( 'click.fb-start', selector, {
+ options : options
+ }, _run );
+
+ } else {
+
+ this.off( 'click.fb-start' ).on( 'click.fb-start', {
+ items : this,
+ options : options
+ }, _run);
+
+ }
+
+ return this;
+ };
+
+
+ // Self initializing plugin
+ // ========================
+
+ $D.on( 'click.fb-start', '[data-fancybox]', _run );
+
+}( window, document, window.jQuery || jQuery ));
diff --git a/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/fullscreen.js b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/fullscreen.js
new file mode 100644
index 0000000000..27143ef609
--- /dev/null
+++ b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/fullscreen.js
@@ -0,0 +1,210 @@
+// ==========================================================================
+//
+// FullScreen
+// Adds fullscreen functionality
+//
+// ==========================================================================
+;(function (document, $) {
+ 'use strict';
+
+ // Collection of methods supported by user browser
+ var fn = (function () {
+
+ var fnMap = [
+ [
+ 'requestFullscreen',
+ 'exitFullscreen',
+ 'fullscreenElement',
+ 'fullscreenEnabled',
+ 'fullscreenchange',
+ 'fullscreenerror'
+ ],
+ // new WebKit
+ [
+ 'webkitRequestFullscreen',
+ 'webkitExitFullscreen',
+ 'webkitFullscreenElement',
+ 'webkitFullscreenEnabled',
+ 'webkitfullscreenchange',
+ 'webkitfullscreenerror'
+
+ ],
+ // old WebKit (Safari 5.1)
+ [
+ 'webkitRequestFullScreen',
+ 'webkitCancelFullScreen',
+ 'webkitCurrentFullScreenElement',
+ 'webkitCancelFullScreen',
+ 'webkitfullscreenchange',
+ 'webkitfullscreenerror'
+
+ ],
+ [
+ 'mozRequestFullScreen',
+ 'mozCancelFullScreen',
+ 'mozFullScreenElement',
+ 'mozFullScreenEnabled',
+ 'mozfullscreenchange',
+ 'mozfullscreenerror'
+ ],
+ [
+ 'msRequestFullscreen',
+ 'msExitFullscreen',
+ 'msFullscreenElement',
+ 'msFullscreenEnabled',
+ 'MSFullscreenChange',
+ 'MSFullscreenError'
+ ]
+ ];
+
+ var val;
+ var ret = {};
+ var i, j;
+
+ for ( i = 0; i < fnMap.length; i++ ) {
+ val = fnMap[ i ];
+
+ if ( val && val[ 1 ] in document ) {
+ for ( j = 0; j < val.length; j++ ) {
+ ret[ fnMap[ 0 ][ j ] ] = val[ j ];
+ }
+
+ return ret;
+ }
+ }
+
+ return false;
+ })();
+
+ // If browser does not have Full Screen API, then simply unset default button template and stop
+ if ( !fn ) {
+
+ if ( $ && $.fancybox ) {
+ $.fancybox.defaults.btnTpl.fullScreen = false;
+ }
+
+ return;
+ }
+
+ var FullScreen = {
+
+ request : function ( elem ) {
+
+ elem = elem || document.documentElement;
+
+ elem[ fn.requestFullscreen ]( elem.ALLOW_KEYBOARD_INPUT );
+
+ },
+ exit : function () {
+
+ document[ fn.exitFullscreen ]();
+
+ },
+ toggle : function ( elem ) {
+
+ elem = elem || document.documentElement;
+
+ if ( this.isFullscreen() ) {
+ this.exit();
+
+ } else {
+ this.request( elem );
+ }
+
+ },
+ isFullscreen : function() {
+
+ return Boolean( document[ fn.fullscreenElement ] );
+
+ },
+ enabled : function() {
+
+ return Boolean( document[ fn.fullscreenEnabled ] );
+
+ }
+ };
+
+ $.extend(true, $.fancybox.defaults, {
+ btnTpl : {
+ fullScreen :
+ ''
+ },
+ fullScreen : {
+ autoStart : false
+ }
+ });
+
+ $(document).on({
+ 'onInit.fb' : function(e, instance) {
+ var $container;
+
+ if ( instance && instance.group[ instance.currIndex ].opts.fullScreen ) {
+ $container = instance.$refs.container;
+
+ $container.on('click.fb-fullscreen', '[data-fancybox-fullscreen]', function(e) {
+
+ e.stopPropagation();
+ e.preventDefault();
+
+ FullScreen.toggle( $container[ 0 ] );
+
+ });
+
+ if ( instance.opts.fullScreen && instance.opts.fullScreen.autoStart === true ) {
+ FullScreen.request( $container[ 0 ] );
+ }
+
+ // Expose API
+ instance.FullScreen = FullScreen;
+
+ } else if ( instance ) {
+ instance.$refs.toolbar.find('[data-fancybox-fullscreen]').hide();
+ }
+
+ },
+
+ 'afterKeydown.fb' : function(e, instance, current, keypress, keycode) {
+
+ // "P" or Spacebar
+ if ( instance && instance.FullScreen && keycode === 70 ) {
+ keypress.preventDefault();
+
+ instance.FullScreen.toggle( instance.$refs.container[ 0 ] );
+ }
+
+ },
+
+ 'beforeClose.fb' : function( instance ) {
+ if ( instance && instance.FullScreen ) {
+ FullScreen.exit();
+ }
+ }
+ });
+
+ $(document).on(fn.fullscreenchange, function() {
+ var isFullscreen = FullScreen.isFullscreen(),
+ instance = $.fancybox.getInstance();
+
+ if ( instance ) {
+
+ // If image is zooming, then force to stop and reposition properly
+ if ( instance.current && instance.current.type === 'image' && instance.isAnimating ) {
+ instance.current.$content.css( 'transition', 'none' );
+
+ instance.isAnimating = false;
+
+ instance.update( true, true, 0 );
+ }
+
+ instance.trigger( 'onFullscreenChange', isFullscreen );
+
+ instance.$refs.container.toggleClass( 'fancybox-is-fullscreen', isFullscreen );
+ }
+
+ });
+
+}( document, window.jQuery || jQuery ));
diff --git a/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/guestures.js b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/guestures.js
new file mode 100644
index 0000000000..cc113fe033
--- /dev/null
+++ b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/guestures.js
@@ -0,0 +1,919 @@
+// ==========================================================================
+//
+// Guestures
+// Adds touch guestures, handles click and tap events
+//
+// ==========================================================================
+;(function (window, document, $) {
+ 'use strict';
+
+ var requestAFrame = (function () {
+ return window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ // if all else fails, use setTimeout
+ function (callback) {
+ return window.setTimeout(callback, 1000 / 60);
+ };
+ })();
+
+ var cancelAFrame = (function () {
+ return window.cancelAnimationFrame ||
+ window.webkitCancelAnimationFrame ||
+ window.mozCancelAnimationFrame ||
+ window.oCancelAnimationFrame ||
+ function (id) {
+ window.clearTimeout(id);
+ };
+ })();
+
+ var pointers = function( e ) {
+ var result = [];
+
+ e = e.originalEvent || e || window.e;
+ e = e.touches && e.touches.length ? e.touches : ( e.changedTouches && e.changedTouches.length ? e.changedTouches : [ e ] );
+
+ for ( var key in e ) {
+
+ if ( e[ key ].pageX ) {
+ result.push( { x : e[ key ].pageX, y : e[ key ].pageY } );
+
+ } else if ( e[ key ].clientX ) {
+ result.push( { x : e[ key ].clientX, y : e[ key ].clientY } );
+ }
+ }
+
+ return result;
+ };
+
+ var distance = function( point2, point1, what ) {
+ if ( !point1 || !point2 ) {
+ return 0;
+ }
+
+ if ( what === 'x' ) {
+ return point2.x - point1.x;
+
+ } else if ( what === 'y' ) {
+ return point2.y - point1.y;
+ }
+
+ return Math.sqrt( Math.pow( point2.x - point1.x, 2 ) + Math.pow( point2.y - point1.y, 2 ) );
+ };
+
+ var isClickable = function( $el ) {
+ if ( $el.is('a,area,button,[role="button"],input,label,select,summary,textarea') || $.isFunction( $el.get(0).onclick ) || $el.data('selectable') ) {
+ return true;
+ }
+
+ // Check for attributes like data-fancybox-next or data-fancybox-close
+ for ( var i = 0, atts = $el[0].attributes, n = atts.length; i < n; i++ ) {
+ if ( atts[i].nodeName.substr(0, 14) === 'data-fancybox-' ) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ var hasScrollbars = function( el ) {
+ var overflowY = window.getComputedStyle( el )['overflow-y'];
+ var overflowX = window.getComputedStyle( el )['overflow-x'];
+
+ var vertical = (overflowY === 'scroll' || overflowY === 'auto') && el.scrollHeight > el.clientHeight;
+ var horizontal = (overflowX === 'scroll' || overflowX === 'auto') && el.scrollWidth > el.clientWidth;
+
+ return vertical || horizontal;
+ };
+
+ var isScrollable = function ( $el ) {
+ var rez = false;
+
+ while ( true ) {
+ rez = hasScrollbars( $el.get(0) );
+
+ if ( rez ) {
+ break;
+ }
+
+ $el = $el.parent();
+
+ if ( !$el.length || $el.hasClass( 'fancybox-stage' ) || $el.is( 'body' ) ) {
+ break;
+ }
+ }
+
+ return rez;
+ };
+
+
+ var Guestures = function ( instance ) {
+ var self = this;
+
+ self.instance = instance;
+
+ self.$bg = instance.$refs.bg;
+ self.$stage = instance.$refs.stage;
+ self.$container = instance.$refs.container;
+
+ self.destroy();
+
+ self.$container.on( 'touchstart.fb.touch mousedown.fb.touch', $.proxy(self, 'ontouchstart') );
+ };
+
+ Guestures.prototype.destroy = function() {
+ this.$container.off( '.fb.touch' );
+ };
+
+ Guestures.prototype.ontouchstart = function( e ) {
+ var self = this;
+
+ var $target = $( e.target );
+ var instance = self.instance;
+ var current = instance.current;
+ var $content = current.$content;
+
+ var isTouchDevice = ( e.type == 'touchstart' );
+
+ // Do not respond to both (touch and mouse) events
+ if ( isTouchDevice ) {
+ self.$container.off( 'mousedown.fb.touch' );
+ }
+
+ // Ignore right click
+ if ( e.originalEvent && e.originalEvent.button == 2 ) {
+ return;
+ }
+
+ // Ignore taping on links, buttons, input elements
+ if ( !$target.length || isClickable( $target ) || isClickable( $target.parent() ) ) {
+ return;
+ }
+
+ // Ignore clicks on the scrollbar
+ if ( !$target.is('img') && e.originalEvent.clientX > $target[0].clientWidth + $target.offset().left ) {
+ return;
+ }
+
+ // Ignore clicks while zooming or closing
+ if ( !current || self.instance.isAnimating || self.instance.isClosing ) {
+ e.stopPropagation();
+ e.preventDefault();
+
+ return;
+ }
+
+ self.realPoints = self.startPoints = pointers( e );
+
+ if ( !self.startPoints ) {
+ return;
+ }
+
+ e.stopPropagation();
+
+ self.startEvent = e;
+
+ self.canTap = true;
+ self.$target = $target;
+ self.$content = $content;
+ self.opts = current.opts.touch;
+
+ self.isPanning = false;
+ self.isSwiping = false;
+ self.isZooming = false;
+ self.isScrolling = false;
+
+ self.sliderStartPos = self.sliderLastPos || { top: 0, left: 0 };
+ self.contentStartPos = $.fancybox.getTranslate( self.$content );
+ self.contentLastPos = null;
+
+ self.startTime = new Date().getTime();
+ self.distanceX = self.distanceY = self.distance = 0;
+
+ self.canvasWidth = Math.round( current.$slide[0].clientWidth );
+ self.canvasHeight = Math.round( current.$slide[0].clientHeight );
+
+ $(document)
+ .off( '.fb.touch' )
+ .on( isTouchDevice ? 'touchend.fb.touch touchcancel.fb.touch' : 'mouseup.fb.touch mouseleave.fb.touch', $.proxy(self, "ontouchend"))
+ .on( isTouchDevice ? 'touchmove.fb.touch' : 'mousemove.fb.touch', $.proxy(self, "ontouchmove"));
+
+ if ( $.fancybox.isMobile ) {
+ document.addEventListener('scroll', self.onscroll, true);
+ }
+
+ if ( !(self.opts || instance.canPan() ) || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) {
+
+ // Prevent image ghosting while dragging
+ if ( $target.is('img') ) {
+ e.preventDefault();
+ }
+
+ return;
+ }
+
+ if ( !( $.fancybox.isMobile && ( isScrollable( $target ) || isScrollable( $target.parent() ) ) ) ) {
+ e.preventDefault();
+ }
+
+ if ( self.startPoints.length === 1 ) {
+ if ( current.type === 'image' && ( self.contentStartPos.width > self.canvasWidth + 1 || self.contentStartPos.height > self.canvasHeight + 1 ) ) {
+ $.fancybox.stop( self.$content );
+
+ self.$content.css( 'transition-duration', '' );
+
+ self.isPanning = true;
+
+ } else {
+ self.isSwiping = true;
+ }
+
+ self.$container.addClass( 'fancybox-controls--isGrabbing' );
+ }
+
+ if ( self.startPoints.length === 2 && !instance.isAnimating && !current.hasError && current.type === 'image' && ( current.isLoaded || current.$ghost ) ) {
+ self.canTap = false;
+ self.isSwiping = false;
+ self.isPanning = false;
+
+ self.isZooming = true;
+
+ $.fancybox.stop( self.$content );
+
+ self.$content.css( 'transition-duration', '' );
+
+ self.centerPointStartX = ( ( self.startPoints[0].x + self.startPoints[1].x ) * 0.5 ) - $(window).scrollLeft();
+ self.centerPointStartY = ( ( self.startPoints[0].y + self.startPoints[1].y ) * 0.5 ) - $(window).scrollTop();
+
+ self.percentageOfImageAtPinchPointX = ( self.centerPointStartX - self.contentStartPos.left ) / self.contentStartPos.width;
+ self.percentageOfImageAtPinchPointY = ( self.centerPointStartY - self.contentStartPos.top ) / self.contentStartPos.height;
+
+ self.startDistanceBetweenFingers = distance( self.startPoints[0], self.startPoints[1] );
+ }
+
+ };
+
+ Guestures.prototype.onscroll = function(e) {
+ self.isScrolling = true;
+ };
+
+ Guestures.prototype.ontouchmove = function( e ) {
+ var self = this,
+ $target = $(e.target);
+
+ if ( self.isScrolling || !( $target.is( self.$stage ) || self.$stage.find( $target ).length ) ) {
+ self.canTap = false;
+
+ return;
+ }
+
+ self.newPoints = pointers( e );
+
+ if ( !( self.opts || self.instance.canPan() ) || !self.newPoints || !self.newPoints.length ) {
+ return;
+ }
+
+ if ( !(self.isSwiping && self.isSwiping === true) ) {
+ e.preventDefault();
+ }
+
+ self.distanceX = distance( self.newPoints[0], self.startPoints[0], 'x' );
+ self.distanceY = distance( self.newPoints[0], self.startPoints[0], 'y' );
+
+ self.distance = distance( self.newPoints[0], self.startPoints[0] )
+
+ // Skip false ontouchmove events (Chrome)
+ if ( self.distance > 0 ) {
+ if ( self.isSwiping ) {
+ self.onSwipe(e);
+
+ } else if ( self.isPanning ) {
+ self.onPan();
+
+ } else if ( self.isZooming ) {
+ self.onZoom();
+ }
+ }
+
+ };
+
+ Guestures.prototype.onSwipe = function(e) {
+ var self = this,
+ swiping = self.isSwiping,
+ left = self.sliderStartPos.left || 0,
+ angle;
+
+ // If direction is not yet determined
+ if ( swiping === true ) {
+
+ // We need at least 10px distance to correctly calculate an angle
+ if ( Math.abs( self.distance ) > 10 ) {
+ self.canTap = false;
+
+ if ( self.instance.group.length < 2 && self.opts.vertical ) {
+ self.isSwiping = 'y';
+
+ } else if ( self.instance.isDragging || self.opts.vertical === false || ( self.opts.vertical === 'auto' && $( window ).width() > 800 ) ) {
+ self.isSwiping = 'x';
+
+ } else {
+ angle = Math.abs( Math.atan2( self.distanceY, self.distanceX ) * 180 / Math.PI );
+
+ self.isSwiping = ( angle > 45 && angle < 135 ) ? 'y' : 'x';
+ }
+
+ self.canTap = false;
+
+ if ( self.isSwiping === 'y' && $.fancybox.isMobile && ( isScrollable( self.$target ) || isScrollable( self.$target.parent() ) ) ) {
+ self.isScrolling = true;
+
+ return;
+ }
+
+ self.instance.isDragging = self.isSwiping;
+
+ // Reset points to avoid jumping, because we dropped first swipes to calculate the angle
+ self.startPoints = self.newPoints;
+
+ $.each(self.instance.slides, function( index, slide ) {
+ $.fancybox.stop( slide.$slide );
+
+ slide.$slide.css( 'transition-duration', '' );
+
+ slide.inTransition = false;
+
+ if ( slide.pos === self.instance.current.pos ) {
+ self.sliderStartPos.left = $.fancybox.getTranslate( slide.$slide ).left;
+ }
+ });
+
+ // Stop slideshow
+ if ( self.instance.SlideShow && self.instance.SlideShow.isActive ) {
+ self.instance.SlideShow.stop();
+ }
+ }
+
+ return;
+ }
+
+ // Sticky edges
+ if ( swiping == 'x' ) {
+ if ( self.distanceX > 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === 0 && !self.instance.current.opts.loop ) ) ) {
+ left = left + Math.pow( self.distanceX, 0.8 );
+
+ } else if ( self.distanceX < 0 && ( self.instance.group.length < 2 || ( self.instance.current.index === self.instance.group.length - 1 && !self.instance.current.opts.loop ) ) ) {
+ left = left - Math.pow( -self.distanceX, 0.8 );
+
+ } else {
+ left = left + self.distanceX;
+ }
+ }
+
+ self.sliderLastPos = {
+ top : swiping == 'x' ? 0 : self.sliderStartPos.top + self.distanceY,
+ left : left
+ };
+
+ if ( self.requestId ) {
+ cancelAFrame( self.requestId );
+
+ self.requestId = null;
+ }
+
+ self.requestId = requestAFrame(function() {
+
+ if ( self.sliderLastPos ) {
+ $.each(self.instance.slides, function( index, slide ) {
+ var pos = slide.pos - self.instance.currPos;
+
+ $.fancybox.setTranslate( slide.$slide, {
+ top : self.sliderLastPos.top,
+ left : self.sliderLastPos.left + ( pos * self.canvasWidth ) + ( pos * slide.opts.gutter )
+ });
+ });
+
+ self.$container.addClass( 'fancybox-is-sliding' );
+ }
+
+ });
+
+ };
+
+ Guestures.prototype.onPan = function() {
+ var self = this;
+
+ // Sometimes, when tapping causally, image can move a bit and that breaks double tapping
+ if ( distance( self.newPoints[0], self.realPoints[0] ) < ($.fancybox.isMobile ? 10 : 5) ) {
+ self.startPoints = self.newPoints;
+ return;
+ }
+
+ self.canTap = false;
+
+ self.contentLastPos = self.limitMovement();
+
+ if ( self.requestId ) {
+ cancelAFrame( self.requestId );
+
+ self.requestId = null;
+ }
+
+ self.requestId = requestAFrame(function() {
+ $.fancybox.setTranslate( self.$content, self.contentLastPos );
+ });
+ };
+
+ // Make panning sticky to the edges
+ Guestures.prototype.limitMovement = function() {
+ var self = this;
+
+ var canvasWidth = self.canvasWidth;
+ var canvasHeight = self.canvasHeight;
+
+ var distanceX = self.distanceX;
+ var distanceY = self.distanceY;
+
+ var contentStartPos = self.contentStartPos;
+
+ var currentOffsetX = contentStartPos.left;
+ var currentOffsetY = contentStartPos.top;
+
+ var currentWidth = contentStartPos.width;
+ var currentHeight = contentStartPos.height;
+
+ var minTranslateX, minTranslateY,
+ maxTranslateX, maxTranslateY,
+ newOffsetX, newOffsetY;
+
+ if ( currentWidth > canvasWidth ) {
+ newOffsetX = currentOffsetX + distanceX;
+
+ } else {
+ newOffsetX = currentOffsetX;
+ }
+
+ newOffsetY = currentOffsetY + distanceY;
+
+ // Slow down proportionally to traveled distance
+ minTranslateX = Math.max( 0, canvasWidth * 0.5 - currentWidth * 0.5 );
+ minTranslateY = Math.max( 0, canvasHeight * 0.5 - currentHeight * 0.5 );
+
+ maxTranslateX = Math.min( canvasWidth - currentWidth, canvasWidth * 0.5 - currentWidth * 0.5 );
+ maxTranslateY = Math.min( canvasHeight - currentHeight, canvasHeight * 0.5 - currentHeight * 0.5 );
+
+ if ( currentWidth > canvasWidth ) {
+
+ // ->
+ if ( distanceX > 0 && newOffsetX > minTranslateX ) {
+ newOffsetX = minTranslateX - 1 + Math.pow( -minTranslateX + currentOffsetX + distanceX, 0.8 ) || 0;
+ }
+
+ // <-
+ if ( distanceX < 0 && newOffsetX < maxTranslateX ) {
+ newOffsetX = maxTranslateX + 1 - Math.pow( maxTranslateX - currentOffsetX - distanceX, 0.8 ) || 0;
+ }
+
+ }
+
+ if ( currentHeight > canvasHeight ) {
+
+ // \/
+ if ( distanceY > 0 && newOffsetY > minTranslateY ) {
+ newOffsetY = minTranslateY - 1 + Math.pow(-minTranslateY + currentOffsetY + distanceY, 0.8 ) || 0;
+ }
+
+ // /\
+ if ( distanceY < 0 && newOffsetY < maxTranslateY ) {
+ newOffsetY = maxTranslateY + 1 - Math.pow ( maxTranslateY - currentOffsetY - distanceY, 0.8 ) || 0;
+ }
+
+ }
+
+ return {
+ top : newOffsetY,
+ left : newOffsetX,
+ scaleX : contentStartPos.scaleX,
+ scaleY : contentStartPos.scaleY
+ };
+
+ };
+
+ Guestures.prototype.limitPosition = function( newOffsetX, newOffsetY, newWidth, newHeight ) {
+ var self = this;
+
+ var canvasWidth = self.canvasWidth;
+ var canvasHeight = self.canvasHeight;
+
+ if ( newWidth > canvasWidth ) {
+ newOffsetX = newOffsetX > 0 ? 0 : newOffsetX;
+ newOffsetX = newOffsetX < canvasWidth - newWidth ? canvasWidth - newWidth : newOffsetX;
+
+ } else {
+
+ // Center horizontally
+ newOffsetX = Math.max( 0, canvasWidth / 2 - newWidth / 2 );
+
+ }
+
+ if ( newHeight > canvasHeight ) {
+ newOffsetY = newOffsetY > 0 ? 0 : newOffsetY;
+ newOffsetY = newOffsetY < canvasHeight - newHeight ? canvasHeight - newHeight : newOffsetY;
+
+ } else {
+
+ // Center vertically
+ newOffsetY = Math.max( 0, canvasHeight / 2 - newHeight / 2 );
+
+ }
+
+ return {
+ top : newOffsetY,
+ left : newOffsetX
+ };
+
+ };
+
+ Guestures.prototype.onZoom = function() {
+ var self = this;
+
+ // Calculate current distance between points to get pinch ratio and new width and height
+
+ var currentWidth = self.contentStartPos.width;
+ var currentHeight = self.contentStartPos.height;
+
+ var currentOffsetX = self.contentStartPos.left;
+ var currentOffsetY = self.contentStartPos.top;
+
+ var endDistanceBetweenFingers = distance( self.newPoints[0], self.newPoints[1] );
+
+ var pinchRatio = endDistanceBetweenFingers / self.startDistanceBetweenFingers;
+
+ var newWidth = Math.floor( currentWidth * pinchRatio );
+ var newHeight = Math.floor( currentHeight * pinchRatio );
+
+ // This is the translation due to pinch-zooming
+ var translateFromZoomingX = (currentWidth - newWidth) * self.percentageOfImageAtPinchPointX;
+ var translateFromZoomingY = (currentHeight - newHeight) * self.percentageOfImageAtPinchPointY;
+
+ //Point between the two touches
+
+ var centerPointEndX = ((self.newPoints[0].x + self.newPoints[1].x) / 2) - $(window).scrollLeft();
+ var centerPointEndY = ((self.newPoints[0].y + self.newPoints[1].y) / 2) - $(window).scrollTop();
+
+ // And this is the translation due to translation of the centerpoint
+ // between the two fingers
+
+ var translateFromTranslatingX = centerPointEndX - self.centerPointStartX;
+ var translateFromTranslatingY = centerPointEndY - self.centerPointStartY;
+
+ // The new offset is the old/current one plus the total translation
+
+ var newOffsetX = currentOffsetX + ( translateFromZoomingX + translateFromTranslatingX );
+ var newOffsetY = currentOffsetY + ( translateFromZoomingY + translateFromTranslatingY );
+
+ var newPos = {
+ top : newOffsetY,
+ left : newOffsetX,
+ scaleX : self.contentStartPos.scaleX * pinchRatio,
+ scaleY : self.contentStartPos.scaleY * pinchRatio
+ };
+
+ self.canTap = false;
+
+ self.newWidth = newWidth;
+ self.newHeight = newHeight;
+
+ self.contentLastPos = newPos;
+
+ if ( self.requestId ) {
+ cancelAFrame( self.requestId );
+
+ self.requestId = null;
+ }
+
+ self.requestId = requestAFrame(function() {
+ $.fancybox.setTranslate( self.$content, self.contentLastPos );
+ });
+
+ };
+
+ Guestures.prototype.ontouchend = function( e ) {
+ var self = this;
+ var dMs = Math.max( (new Date().getTime() ) - self.startTime, 1);
+
+ var swiping = self.isSwiping;
+ var panning = self.isPanning;
+ var zooming = self.isZooming;
+ var scrolling = self.isScrolling;
+
+ self.endPoints = pointers( e );
+
+ self.$container.removeClass( 'fancybox-controls--isGrabbing' );
+
+ $(document).off( '.fb.touch' );
+
+ document.removeEventListener('scroll', self.onscroll, true);
+
+ if ( self.requestId ) {
+ cancelAFrame( self.requestId );
+
+ self.requestId = null;
+ }
+
+ self.isSwiping = false;
+ self.isPanning = false;
+ self.isZooming = false;
+ self.isScrolling = false;
+
+ self.instance.isDragging = false;
+
+ if ( self.canTap ) {
+ return self.onTap( e );
+ }
+
+ self.speed = 366;
+
+ // Speed in px/ms
+ self.velocityX = self.distanceX / dMs * 0.5;
+ self.velocityY = self.distanceY / dMs * 0.5;
+
+ self.speedX = Math.max( self.speed * 0.5, Math.min( self.speed * 1.5, ( 1 / Math.abs( self.velocityX ) ) * self.speed ) );
+
+ if ( panning ) {
+ self.endPanning();
+
+ } else if ( zooming ) {
+ self.endZooming();
+
+ } else {
+ self.endSwiping( swiping, scrolling );
+ }
+
+ return;
+ };
+
+ Guestures.prototype.endSwiping = function( swiping, scrolling ) {
+ var self = this,
+ ret = false,
+ len = self.instance.group.length;
+
+ self.sliderLastPos = null;
+
+ // Close if swiped vertically / navigate if horizontally
+ if ( swiping == 'y' && !scrolling && Math.abs( self.distanceY ) > 50 ) {
+
+ // Continue vertical movement
+ $.fancybox.animate( self.instance.current.$slide, {
+ top : self.sliderStartPos.top + self.distanceY + ( self.velocityY * 150 ),
+ opacity : 0
+ }, 150 );
+
+ ret = self.instance.close( true, 300 );
+
+ } else if ( swiping == 'x' && self.distanceX > 50 && len > 1 ) {
+ ret = self.instance.previous( self.speedX );
+
+ } else if ( swiping == 'x' && self.distanceX < -50 && len > 1 ) {
+ ret = self.instance.next( self.speedX );
+ }
+
+ if ( ret === false && ( swiping == 'x' || swiping == 'y' ) ) {
+ if ( scrolling || len < 2 ) {
+ self.instance.centerSlide( self.instance.current, 150 );
+ } else {
+ self.instance.jumpTo( self.instance.current.index );
+ }
+ }
+
+ self.$container.removeClass( 'fancybox-is-sliding' );
+
+ };
+
+ // Limit panning from edges
+ // ========================
+
+ Guestures.prototype.endPanning = function() {
+
+ var self = this;
+ var newOffsetX, newOffsetY, newPos;
+
+ if ( !self.contentLastPos ) {
+ return;
+ }
+
+ if ( self.opts.momentum === false ) {
+ newOffsetX = self.contentLastPos.left;
+ newOffsetY = self.contentLastPos.top;
+
+ } else {
+
+ // Continue movement
+ newOffsetX = self.contentLastPos.left + ( self.velocityX * self.speed );
+ newOffsetY = self.contentLastPos.top + ( self.velocityY * self.speed );
+ }
+
+ newPos = self.limitPosition( newOffsetX, newOffsetY, self.contentStartPos.width, self.contentStartPos.height );
+
+ newPos.width = self.contentStartPos.width;
+ newPos.height = self.contentStartPos.height;
+
+ $.fancybox.animate( self.$content, newPos, 330 );
+ };
+
+
+ Guestures.prototype.endZooming = function() {
+ var self = this;
+
+ var current = self.instance.current;
+
+ var newOffsetX, newOffsetY, newPos, reset;
+
+ var newWidth = self.newWidth;
+ var newHeight = self.newHeight;
+
+ if ( !self.contentLastPos ) {
+ return;
+ }
+
+ newOffsetX = self.contentLastPos.left;
+ newOffsetY = self.contentLastPos.top;
+
+ reset = {
+ top : newOffsetY,
+ left : newOffsetX,
+ width : newWidth,
+ height : newHeight,
+ scaleX : 1,
+ scaleY : 1
+ };
+
+ // Reset scalex/scaleY values; this helps for perfomance and does not break animation
+ $.fancybox.setTranslate( self.$content, reset );
+
+ if ( newWidth < self.canvasWidth && newHeight < self.canvasHeight ) {
+ self.instance.scaleToFit( 150 );
+
+ } else if ( newWidth > current.width || newHeight > current.height ) {
+ self.instance.scaleToActual( self.centerPointStartX, self.centerPointStartY, 150 );
+
+ } else {
+
+ newPos = self.limitPosition( newOffsetX, newOffsetY, newWidth, newHeight );
+
+ // Switch from scale() to width/height or animation will not work correctly
+ $.fancybox.setTranslate( self.content, $.fancybox.getTranslate( self.$content ) );
+
+ $.fancybox.animate( self.$content, newPos, 150 );
+ }
+
+ };
+
+ Guestures.prototype.onTap = function(e) {
+ var self = this;
+ var $target = $( e.target );
+
+ var instance = self.instance;
+ var current = instance.current;
+
+ var endPoints = ( e && pointers( e ) ) || self.startPoints;
+
+ var tapX = endPoints[0] ? endPoints[0].x - self.$stage.offset().left : 0;
+ var tapY = endPoints[0] ? endPoints[0].y - self.$stage.offset().top : 0;
+
+ var where;
+
+ var process = function ( prefix ) {
+
+ var action = current.opts[ prefix ];
+
+ if ( $.isFunction( action ) ) {
+ action = action.apply( instance, [ current, e ] );
+ }
+
+ if ( !action) {
+ return;
+ }
+
+ switch ( action ) {
+
+ case "close" :
+
+ instance.close( self.startEvent );
+
+ break;
+
+ case "toggleControls" :
+
+ instance.toggleControls( true );
+
+ break;
+
+ case "next" :
+
+ instance.next();
+
+ break;
+
+ case "nextOrClose" :
+
+ if ( instance.group.length > 1 ) {
+ instance.next();
+
+ } else {
+ instance.close( self.startEvent );
+ }
+
+ break;
+
+ case "zoom" :
+
+ if ( current.type == 'image' && ( current.isLoaded || current.$ghost ) ) {
+
+ if ( instance.canPan() ) {
+ instance.scaleToFit();
+
+ } else if ( instance.isScaledDown() ) {
+ instance.scaleToActual( tapX, tapY );
+
+ } else if ( instance.group.length < 2 ) {
+ instance.close( self.startEvent );
+ }
+ }
+
+ break;
+ }
+
+ };
+
+ // Ignore right click
+ if ( e.originalEvent && e.originalEvent.button == 2 ) {
+ return;
+ }
+
+ // Skip if clicked on the scrollbar
+ if ( !$target.is('img') && tapX > $target[0].clientWidth + $target.offset().left ) {
+ return;
+ }
+
+ // Check where is clicked
+ if ( $target.is( '.fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container' ) ) {
+ where = 'Outside';
+
+ } else if ( $target.is( '.fancybox-slide' ) ) {
+ where = 'Slide';
+
+ } else if ( instance.current.$content && instance.current.$content.find( $target ).addBack().filter( $target ).length ) {
+ where = 'Content';
+
+ } else {
+ return;
+ }
+
+ // Check if this is a double tap
+ if ( self.tapped ) {
+
+ // Stop previously created single tap
+ clearTimeout( self.tapped );
+ self.tapped = null;
+
+ // Skip if distance between taps is too big
+ if ( Math.abs( tapX - self.tapX ) > 50 || Math.abs( tapY - self.tapY ) > 50 ) {
+ return this;
+ }
+
+ // OK, now we assume that this is a double-tap
+ process( 'dblclick' + where );
+
+ } else {
+
+ // Single tap will be processed if user has not clicked second time within 300ms
+ // or there is no need to wait for double-tap
+ self.tapX = tapX;
+ self.tapY = tapY;
+
+ if ( current.opts[ 'dblclick' + where ] && current.opts[ 'dblclick' + where ] !== current.opts[ 'click' + where ] ) {
+
+ self.tapped = setTimeout(function() {
+ self.tapped = null;
+
+ process( 'click' + where );
+
+ }, 500);
+
+ } else {
+ process( 'click' + where );
+ }
+
+ }
+
+ return this;
+ };
+
+ $(document).on('onActivate.fb', function (e, instance) {
+ if ( instance && !instance.Guestures ) {
+ instance.Guestures = new Guestures( instance );
+ }
+ });
+
+}( window, document, window.jQuery || jQuery ));
diff --git a/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/hash.js b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/hash.js
new file mode 100644
index 0000000000..42287006d5
--- /dev/null
+++ b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/hash.js
@@ -0,0 +1,218 @@
+// ==========================================================================
+//
+// Hash
+// Enables linking to each modal
+//
+// ==========================================================================
+;(function (document, window, $) {
+ 'use strict';
+
+ // Simple $.escapeSelector polyfill (for jQuery prior v3)
+ if ( !$.escapeSelector ) {
+ $.escapeSelector = function( sel ) {
+ var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;
+ var fcssescape = function( ch, asCodePoint ) {
+ if ( asCodePoint ) {
+ // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
+ if ( ch === "\0" ) {
+ return "\uFFFD";
+ }
+
+ // Control characters and (dependent upon position) numbers get escaped as code points
+ return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
+ }
+
+ // Other potentially-special ASCII characters get backslash-escaped
+ return "\\" + ch;
+ };
+
+ return ( sel + "" ).replace( rcssescape, fcssescape );
+ };
+ }
+
+ // Create new history entry only once
+ var shouldCreateHistory = true;
+
+ // Variable containing last hash value set by fancyBox
+ // It will be used to determine if fancyBox needs to close after hash change is detected
+ var currentHash = null;
+
+ // Throttling the history change
+ var timerID = null;
+
+ // Get info about gallery name and current index from url
+ function parseUrl() {
+ var hash = window.location.hash.substr( 1 );
+ var rez = hash.split( '-' );
+ var index = rez.length > 1 && /^\+?\d+$/.test( rez[ rez.length - 1 ] ) ? parseInt( rez.pop( -1 ), 10 ) || 1 : 1;
+ var gallery = rez.join( '-' );
+
+ // Index is starting from 1
+ if ( index < 1 ) {
+ index = 1;
+ }
+
+ return {
+ hash : hash,
+ index : index,
+ gallery : gallery
+ };
+ }
+
+ // Trigger click evnt on links to open new fancyBox instance
+ function triggerFromUrl( url ) {
+ var $el;
+
+ if ( url.gallery !== '' ) {
+
+ // If we can find element matching 'data-fancybox' atribute, then trigger click event for that ..
+ $el = $( "[data-fancybox='" + $.escapeSelector( url.gallery ) + "']" ).eq( url.index - 1 );
+
+ if ( !$el.length ) {
+ // .. if not, try finding element by ID
+ $el = $( "#" + $.escapeSelector( url.gallery ) + "" );
+ }
+
+ if ( $el.length ) {
+ shouldCreateHistory = false;
+
+ $el.trigger( 'click' );
+ }
+
+ }
+ }
+
+ // Get gallery name from current instance
+ function getGalleryID( instance ) {
+ var opts;
+
+ if ( !instance ) {
+ return false;
+ }
+
+ opts = instance.current ? instance.current.opts : instance.opts;
+
+ return opts.hash || ( opts.$orig ? opts.$orig.data( 'fancybox' ) : '' );
+ }
+
+ // Start when DOM becomes ready
+ $(function() {
+
+ // Check if user has disabled this module
+ if ( $.fancybox.defaults.hash === false ) {
+ return;
+ }
+
+ // Update hash when opening/closing fancyBox
+ $(document).on({
+ 'onInit.fb' : function( e, instance ) {
+ var url, gallery;
+
+ if ( instance.group[ instance.currIndex ].opts.hash === false ) {
+ return;
+ }
+
+ url = parseUrl();
+ gallery = getGalleryID( instance );
+
+ // Make sure gallery start index matches index from hash
+ if ( gallery && url.gallery && gallery == url.gallery ) {
+ instance.currIndex = url.index - 1;
+ }
+ },
+
+ 'beforeShow.fb' : function( e, instance, current ) {
+ var gallery;
+
+ if ( !current || current.opts.hash === false ) {
+ return;
+ }
+
+ gallery = getGalleryID( instance );
+
+ // Update window hash
+ if ( gallery && gallery !== '' ) {
+
+ if ( window.location.hash.indexOf( gallery ) < 0 ) {
+ instance.opts.origHash = window.location.hash;
+ }
+
+ currentHash = gallery + ( instance.group.length > 1 ? '-' + ( current.index + 1 ) : '' );
+
+ if ( 'replaceState' in window.history ) {
+ if ( timerID ) {
+ clearTimeout( timerID );
+ }
+
+ timerID = setTimeout(function() {
+ window.history[ shouldCreateHistory ? 'pushState' : 'replaceState' ]( {} , document.title, window.location.pathname + window.location.search + '#' + currentHash );
+
+ timerID = null;
+
+ shouldCreateHistory = false;
+
+ }, 300);
+
+ } else {
+ window.location.hash = currentHash;
+ }
+
+ }
+
+ },
+
+ 'beforeClose.fb' : function( e, instance, current ) {
+ var gallery, origHash;
+
+ if ( timerID ) {
+ clearTimeout( timerID );
+ }
+
+ if ( current.opts.hash === false ) {
+ return;
+ }
+
+ gallery = getGalleryID( instance );
+ origHash = instance && instance.opts.origHash ? instance.opts.origHash : '';
+
+ // Remove hash from location bar
+ if ( gallery && gallery !== '' ) {
+
+ if ( 'replaceState' in history ) {
+ window.history.replaceState( {} , document.title, window.location.pathname + window.location.search + origHash );
+
+ } else {
+ window.location.hash = origHash;
+
+ // Keep original scroll position
+ $( window ).scrollTop( instance.scrollTop ).scrollLeft( instance.scrollLeft );
+ }
+ }
+
+ currentHash = null;
+ }
+ });
+
+ // Check if need to close after url has changed
+ $(window).on('hashchange.fb', function() {
+ var url = parseUrl();
+
+ if ( $.fancybox.getInstance() ) {
+ if ( currentHash && currentHash !== url.gallery + '-' + url.index && !( url.index === 1 && currentHash == url.gallery ) ) {
+ currentHash = null;
+
+ $.fancybox.close();
+ }
+
+ } else if ( url.gallery !== '' ) {
+ triggerFromUrl( url );
+ }
+ });
+
+ // Check current hash and trigger click event on matching element to start fancyBox, if needed
+ setTimeout(function() {
+ triggerFromUrl( parseUrl() );
+ }, 50);
+ });
+
+}( document, window, window.jQuery || jQuery ));
diff --git a/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/media.js b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/media.js
new file mode 100644
index 0000000000..5c3820bc38
--- /dev/null
+++ b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/media.js
@@ -0,0 +1,211 @@
+// ==========================================================================
+//
+// Media
+// Adds additional media type support
+//
+// ==========================================================================
+;(function ($) {
+
+ 'use strict';
+
+ // Formats matching url to final form
+
+ var format = function (url, rez, params) {
+ if ( !url ) {
+ return;
+ }
+
+ params = params || '';
+
+ if ( $.type(params) === "object" ) {
+ params = $.param(params, true);
+ }
+
+ $.each(rez, function (key, value) {
+ url = url.replace('$' + key, value || '');
+ });
+
+ if (params.length) {
+ url += (url.indexOf('?') > 0 ? '&' : '?') + params;
+ }
+
+ return url;
+ };
+
+ // Object containing properties for each media type
+
+ var defaults = {
+ youtube : {
+ matcher : /(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,
+ params : {
+ autoplay : 1,
+ autohide : 1,
+ fs : 1,
+ rel : 0,
+ hd : 1,
+ wmode : 'transparent',
+ enablejsapi : 1,
+ html5 : 1
+ },
+ paramPlace : 8,
+ type : 'iframe',
+ url : '//www.youtube.com/embed/$4',
+ thumb : '//img.youtube.com/vi/$4/hqdefault.jpg'
+ },
+
+ vimeo : {
+ matcher : /^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,
+ params : {
+ autoplay : 1,
+ hd : 1,
+ show_title : 1,
+ show_byline : 1,
+ show_portrait : 0,
+ fullscreen : 1,
+ api : 1
+ },
+ paramPlace : 3,
+ type : 'iframe',
+ url : '//player.vimeo.com/video/$2'
+ },
+
+ metacafe : {
+ matcher : /metacafe.com\/watch\/(\d+)\/(.*)?/,
+ type : 'iframe',
+ url : '//www.metacafe.com/embed/$1/?ap=1'
+ },
+
+ dailymotion : {
+ matcher : /dailymotion.com\/video\/(.*)\/?(.*)/,
+ params : {
+ additionalInfos : 0,
+ autoStart : 1
+ },
+ type : 'iframe',
+ url : '//www.dailymotion.com/embed/video/$1'
+ },
+
+ vine : {
+ matcher : /vine.co\/v\/([a-zA-Z0-9\?\=\-]+)/,
+ type : 'iframe',
+ url : '//vine.co/v/$1/embed/simple'
+ },
+
+ instagram : {
+ matcher : /(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,
+ type : 'image',
+ url : '//$1/p/$2/media/?size=l'
+ },
+
+ // Examples:
+ // http://maps.google.com/?ll=48.857995,2.294297&spn=0.007666,0.021136&t=m&z=16
+ // https://www.google.com/maps/@37.7852006,-122.4146355,14.65z
+ // https://www.google.com/maps/place/Googleplex/@37.4220041,-122.0833494,17z/data=!4m5!3m4!1s0x0:0x6c296c66619367e0!8m2!3d37.4219998!4d-122.0840572
+ gmap_place : {
+ matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,
+ type : 'iframe',
+ url : function (rez) {
+ return '//maps.google.' + rez[2] + '/?ll=' + ( rez[9] ? rez[9] + '&z=' + Math.floor( rez[10] ) + ( rez[12] ? rez[12].replace(/^\//, "&") : '' ) : rez[12] ) + '&output=' + ( rez[12] && rez[12].indexOf('layer=c') > 0 ? 'svembed' : 'embed' );
+ }
+ },
+
+ // Examples:
+ // https://www.google.com/maps/search/Empire+State+Building/
+ // https://www.google.com/maps/search/?api=1&query=centurylink+field
+ // https://www.google.com/maps/search/?api=1&query=47.5951518,-122.3316393
+ gmap_search : {
+ matcher : /(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,
+ type : 'iframe',
+ url : function (rez) {
+ return '//maps.google.' + rez[2] + '/maps?q=' + rez[5].replace('query=', 'q=').replace('api=1', '') + '&output=embed';
+ }
+ }
+ };
+
+ $(document).on('objectNeedsType.fb', function (e, instance, item) {
+
+ var url = item.src || '',
+ type = false,
+ media,
+ thumb,
+ rez,
+ params,
+ urlParams,
+ paramObj,
+ provider;
+
+ media = $.extend( true, {}, defaults, item.opts.media );
+
+ // Look for any matching media type
+ $.each(media, function ( providerName, providerOpts ) {
+ rez = url.match( providerOpts.matcher );
+
+ if ( !rez ) {
+ return;
+ }
+
+ type = providerOpts.type;
+ paramObj = {};
+
+ if ( providerOpts.paramPlace && rez[ providerOpts.paramPlace ] ) {
+ urlParams = rez[ providerOpts.paramPlace ];
+
+ if ( urlParams[ 0 ] == '?' ) {
+ urlParams = urlParams.substring(1);
+ }
+
+ urlParams = urlParams.split('&');
+
+ for ( var m = 0; m < urlParams.length; ++m ) {
+ var p = urlParams[ m ].split('=', 2);
+
+ if ( p.length == 2 ) {
+ paramObj[ p[0] ] = decodeURIComponent( p[1].replace(/\+/g, " ") );
+ }
+ }
+ }
+
+ params = $.extend( true, {}, providerOpts.params, item.opts[ providerName ], paramObj );
+
+ url = $.type( providerOpts.url ) === "function" ? providerOpts.url.call( this, rez, params, item ) : format( providerOpts.url, rez, params );
+ thumb = $.type( providerOpts.thumb ) === "function" ? providerOpts.thumb.call( this, rez, params, item ) : format( providerOpts.thumb, rez );
+
+ if ( providerName === 'vimeo' ) {
+ url = url.replace('&%23', '#');
+ }
+
+ return false;
+ });
+
+ // If it is found, then change content type and update the url
+
+ if ( type ) {
+ item.src = url;
+ item.type = type;
+
+ if ( !item.opts.thumb && !( item.opts.$thumb && item.opts.$thumb.length ) ) {
+ item.opts.thumb = thumb;
+ }
+
+ if ( type === 'iframe' ) {
+ $.extend(true, item.opts, {
+ iframe : {
+ preload : false,
+ attr : {
+ scrolling : "no"
+ }
+ }
+ });
+
+ item.contentProvider = provider;
+
+ item.opts.slideClass += ' fancybox-slide--' + ( provider == 'gmap_place' || provider == 'gmap_search' ? 'map' : 'video' );
+ }
+
+ } else if ( url ) {
+ item.type = item.opts.defaultType;
+ }
+
+ });
+
+}( window.jQuery || jQuery ));
diff --git a/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/share.js b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/share.js
new file mode 100644
index 0000000000..789284739e
--- /dev/null
+++ b/mayan/apps/appearance/static/appearance/vendors/fancybox-master/src/js/share.js
@@ -0,0 +1,91 @@
+//// ==========================================================================
+//
+// Share
+// Displays simple form for sharing current url
+//
+// ==========================================================================
+;(function (document, $) {
+ 'use strict';
+
+ $.extend(true, $.fancybox.defaults, {
+ btnTpl : {
+ share :
+ ''
+ },
+ share : {
+ tpl :
+ '