commit
8084917370
17 changed files with 1430 additions and 0 deletions
@ -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. |
@ -0,0 +1,2 @@
|
||||
__version__ = '1.7.9' |
||||
VERSION = tuple(int(d) for d in __version__.split('.')) |
@ -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; |
||||
} |
||||
} |
@ -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; |
||||
} |
||||
} |
@ -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 $('<span title="'+target+'" class="'+this.namespace+'-'+target+'"><span>'+this[target+'Icon']+'</span></span>').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)); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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 || [ |
||||
'<div class="'+css+'-loading '+css+'">', |
||||
'<div class="'+css+'-content">', |
||||
'<button class="'+css+'-close-icon '+ self.namespace + '-close" aria-label="Close">', |
||||
self.closeIcon, |
||||
'</button>', |
||||
'<div class="'+self.namespace+'-inner">' + self.loading + '</div>', |
||||
'</div>', |
||||
'</div>'].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 src="'+url+'" alt="" class="'+self.namespace+'-image" />'); |
||||
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 = $('<div></div>').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 = $('<iframe/>'); |
||||
var css = parseAttrs(this, 'iframe'); |
||||
var attrs = slice(css, iFrameAttributeSet); |
||||
$content.hide() |
||||
.attr('src', url) |
||||
.attr(attrs) |
||||
.css(css) |
||||
.on('load', function() { deferred.resolve($content.show()); }) |
||||
// We can't move an <iframe> and avoid reloading it,
|
||||
// so let's put it in place ourselves right now:
|
||||
.appendTo(this.$instance.find('.' + this.namespace + '-content')); |
||||
return deferred.promise(); |
||||
} |
||||
}, |
||||
text: { |
||||
process: function(text) { return $('<div>', {text: text}); } |
||||
} |
||||
}, |
||||
|
||||
functionAttributes: ['beforeOpen', 'afterOpen', 'beforeContent', 'afterContent', 'beforeClose', 'afterClose'], |
||||
|
||||
/*** class methods ***/ |
||||
/* read element's attributes starting with data-featherlight- */ |
||||
readElementConfig: function(element, namespace) { |
||||
var Klass = this, |
||||
regexp = new RegExp('^data-' + namespace + '-(.*)'), |
||||
config = {}; |
||||
if (element && element.attributes) { |
||||
$.each(element.attributes, function(){ |
||||
var match = this.name.match(regexp); |
||||
if (match) { |
||||
var val = this.value, |
||||
name = $.camelCase(match[1]); |
||||
if ($.inArray(name, Klass.functionAttributes) >= 0) { /* jshint -W054 */ |
||||
val = new Function(val); /* jshint +W054 */ |
||||
} else { |
||||
try { val = JSON.parse(val); } |
||||
catch(e) {} |
||||
} |
||||
config[name] = val; |
||||
} |
||||
}); |
||||
} |
||||
return config; |
||||
}, |
||||
|
||||
/* Used to create a Featherlight extension |
||||
[Warning: guru-level] |
||||
Creates the extension's prototype that in turn |
||||
inherits Featherlight's prototype. |
||||
Could be used to extend an extension too... |
||||
This is pretty high level wizardy, it comes pretty much straight |
||||
from CoffeeScript and won't teach you anything about Featherlight |
||||
as it's not really specific to this library. |
||||
My suggestion: move along and keep your sanity. |
||||
*/ |
||||
extend: function(child, defaults) { |
||||
/* Setup class hierarchy, adapted from CoffeeScript */ |
||||
var Ctor = function(){ this.constructor = child; }; |
||||
Ctor.prototype = this.prototype; |
||||
child.prototype = new Ctor(); |
||||
child.__super__ = this.prototype; |
||||
/* Copy class methods & attributes */ |
||||
$.extend(child, this, defaults); |
||||
child.defaults = child.prototype; |
||||
return child; |
||||
}, |
||||
|
||||
attach: function($source, $content, config) { |
||||
var Klass = this; |
||||
if (typeof $content === 'object' && $content instanceof $ === false && !config) { |
||||
config = $content; |
||||
$content = undefined; |
||||
} |
||||
/* make a copy */ |
||||
config = $.extend({}, config); |
||||
|
||||
/* Only for openTrigger and namespace... */ |
||||
var namespace = config.namespace || Klass.defaults.namespace, |
||||
tempConfig = $.extend({}, Klass.defaults, Klass.readElementConfig($source[0], namespace), config), |
||||
sharedPersist; |
||||
var handler = function(event) { |
||||
var $target = $(event.currentTarget); |
||||
/* ... since we might as well compute the config on the actual target */ |
||||
var elemConfig = $.extend( |
||||
{$source: $source, $currentTarget: $target}, |
||||
Klass.readElementConfig($source[0], tempConfig.namespace), |
||||
Klass.readElementConfig(event.currentTarget, tempConfig.namespace), |
||||
config); |
||||
var fl = sharedPersist || $target.data('featherlight-persisted') || new Klass($content, elemConfig); |
||||
if(fl.persist === 'shared') { |
||||
sharedPersist = fl; |
||||
} else if(fl.persist !== false) { |
||||
$target.data('featherlight-persisted', fl); |
||||
} |
||||
if (elemConfig.$currentTarget.blur) { |
||||
elemConfig.$currentTarget.blur(); // Otherwise 'enter' key might trigger the dialog again
|
||||
} |
||||
fl.open(event); |
||||
}; |
||||
|
||||
$source.on(tempConfig.openTrigger+'.'+tempConfig.namespace, tempConfig.filter, handler); |
||||
|
||||
return handler; |
||||
}, |
||||
|
||||
current: function() { |
||||
var all = this.opened(); |
||||
return all[all.length - 1] || null; |
||||
}, |
||||
|
||||
opened: function() { |
||||
var klass = this; |
||||
pruneOpened(); |
||||
return $.grep(opened, function(fl) { return fl instanceof klass; } ); |
||||
}, |
||||
|
||||
close: function(event) { |
||||
var cur = this.current(); |
||||
if(cur) { return cur.close(event); } |
||||
}, |
||||
|
||||
/* Does the auto binding on startup. |
||||
Meant only to be used by Featherlight and its extensions |
||||
*/ |
||||
_onReady: function() { |
||||
var Klass = this; |
||||
if(Klass.autoBind){ |
||||
/* Bind existing elements */ |
||||
$(Klass.autoBind).each(function(){ |
||||
Klass.attach($(this)); |
||||
}); |
||||
/* If a click propagates to the document level, then we have an item that was added later on */ |
||||
$(document).on('click', Klass.autoBind, function(evt) { |
||||
if (evt.isDefaultPrevented()) { |
||||
return; |
||||
} |
||||
/* Bind featherlight */ |
||||
var handler = Klass.attach($(evt.currentTarget)); |
||||
/* Dispatch event directly */ |
||||
handler(evt); |
||||
}); |
||||
} |
||||
}, |
||||
|
||||
/* Featherlight uses the onKeyUp callback to intercept the escape key. |
||||
Private to Featherlight. |
||||
*/ |
||||
_callbackChain: { |
||||
onKeyUp: function(_super, event){ |
||||
if(27 === event.keyCode) { |
||||
if (this.closeOnEsc) { |
||||
$.featherlight.close(event); |
||||
} |
||||
return false; |
||||
} else { |
||||
return _super(event); |
||||
} |
||||
}, |
||||
|
||||
beforeOpen: function(_super, event) { |
||||
// Used to disable scrolling
|
||||
$(document.documentElement).addClass('with-featherlight'); |
||||
|
||||
// Remember focus:
|
||||
this._previouslyActive = document.activeElement; |
||||
|
||||
// Disable tabbing:
|
||||
// See http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
|
||||
this._$previouslyTabbable = $("a, input, select, textarea, iframe, button, iframe, [contentEditable=true]") |
||||
.not('[tabindex]') |
||||
.not(this.$instance.find('button')); |
||||
|
||||
this._$previouslyWithTabIndex = $('[tabindex]').not('[tabindex="-1"]'); |
||||
this._previousWithTabIndices = this._$previouslyWithTabIndex.map(function(_i, elem) { |
||||
return $(elem).attr('tabindex'); |
||||
}); |
||||
|
||||
this._$previouslyWithTabIndex.add(this._$previouslyTabbable).attr('tabindex', -1); |
||||
|
||||
if (document.activeElement.blur) { |
||||
document.activeElement.blur(); |
||||
} |
||||
return _super(event); |
||||
}, |
||||
|
||||
afterClose: function(_super, event) { |
||||
var r = _super(event); |
||||
// Restore focus
|
||||
var self = this; |
||||
this._$previouslyTabbable.removeAttr('tabindex'); |
||||
this._$previouslyWithTabIndex.each(function(i, elem) { |
||||
$(elem).attr('tabindex', self._previousWithTabIndices[i]); |
||||
}); |
||||
this._previouslyActive.focus(); |
||||
// Restore scroll
|
||||
if(Featherlight.opened().length === 0) { |
||||
$(document.documentElement).removeClass('with-featherlight'); |
||||
} |
||||
return r; |
||||
}, |
||||
|
||||
onResize: function(_super, event){ |
||||
this.resize(this.$content.naturalWidth, this.$content.naturalHeight); |
||||
return _super(event); |
||||
}, |
||||
|
||||
afterContent: function(_super, event){ |
||||
var r = _super(event); |
||||
this.$instance.find('[autofocus]:not([disabled])').focus(); |
||||
this.onResize(event); |
||||
return r; |
||||
} |
||||
} |
||||
}); |
||||
|
||||
$.featherlight = Featherlight; |
||||
|
||||
/* bind jQuery elements to trigger featherlight */ |
||||
$.fn.featherlight = function($content, config) { |
||||
Featherlight.attach(this, $content, config); |
||||
return this; |
||||
}; |
||||
|
||||
/* bind featherlight on ready if config autoBind is set */ |
||||
$(document).ready(function(){ Featherlight._onReady(); }); |
||||
}(jQuery)); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
from io import open |
||||
import os |
||||
from setuptools import setup, find_packages |
||||
|
||||
|
||||
def get_version(prefix): |
||||
import re |
||||
with open(os.path.join(prefix, '__init__.py')) as fd: |
||||
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", fd.read())) |
||||
return metadata['version'] |
||||
|
||||
|
||||
def read(filename): |
||||
path = os.path.join(os.path.dirname(__file__), filename) |
||||
with open(path, encoding='utf-8') as handle: |
||||
return handle.read() |
||||
|
||||
|
||||
setup( |
||||
name='django-featherlightjs', |
||||
version=get_version('featherlight_js'), |
||||
description=' Django static files for featherlight.js package.', |
||||
# long_description=read('README.md'), |
||||
author='Erik Stein', |
||||
author_email='erik@classlibrary.net', |
||||
url='https://projects.c--y.net/erik/django-featherlightjs/', |
||||
license='MIT License', |
||||
platforms=['OS Independent'], |
||||
packages=find_packages( |
||||
exclude=['tests', 'testapp'], |
||||
), |
||||
# namespace_packages=['shared'], |
||||
include_package_data=True, |
||||
install_requires=[ |
||||
# 'django<2', commented out to make `pip install -U` easier |
||||
# 'python-dateutil', |
||||
# 'beautifulsoup4', |
||||
# 'translitcodec', |
||||
], |
||||
classifiers=[ |
||||
# 'Development Status :: 4 - Beta', |
||||
'Environment :: Web Environment', |
||||
'Framework :: Django', |
||||
'Intended Audience :: Developers', |
||||
'License :: OSI Approved :: BSD License', |
||||
'Operating System :: OS Independent', |
||||
'Programming Language :: Python', |
||||
# 'Programming Language :: Python :: 2', |
||||
# 'Programming Language :: Python :: 2.7', |
||||
# 'Programming Language :: Python :: 3', |
||||
# 'Programming Language :: Python :: 3.3', |
||||
# 'Programming Language :: Python :: 3.4', |
||||
'Topic :: Utilities', |
||||
], |
||||
zip_safe=False, |
||||
tests_require=[ |
||||
# 'Django', |
||||
# 'coverage', |
||||
# 'django-mptt', |
||||
# 'pytz', |
||||
], |
||||
# test_suite='testapp.runtests.runtests', |
||||
) |
Loading…
Reference in new issue