commit 80849173701b4ff853404306924a17e9332f63da Author: Erik Stein Date: Sat Dec 12 14:20:23 2020 +0100 Initial import. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..fa42a60 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include featherlight_js/static * diff --git a/featherlight_js/LICENSE b/featherlight_js/LICENSE new file mode 100644 index 0000000..b4b5d5e --- /dev/null +++ b/featherlight_js/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2014 Noel Bossart, http://noelboss.com +MIT Licensed. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/featherlight_js/__init__.py b/featherlight_js/__init__.py new file mode 100644 index 0000000..c1913dc --- /dev/null +++ b/featherlight_js/__init__.py @@ -0,0 +1,2 @@ +__version__ = '1.7.9' +VERSION = tuple(int(d) for d in __version__.split('.')) diff --git a/featherlight_js/models.py b/featherlight_js/models.py new file mode 100644 index 0000000..e484f8b --- /dev/null +++ b/featherlight_js/models.py @@ -0,0 +1 @@ +# Intentionally left blank diff --git a/featherlight_js/static/featherlightjs/featherlight.css b/featherlight_js/static/featherlightjs/featherlight.css new file mode 100644 index 0000000..6d8c7af --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.css @@ -0,0 +1,162 @@ +/** + * Featherlight – ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +@media all { + html.with-featherlight { + /* disable global scrolling when featherlights are visible */ + overflow: hidden; + } + + .featherlight { + display: none; + + /* dimensions: spanning the background from edge to edge */ + position:fixed; + top: 0; right: 0; bottom: 0; left: 0; + z-index: 2147483647; /* z-index needs to be >= elements on the site. */ + + /* position: centering content */ + text-align: center; + + /* insures that the ::before pseudo element doesn't force wrap with fixed width content; */ + white-space: nowrap; + + /* styling */ + cursor: pointer; + background: #333; + /* IE8 "hack" for nested featherlights */ + background: rgba(0, 0, 0, 0); + } + + /* support for nested featherlights. Does not work in IE8 (use JS to fix) */ + .featherlight:last-of-type { + background: rgba(0, 0, 0, 0.8); + } + + .featherlight:before { + /* position: trick to center content vertically */ + content: ''; + display: inline-block; + height: 100%; + vertical-align: middle; + } + + .featherlight .featherlight-content { + /* make content container for positioned elements (close button) */ + position: relative; + + /* position: centering vertical and horizontal */ + text-align: left; + vertical-align: middle; + display: inline-block; + + /* dimensions: cut off images */ + overflow: auto; + padding: 25px 25px 0; + border-bottom: 25px solid transparent; + + /* dimensions: handling large content */ + margin-left: 5%; + margin-right: 5%; + max-height: 95%; + + /* styling */ + background: #fff; + cursor: auto; + + /* reset white-space wrapping */ + white-space: normal; + } + + /* contains the content */ + .featherlight .featherlight-inner { + /* make sure its visible */ + display: block; + } + + /* don't show these though */ + .featherlight script.featherlight-inner, + .featherlight link.featherlight-inner, + .featherlight style.featherlight-inner { + display: none; + } + + .featherlight .featherlight-close-icon { + /* position: centering vertical and horizontal */ + position: absolute; + z-index: 9999; + top: 0; + right: 0; + + /* dimensions: 25px x 25px */ + line-height: 25px; + width: 25px; + + /* styling */ + cursor: pointer; + text-align: center; + font-family: Arial, sans-serif; + background: #fff; /* Set the background in case it overlaps the content */ + background: rgba(255, 255, 255, 0.3); + color: #000; + border: none; + padding: 0; + } + + /* See http://stackoverflow.com/questions/16077341/how-to-reset-all-default-styles-of-the-html5-button-element */ + .featherlight .featherlight-close-icon::-moz-focus-inner { + border: 0; + padding: 0; + } + + .featherlight .featherlight-image { + /* styling */ + width: 100%; + } + + + .featherlight-iframe .featherlight-content { + /* removed the border for image croping since iframe is edge to edge */ + border-bottom: 0; + padding: 0; + -webkit-overflow-scrolling: touch; + overflow-y: scroll; + } + + .featherlight iframe { + /* styling */ + border: none; + } + + .featherlight * { /* See https://github.com/noelboss/featherlight/issues/42 */ + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } +} + +/* handling phones and small screens */ +@media only screen and (max-width: 1024px) { + .featherlight .featherlight-content { + /* dimensions: maximize lightbox with for small screens */ + margin-left: 0; + margin-right: 0; + max-height: 98%; + + padding: 10px 10px 0; + border-bottom: 10px solid transparent; + } +} + +/* hide non featherlight items when printing */ +@media print { + @page {size: landscape} + + html.with-featherlight > * > :not(.featherlight) { + display: none; + } +} diff --git a/featherlight_js/static/featherlightjs/featherlight.gallery.css b/featherlight_js/static/featherlightjs/featherlight.gallery.css new file mode 100644 index 0000000..276505b --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.gallery.css @@ -0,0 +1,122 @@ +/** + * Featherlight Gallery – an extension for the ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +@media all { + .featherlight-next, + .featherlight-previous { + display: block; + position: absolute; + top: 25px; + right: 25px; + bottom: 0; + left: 80%; + cursor: pointer; + /* preventing text selection */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + /* IE9 hack, otherwise navigation doesn't appear */ + background: rgba(0,0,0,0); + } + + .featherlight-previous { + left: 25px; + right: 80%; + } + + .featherlight-next:hover, + .featherlight-previous:hover { + background: rgba(255,255,255,0.25); + } + + + .featherlight-next span, + .featherlight-previous span { + display: none; + position: absolute; + + top: 50%; + left: 5%; + width: 82%; + + /* center horizontally */ + text-align: center; + + font-size: 80px; + line-height: 80px; + + /* center vertically */ + margin-top: -40px; + + text-shadow: 0px 0px 5px #fff; + color: #fff; + font-style: normal; + font-weight: normal; + } + .featherlight-next span { + right: 5%; + left: auto; + } + + + .featherlight-next:hover span, + .featherlight-previous:hover span { + display: inline-block; + } + + .featherlight-swipe-aware .featherlight-next, + .featherlight-swipe-aware .featherlight-previous { + display: none; + } + + /* Hide navigation while loading */ + .featherlight-loading .featherlight-previous, .featherlight-loading .featherlight-next { + display:none; + } + + /* Hide navigation in case of single image */ + .featherlight-first-slide.featherlight-last-slide .featherlight-previous, + .featherlight-first-slide.featherlight-last-slide .featherlight-next { + display:none; + } +} + +/* Always display arrows on touch devices */ +@media only screen and (max-device-width: 1024px){ + .featherlight-next:hover, + .featherlight-previous:hover { + background: none; + } + .featherlight-next span, + .featherlight-previous span { + display: block; + } +} + +/* handling phones and small screens */ +@media only screen and (max-width: 1024px) { + .featherlight-next, + .featherlight-previous { + top: 10px; + right: 10px; + left: 85%; + } + + .featherlight-previous { + left: 10px; + right: 85%; + } + + .featherlight-next span, + .featherlight-previous span { + margin-top: -30px; + font-size: 40px; + } +} diff --git a/featherlight_js/static/featherlightjs/featherlight.gallery.js b/featherlight_js/static/featherlightjs/featherlight.gallery.js new file mode 100644 index 0000000..ad4581a --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.gallery.js @@ -0,0 +1,168 @@ +/** + * Featherlight Gallery – an extension for the ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +(function($) { + "use strict"; + + var warn = function(m) { + if(window.console && window.console.warn) { + window.console.warn('FeatherlightGallery: ' + m); + } + }; + + if('undefined' === typeof $) { + return warn('Too much lightness, Featherlight needs jQuery.'); + } else if(!$.featherlight) { + return warn('Load the featherlight plugin before the gallery plugin'); + } + + var isTouchAware = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch, + jQueryConstructor = $.event && $.event.special.swipeleft && $, + hammerConstructor = window.Hammer && function($el){ + var mc = new window.Hammer.Manager($el[0]); + mc.add(new window.Hammer.Swipe()); + return mc; + }, + swipeAwareConstructor = isTouchAware && (jQueryConstructor || hammerConstructor); + if(isTouchAware && !swipeAwareConstructor) { + warn('No compatible swipe library detected; one must be included before featherlightGallery for swipe motions to navigate the galleries.'); + } + + var callbackChain = { + afterClose: function(_super, event) { + var self = this; + self.$instance.off('next.'+self.namespace+' previous.'+self.namespace); + if (self._swiper) { + self._swiper + .off('swipeleft', self._swipeleft) /* See http://stackoverflow.com/questions/17367198/hammer-js-cant-remove-event-listener */ + .off('swiperight', self._swiperight); + self._swiper = null; + } + return _super(event); + }, + beforeOpen: function(_super, event){ + var self = this; + + self.$instance.on('next.'+self.namespace+' previous.'+self.namespace, function(event){ + var offset = event.type === 'next' ? +1 : -1; + self.navigateTo(self.currentNavigation() + offset); + }); + + if (swipeAwareConstructor) { + self._swiper = swipeAwareConstructor(self.$instance) + .on('swipeleft', self._swipeleft = function() { self.$instance.trigger('next'); }) + .on('swiperight', self._swiperight = function() { self.$instance.trigger('previous'); }); + + self.$instance + .addClass(this.namespace+'-swipe-aware', swipeAwareConstructor); + } + + self.$instance.find('.'+self.namespace+'-content') + .append(self.createNavigation('previous')) + .append(self.createNavigation('next')); + + return _super(event); + }, + beforeContent: function(_super, event) { + var index = this.currentNavigation(); + var len = this.slides().length; + this.$instance + .toggleClass(this.namespace+'-first-slide', index === 0) + .toggleClass(this.namespace+'-last-slide', index === len - 1); + return _super(event); + }, + onKeyUp: function(_super, event){ + var dir = { + 37: 'previous', /* Left arrow */ + 39: 'next' /* Rigth arrow */ + }[event.keyCode]; + if(dir) { + this.$instance.trigger(dir); + return false; + } else { + return _super(event); + } + } + }; + + function FeatherlightGallery($source, config) { + if(this instanceof FeatherlightGallery) { /* called with new */ + $.featherlight.apply(this, arguments); + this.chainCallbacks(callbackChain); + } else { + var flg = new FeatherlightGallery($.extend({$source: $source, $currentTarget: $source.first()}, config)); + flg.open(); + return flg; + } + } + + $.featherlight.extend(FeatherlightGallery, { + autoBind: '[data-featherlight-gallery]' + }); + + $.extend(FeatherlightGallery.prototype, { + /** Additional settings for Gallery **/ + previousIcon: '◀', /* Code that is used as previous icon */ + nextIcon: '▶', /* Code that is used as next icon */ + galleryFadeIn: 100, /* fadeIn speed when image is loaded */ + galleryFadeOut: 300, /* fadeOut speed before image is loaded */ + + slides: function() { + if (this.filter) { + return this.$source.find(this.filter); + } + return this.$source; + }, + + images: function() { + warn('images is deprecated, please use slides instead'); + return this.slides(); + }, + + currentNavigation: function() { + return this.slides().index(this.$currentTarget); + }, + + navigateTo: function(index) { + var self = this, + source = self.slides(), + len = source.length, + $inner = self.$instance.find('.' + self.namespace + '-inner'); + index = ((index % len) + len) % len; /* pin index to [0, len[ */ + + self.$currentTarget = source.eq(index); + self.beforeContent(); + return $.when( + self.getContent(), + $inner.fadeTo(self.galleryFadeOut,0.2) + ).always(function($newContent) { + self.setContent($newContent); + self.afterContent(); + $newContent.fadeTo(self.galleryFadeIn,1); + }); + }, + + createNavigation: function(target) { + var self = this; + return $(''+this[target+'Icon']+'').click(function(){ + $(this).trigger(target+'.'+self.namespace); + }); + } + }); + + $.featherlightGallery = FeatherlightGallery; + + /* extend jQuery with selector featherlight method $(elm).featherlight(config, elm); */ + $.fn.featherlightGallery = function(config) { + FeatherlightGallery.attach(this, config); + return this; + }; + + /* bind featherlight on ready if config autoBind is set */ + $(document).ready(function(){ FeatherlightGallery._onReady(); }); + +}(jQuery)); diff --git a/featherlight_js/static/featherlightjs/featherlight.gallery.min.css b/featherlight_js/static/featherlightjs/featherlight.gallery.min.css new file mode 100644 index 0000000..cc168a7 --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.gallery.min.css @@ -0,0 +1,8 @@ +/** + * Featherlight Gallery – an extension for the ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +@media all{.featherlight-next,.featherlight-previous{display:block;position:absolute;top:25px;right:25px;bottom:0;left:80%;cursor:pointer;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:rgba(0,0,0,0)}.featherlight-previous{left:25px;right:80%}.featherlight-next:hover,.featherlight-previous:hover{background:rgba(255,255,255,.25)}.featherlight-next span,.featherlight-previous span{display:none;position:absolute;top:50%;left:5%;width:82%;text-align:center;font-size:80px;line-height:80px;margin-top:-40px;text-shadow:0 0 5px #fff;color:#fff;font-style:normal;font-weight:400}.featherlight-next span{right:5%;left:auto}.featherlight-next:hover span,.featherlight-previous:hover span{display:inline-block}.featherlight-first-slide.featherlight-last-slide .featherlight-next,.featherlight-first-slide.featherlight-last-slide .featherlight-previous,.featherlight-loading .featherlight-next,.featherlight-loading .featherlight-previous,.featherlight-swipe-aware .featherlight-next,.featherlight-swipe-aware .featherlight-previous{display:none}}@media only screen and (max-device-width:1024px){.featherlight-next:hover,.featherlight-previous:hover{background:0 0}.featherlight-next span,.featherlight-previous span{display:block}}@media only screen and (max-width:1024px){.featherlight-next,.featherlight-previous{top:10px;right:10px;left:85%}.featherlight-previous{left:10px;right:85%}.featherlight-next span,.featherlight-previous span{margin-top:-30px;font-size:40px}} \ No newline at end of file diff --git a/featherlight_js/static/featherlightjs/featherlight.gallery.min.js b/featherlight_js/static/featherlightjs/featherlight.gallery.min.js new file mode 100644 index 0000000..7e1025e --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.gallery.min.js @@ -0,0 +1,7 @@ +/** + * Featherlight Gallery – an extension for the ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/!function(a){"use strict";function b(c,d){if(!(this instanceof b)){var e=new b(a.extend({$source:c,$currentTarget:c.first()},d));return e.open(),e}a.featherlight.apply(this,arguments),this.chainCallbacks(h)}var c=function(a){window.console&&window.console.warn&&window.console.warn("FeatherlightGallery: "+a)};if("undefined"==typeof a)return c("Too much lightness, Featherlight needs jQuery.");if(!a.featherlight)return c("Load the featherlight plugin before the gallery plugin");var d="ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch,e=a.event&&a.event.special.swipeleft&&a,f=window.Hammer&&function(a){var b=new window.Hammer.Manager(a[0]);return b.add(new window.Hammer.Swipe),b},g=d&&(e||f);d&&!g&&c("No compatible swipe library detected; one must be included before featherlightGallery for swipe motions to navigate the galleries.");var h={afterClose:function(a,b){var c=this;return c.$instance.off("next."+c.namespace+" previous."+c.namespace),c._swiper&&(c._swiper.off("swipeleft",c._swipeleft).off("swiperight",c._swiperight),c._swiper=null),a(b)},beforeOpen:function(a,b){var c=this;return c.$instance.on("next."+c.namespace+" previous."+c.namespace,function(a){var b="next"===a.type?1:-1;c.navigateTo(c.currentNavigation()+b)}),g&&(c._swiper=g(c.$instance).on("swipeleft",c._swipeleft=function(){c.$instance.trigger("next")}).on("swiperight",c._swiperight=function(){c.$instance.trigger("previous")}),c.$instance.addClass(this.namespace+"-swipe-aware",g)),c.$instance.find("."+c.namespace+"-content").append(c.createNavigation("previous")).append(c.createNavigation("next")),a(b)},beforeContent:function(a,b){var c=this.currentNavigation(),d=this.slides().length;return this.$instance.toggleClass(this.namespace+"-first-slide",0===c).toggleClass(this.namespace+"-last-slide",c===d-1),a(b)},onKeyUp:function(a,b){var c={37:"previous",39:"next"}[b.keyCode];return c?(this.$instance.trigger(c),!1):a(b)}};a.featherlight.extend(b,{autoBind:"[data-featherlight-gallery]"}),a.extend(b.prototype,{previousIcon:"◀",nextIcon:"▶",galleryFadeIn:100,galleryFadeOut:300,slides:function(){return this.filter?this.$source.find(this.filter):this.$source},images:function(){return c("images is deprecated, please use slides instead"),this.slides()},currentNavigation:function(){return this.slides().index(this.$currentTarget)},navigateTo:function(b){var c=this,d=c.slides(),e=d.length,f=c.$instance.find("."+c.namespace+"-inner");return b=(b%e+e)%e,c.$currentTarget=d.eq(b),c.beforeContent(),a.when(c.getContent(),f.fadeTo(c.galleryFadeOut,.2)).always(function(a){c.setContent(a),c.afterContent(),a.fadeTo(c.galleryFadeIn,1)})},createNavigation:function(b){var c=this;return a(''+this[b+"Icon"]+"").click(function(){a(this).trigger(b+"."+c.namespace)})}}),a.featherlightGallery=b,a.fn.featherlightGallery=function(a){return b.attach(this,a),this},a(document).ready(function(){b._onReady()})}(jQuery); \ No newline at end of file diff --git a/featherlight_js/static/featherlightjs/featherlight.js b/featherlight_js/static/featherlightjs/featherlight.js new file mode 100644 index 0000000..53ac8c4 --- /dev/null +++ b/featherlight_js/static/featherlightjs/featherlight.js @@ -0,0 +1,628 @@ +/** + * Featherlight - ultra slim jQuery lightbox + * Version 1.7.9 - http://noelboss.github.io/featherlight/ + * + * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com) + * MIT Licensed. +**/ +(function($) { + "use strict"; + + if('undefined' === typeof $) { + if('console' in window){ window.console.info('Too much lightness, Featherlight needs jQuery.'); } + return; + } + + /* Featherlight is exported as $.featherlight. + It is a function used to open a featherlight lightbox. + + [tech] + Featherlight uses prototype inheritance. + Each opened lightbox will have a corresponding object. + That object may have some attributes that override the + prototype's. + Extensions created with Featherlight.extend will have their + own prototype that inherits from Featherlight's prototype, + thus attributes can be overriden either at the object level, + or at the extension level. + To create callbacks that chain themselves instead of overriding, + use chainCallbacks. + For those familiar with CoffeeScript, this correspond to + Featherlight being a class and the Gallery being a class + extending Featherlight. + The chainCallbacks is used since we don't have access to + CoffeeScript's `super`. + */ + + function Featherlight($content, config) { + if(this instanceof Featherlight) { /* called with new */ + this.id = Featherlight.id++; + this.setup($content, config); + this.chainCallbacks(Featherlight._callbackChain); + } else { + var fl = new Featherlight($content, config); + fl.open(); + return fl; + } + } + + var opened = [], + pruneOpened = function(remove) { + opened = $.grep(opened, function(fl) { + return fl !== remove && fl.$instance.closest('body').length > 0; + } ); + return opened; + }; + + // Removes keys of `set` from `obj` and returns the removed key/values. + function slice(obj, set) { + var r = {}; + for (var key in obj) { + if (key in set) { + r[key] = obj[key]; + delete obj[key]; + } + } + return r; + } + + // NOTE: List of available [iframe attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). + var iFrameAttributeSet = { + allowfullscreen: 1, frameborder: 1, height: 1, longdesc: 1, marginheight: 1, marginwidth: 1, + name: 1, referrerpolicy: 1, scrolling: 1, sandbox: 1, src: 1, srcdoc: 1, width: 1 + }; + + // Converts camelCased attributes to dasherized versions for given prefix: + // parseAttrs({hello: 1, hellFrozeOver: 2}, 'hell') => {froze-over: 2} + function parseAttrs(obj, prefix) { + var attrs = {}, + regex = new RegExp('^' + prefix + '([A-Z])(.*)'); + for (var key in obj) { + var match = key.match(regex); + if (match) { + var dasherized = (match[1] + match[2].replace(/([A-Z])/g, '-$1')).toLowerCase(); + attrs[dasherized] = obj[key]; + } + } + return attrs; + } + + /* document wide key handler */ + var eventMap = { keyup: 'onKeyUp', resize: 'onResize' }; + + var globalEventHandler = function(event) { + $.each(Featherlight.opened().reverse(), function() { + if (!event.isDefaultPrevented()) { + if (false === this[eventMap[event.type]](event)) { + event.preventDefault(); event.stopPropagation(); return false; + } + } + }); + }; + + var toggleGlobalEvents = function(set) { + if(set !== Featherlight._globalHandlerInstalled) { + Featherlight._globalHandlerInstalled = set; + var events = $.map(eventMap, function(_, name) { return name+'.'+Featherlight.prototype.namespace; } ).join(' '); + $(window)[set ? 'on' : 'off'](events, globalEventHandler); + } + }; + + Featherlight.prototype = { + constructor: Featherlight, + /*** defaults ***/ + /* extend featherlight with defaults and methods */ + namespace: 'featherlight', /* Name of the events and css class prefix */ + targetAttr: 'data-featherlight', /* Attribute of the triggered element that contains the selector to the lightbox content */ + variant: null, /* Class that will be added to change look of the lightbox */ + resetCss: false, /* Reset all css */ + background: null, /* Custom DOM for the background, wrapper and the closebutton */ + openTrigger: 'click', /* Event that triggers the lightbox */ + closeTrigger: 'click', /* Event that triggers the closing of the lightbox */ + filter: null, /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */ + root: 'body', /* Where to append featherlights */ + openSpeed: 250, /* Duration of opening animation */ + closeSpeed: 250, /* Duration of closing animation */ + closeOnClick: 'background', /* Close lightbox on click ('background', 'anywhere' or false) */ + closeOnEsc: true, /* Close lightbox when pressing esc */ + closeIcon: '✕', /* Close icon */ + loading: '', /* Content to show while initial content is loading */ + persist: false, /* If set, the content will persist and will be shown again when opened again. 'shared' is a special value when binding multiple elements for them to share the same content */ + otherClose: null, /* Selector for alternate close buttons (e.g. "a.close") */ + beforeOpen: $.noop, /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */ + beforeContent: $.noop, /* Called when content is loaded. Gets event as parameter, this contains all data */ + beforeClose: $.noop, /* Called before close. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */ + afterOpen: $.noop, /* Called after open. Gets event as parameter, this contains all data */ + afterContent: $.noop, /* Called after content is ready and has been set. Gets event as parameter, this contains all data */ + afterClose: $.noop, /* Called after close. Gets event as parameter, this contains all data */ + onKeyUp: $.noop, /* Called on key up for the frontmost featherlight */ + onResize: $.noop, /* Called after new content and when a window is resized */ + type: null, /* Specify type of lightbox. If unset, it will check for the targetAttrs value. */ + contentFilters: ['jquery', 'image', 'html', 'ajax', 'iframe', 'text'], /* List of content filters to use to determine the content */ + + /*** methods ***/ + /* setup iterates over a single instance of featherlight and prepares the background and binds the events */ + setup: function(target, config){ + /* all arguments are optional */ + if (typeof target === 'object' && target instanceof $ === false && !config) { + config = target; + target = undefined; + } + + var self = $.extend(this, config, {target: target}), + css = !self.resetCss ? self.namespace : self.namespace+'-reset', /* by adding -reset to the classname, we reset all the default css */ + $background = $(self.background || [ + '
', + '
', + '', + '
' + self.loading + '
', + '
', + '
'].join('')), + closeButtonSelector = '.'+self.namespace+'-close' + (self.otherClose ? ',' + self.otherClose : ''); + + self.$instance = $background.clone().addClass(self.variant); /* clone DOM for the background, wrapper and the close button */ + + /* close when click on background/anywhere/null or closebox */ + self.$instance.on(self.closeTrigger+'.'+self.namespace, function(event) { + var $target = $(event.target); + if( ('background' === self.closeOnClick && $target.is('.'+self.namespace)) + || 'anywhere' === self.closeOnClick + || $target.closest(closeButtonSelector).length ){ + self.close(event); + event.preventDefault(); + } + }); + + return this; + }, + + /* this method prepares the content and converts it into a jQuery object or a promise */ + getContent: function(){ + if(this.persist !== false && this.$content) { + return this.$content; + } + var self = this, + filters = this.constructor.contentFilters, + readTargetAttr = function(name){ return self.$currentTarget && self.$currentTarget.attr(name); }, + targetValue = readTargetAttr(self.targetAttr), + data = self.target || targetValue || ''; + + /* Find which filter applies */ + var filter = filters[self.type]; /* check explicit type like {type: 'image'} */ + + /* check explicit type like data-featherlight="image" */ + if(!filter && data in filters) { + filter = filters[data]; + data = self.target && targetValue; + } + data = data || readTargetAttr('href') || ''; + + /* check explicity type & content like {image: 'photo.jpg'} */ + if(!filter) { + for(var filterName in filters) { + if(self[filterName]) { + filter = filters[filterName]; + data = self[filterName]; + } + } + } + + /* otherwise it's implicit, run checks */ + if(!filter) { + var target = data; + data = null; + $.each(self.contentFilters, function() { + filter = filters[this]; + if(filter.test) { + data = filter.test(target); + } + if(!data && filter.regex && target.match && target.match(filter.regex)) { + data = target; + } + return !data; + }); + if(!data) { + if('console' in window){ window.console.error('Featherlight: no content filter found ' + (target ? ' for "' + target + '"' : ' (no target specified)')); } + return false; + } + } + /* Process it */ + return filter.process.call(self, data); + }, + + /* sets the content of $instance to $content */ + setContent: function($content){ + var self = this; + /* we need a special class for the iframe */ + if($content.is('iframe')) { + self.$instance.addClass(self.namespace+'-iframe'); + } + + self.$instance.removeClass(self.namespace+'-loading'); + + /* replace content by appending to existing one before it is removed + this insures that featherlight-inner remain at the same relative + position to any other items added to featherlight-content */ + self.$instance.find('.'+self.namespace+'-inner') + .not($content) /* excluded new content, important if persisted */ + .slice(1).remove().end() /* In the unexpected event where there are many inner elements, remove all but the first one */ + .replaceWith($.contains(self.$instance[0], $content[0]) ? '' : $content); + + self.$content = $content.addClass(self.namespace+'-inner'); + + return self; + }, + + /* opens the lightbox. "this" contains $instance with the lightbox, and with the config. + Returns a promise that is resolved after is successfully opened. */ + open: function(event){ + var self = this; + self.$instance.hide().appendTo(self.root); + if((!event || !event.isDefaultPrevented()) + && self.beforeOpen(event) !== false) { + + if(event){ + event.preventDefault(); + } + var $content = self.getContent(); + + if($content) { + opened.push(self); + + toggleGlobalEvents(true); + + self.$instance.fadeIn(self.openSpeed); + self.beforeContent(event); + + /* Set content and show */ + return $.when($content) + .always(function($content){ + self.setContent($content); + self.afterContent(event); + }) + .then(self.$instance.promise()) + /* Call afterOpen after fadeIn is done */ + .done(function(){ self.afterOpen(event); }); + } + } + self.$instance.detach(); + return $.Deferred().reject().promise(); + }, + + /* closes the lightbox. "this" contains $instance with the lightbox, and with the config + returns a promise, resolved after the lightbox is successfully closed. */ + close: function(event){ + var self = this, + deferred = $.Deferred(); + + if(self.beforeClose(event) === false) { + deferred.reject(); + } else { + + if (0 === pruneOpened(self).length) { + toggleGlobalEvents(false); + } + + self.$instance.fadeOut(self.closeSpeed,function(){ + self.$instance.detach(); + self.afterClose(event); + deferred.resolve(); + }); + } + return deferred.promise(); + }, + + /* resizes the content so it fits in visible area and keeps the same aspect ratio. + Does nothing if either the width or the height is not specified. + Called automatically on window resize. + Override if you want different behavior. */ + resize: function(w, h) { + if (w && h) { + /* Reset apparent image size first so container grows */ + this.$content.css('width', '').css('height', ''); + /* Calculate the worst ratio so that dimensions fit */ + /* Note: -1 to avoid rounding errors */ + var ratio = Math.max( + w / (this.$content.parent().width()-1), + h / (this.$content.parent().height()-1)); + /* Resize content */ + if (ratio > 1) { + ratio = h / Math.floor(h / ratio); /* Round ratio down so height calc works */ + this.$content.css('width', '' + w / ratio + 'px').css('height', '' + h / ratio + 'px'); + } + } + }, + + /* Utility function to chain callbacks + [Warning: guru-level] + Used be extensions that want to let users specify callbacks but + also need themselves to use the callbacks. + The argument 'chain' has callback names as keys and function(super, event) + as values. That function is meant to call `super` at some point. + */ + chainCallbacks: function(chain) { + for (var name in chain) { + this[name] = $.proxy(chain[name], this, $.proxy(this[name], this)); + } + } + }; + + $.extend(Featherlight, { + id: 0, /* Used to id single featherlight instances */ + autoBind: '[data-featherlight]', /* Will automatically bind elements matching this selector. Clear or set before onReady */ + defaults: Featherlight.prototype, /* You can access and override all defaults using $.featherlight.defaults, which is just a synonym for $.featherlight.prototype */ + /* Contains the logic to determine content */ + contentFilters: { + jquery: { + regex: /^[#.]\w/, /* Anything that starts with a class name or identifiers */ + test: function(elem) { return elem instanceof $ && elem; }, + process: function(elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); } + }, + image: { + regex: /\.(png|jpg|jpeg|gif|tiff|bmp|svg)(\?\S*)?$/i, + process: function(url) { + var self = this, + deferred = $.Deferred(), + img = new Image(), + $img = $(''); + img.onload = function() { + /* Store naturalWidth & height for IE8 */ + $img.naturalWidth = img.width; $img.naturalHeight = img.height; + deferred.resolve( $img ); + }; + img.onerror = function() { deferred.reject($img); }; + img.src = url; + return deferred.promise(); + } + }, + html: { + regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */ + process: function(html) { return $(html); } + }, + ajax: { + regex: /./, /* At this point, any content is assumed to be an URL */ + process: function(url) { + var self = this, + deferred = $.Deferred(); + /* we are using load so one can specify a target with: url.html #targetelement */ + var $container = $('
').load(url, function(response, status){ + if ( status !== "error" ) { + deferred.resolve($container.contents()); + } + deferred.fail(); + }); + return deferred.promise(); + } + }, + iframe: { + process: function(url) { + var deferred = new $.Deferred(); + var $content = $('