1/* all.js - Tiger skin scripts */ 2var VER='8.1.1',DEBUG=true,LOCALE='en-US'; 3;Texts = {and:'and',from:'From {0}',tooLong:'This is too long to send directly to the email application.',to:'To',subject:'Subject',copyInstructions:'The text has been copied to your clipboard. Now switch to the email application (or webmail) and paste it. Move "To" and "Subject" into the appropriate boxes!',okButton:'OK',warning:'Warning',copiedToClipboard:'Copied to clipboard!',continueBrowsing:'Continue browsing',removeAll:'Remove all',feedbackButtonExplanation:'Try "Send" if you have an email application installed, use "Copy" for webmail!',removeAllItems:'Remove all items?',yes:'Yes',no:'No',rating:'Rating',print:'Print',printLabel:'Print out this photo on your printer',download:'Download',addCart:'Add to Cart',shopLabel:'Add this item to the shopping cart',viewCart:'View Cart',viewCartLabel:'View shopping cart',fotomotoBtn:'Buy / Share',fotomotoTooltip:'Buy prints or digital files, share, send free eCards',metaBtn:'Photo data',metaLabel:'Display photographic (Exif/Iptc) data',regionsBtn:'People',regionsLabel:'Show tagged people',mapBtn:'Map',mapLabel:'Show the photo location on the map',mostphotosBtn:'Purchase',mostphotosLabel:'Download this image from <b>mostphotos.com</b>!',share:'Share',shareLabel:'Share this photo over social sites',shareOn:'Share on',play:'Play',audioClipLabel:'Play / pause the attached audio clip',startOver:'Start over',upOneLevel:'Up one level',backToHome:'Back to home',backToIndex:'Back to index page',nextFolder:'Next folder',nextIndex:'Next index page',atLastPageQuestion:'Where to go next?',atLastPage:'At last page',clickToOpen:'Click to open this document with the associated viewer',pauseShort:'Pause',startSlideshowShort:'Play',previousPicture:'Previous picture',previousPictureShort:'Prev',upOneLevelShort:'Exit',zoom:'Zoom',oneToOneSize:'1:1 size',oneToOneSizeShort:'1:1',fitToScreen:'Fit to screen',fitToScreenShort:'Fit',showThumbs:'Show thumbnails',showThumbsShort:'Thumbnails',hideThumbs:'Hide thumbnails',hideThumbsShort:'Hide thumbs',showInfo:'Show caption / info',showInfoShort:'Info',hideInfo:'Hide caption / info',hideInfoShort:'Hide info',slideshow:'Slideshow',timing:'Timing',startStopSlideshow:'Start / stop slideshow',fullscreen:'Fullscreen',fullscreenShort:'Fullscr',exitFullscreen:'Exit fullscreen',exitFullscreenShort:'Normal',nextPicture:'Next picture',nextPictureShort:'Next',closeWindow:'Close window',noMoreItems:'You can´t buy more than 99 items at once on PayPal. Added only the first 99.',maxNItems:'Maximum <b>{0}</b> items allowed!',items:'items',item:'item',couponRemoved:'The coupon has been removed.',addMoreItems:'Add {0} more item(s) to use this coupon!',validAbove:'Valid only above {0} cart value.',amountLowerThan:'The amount is lower than the discount <b>{0}</b>.',cartIsEmpty:'The cart is empty',minimumCartValue:'The minimal cart value is <b>{0}</b>',proceedToPayPal:'Proceed to PayPal!',expired:'The coupon code <b>{0}</b> has expired!',minAmountWarning:'This coupon can only be used if the total amount exceeds <b>{0}</b>.',minQuantityWarning:'This coupon can only be used if the number of items exceeds <b>{0}</b>.',lowerThanCurrent:'This coupon offers a lower discount than the current <b>{0}</b>.',couponAccepted:'Coupon code accepted, discounting <b>{0}</b>.',success:'Success',reclaimed:'This coupon has already been used!',higherThanTotal:'The coupon provides a higher discount (<b>{0}</b>) than the cart total.',noSuch:'No such coupon exists!',cart:'Cart',view:'View',addSelectedItems:'Add selected items!',addThisItem:'Add this item!',shoppingCart:'Shopping cart',edit:'Edit',subtotal:'Subtotal',couponCode:'Coupon code',remove:'Remove',discount:'Discount',shippingAndHandling:'Shipping and handling',tax:'Tax',total:'Total',continueShopping:'Continue shopping',emptyCart:'Empty Cart',buyNow:'Buy Now',processedByPaypal:'Payment processed by PayPal',scrollTopTooltip:'Top',checkThisOut:'This album is worth checking out',copied:'Copied',copy:'Copy',email:'Email',today:'today',yesterday:'yesterday',yearsAgo:'{0} years ago',monthsAgo:'{0} months ago',daysAgo:'{0} days ago',inThePast24Hours:'in the past 24 hours',inThePast48Hours:'in the past 48 hours',inThePastNYears:'in the past {0} years',inThePastNMonths:'in the past {0} months',inThePastNDays:'in the past {0} days',close:'Close',cookiePolicyText:'This album is using cookies to remember your preferences. It does not track you, nor does it store personal data.',cookiePolicyAgree:'Got it',gdprComplianceText:'This site uses the following external services that may track y
vendor: 4,834 bytes, lines 3-6
3our visits anonymously. Disabling tracking will stop their functionality.',allowAll:'Allow all',denyAll:'Deny all',allowSelected:'Allow selected',cookiePolicyLearnMore:'Learn more',restrictedLinkTitle:'Restricted material',restrictedLinkQuestion:'Please verify your age to proceed!',restrictedLinkYes:'I´m over 18',restrictedLinkNo:'I´m below 18',return:'return',rendering:'Rendering',selectedItems:'Selected items',label:'Label',search:'Search',searchNew:'Search new',loading:'Loading',foundNTimes:'{0} image(s)',foundTooMany:'{0} images; displaying only the first {1}',notFound:'not found',searchPlaceholder:'Search...',more:'more',less:'less',imagesTaken:'Images taken',imagesModified:'Images modified',imagesAdded:'Images added',betweenDays:'between {0} and {1}',onDay:'on {0}',afterDay:'after {0}',beforeDay:'before {0}',sinceMyLastVisit:'since my last visit',results:'Results',sortedBy:'Sorted by',ascending:'ascending',descending:'descending',noRating:'No rating',multipleSelectHint:'Use SHIFT to select range, CTRL for multiple entries',select:'Select',reset:'Reset',sortBy:'Sort by',image:'image',images:'images',audio:'audio',audios:'audios',video:'video',videos:'videos',other:'other',others:'others',fatalError:'Fatal error!'}; 4/*! modernizr 3.6.0 (Custom Build) | MIT * 5 * https://modernizr.com/download/?-adownload-backgroundsize-bgsizecover-cssanimations-cssgradients-csstransforms-csstransforms3d-csstransitions-cssvhunit-hiddenscroll-preserve3d-svg-touchevents-setclasses !*/ 6!function(e,t,n){function r(t,n,r){var s;if("getComputedStyle"in e){s=getComputedStyle.call(e,t,n);var o=e.console;if(null!==s)r&&(s=s.getPropertyValue(r));else if(o){var i=o.error?"error":"log";o[i].call(o,"getComputedStyle returning null, its possible modernizr test results are inaccurate")}}else s=!n&&t.currentStyle&&t.currentStyle[r];return s}function s(e){var t=z.className,n=Modernizr._config.classPrefix||"";if(k&&(t=t.baseVal),Modernizr._config.enableJSClass){var r=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(r,"$1"+n+"js$2")}Modernizr._config.enableClasses&&(t+=" "+n+e.join(" "+n),k?z.className.baseVal=t:z.className=t)}function o(){return"function"!=typeof t.createElement?t.createElement(arguments[0]):k?t.createElementNS.call(t,"http://www.w3.org/2000/svg",arguments[0]):t.createElement.apply(t,arguments)}function i(e,t){return typeof e===t}function a(){var e,t,n,r,s,o,a;for(var l in C)if(C.hasOwnProperty(l)){if(e=[],t=C[l],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(n=0;n<t.options.aliases.length;n++)e.push(t.options.aliases[n].toLowerCase());for(r=i(t.fn,"function")?t.fn():t.fn,s=0;s<e.length;s++)o=e[s],a=o.split("."),1===a.length?Modernizr[a[0]]=r:(!Modernizr[a[0]]||Modernizr[a[0]]instanceof Boolean||(Modernizr[a[0]]=new Boolean(Modernizr[a[0]])),Modernizr[a[0]][a[1]]=r),S.push((r?"":"no-")+a.join("-"))}}function l(e,t){return e-1===t||e===t||e+1===t}function u(){var e=t.body;return e||(e=o(k?"svg":"body"),e.fake=!0),e}function d(e,n,r,s){var i,a,l,d,f="modernizr",c=o("div"),p=u();if(parseInt(r,10))for(;r--;)l=o("div"),l.id=s?s[r]:f+(r+1),c.appendChild(l);return i=o("style"),i.type="text/css",i.id="s"+f,(p.fake?p:c).appendChild(i),p.appendChild(c),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(t.createTextNode(e)),c.id=f,p.fake&&(p.style.background="",p.style.overflow="hidden",d=z.style.overflow,z.style.overflow="hidden",z.appendChild(p)),a=n(c,e),p.fake?(p.parentNode.removeChild(p),z.style.overflow=d,z.offsetHeight):c.parentNode.removeChild(c),!!a}function f(e,t){return!!~(""+e).indexOf(t)}function c(e,t){return function(){return e.apply(t,arguments)}}function p(e,t,n){var r;for(var s in e)if(e[s]in t)return n===!1?e[s]:(r=t[e[s]],i(r,"function")?c(r,n||t):r);return!1}function m(e){return e.replace(/([a-z])-([a-z])/g,function(e,t,n){return t+n.toUpperCase()}).replace(/^-/,"")}function g(e){return e.replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()}).replace(/^ms-/,"-ms-")}function h(t,s){var o=t.length;if("CSS"in e&&"supports"in e.CSS){for(;o--;)if(e.CSS.supports(g(t[o]),s))return!0;return!1}if("CSSSupportsRule"in e){for(var i=[];o--;)i.push("("+g(t[o])+":"+s+")");return i=i.join(" or "),d("@supports ("+i+") { #modernizr { position: absolute; } }",function(e){return"absolute"==r(e,null,"position")})}return n}function v(e,t,r,s){function a(){u&&(delete O.style,delete O.modElem)}if(s=i(s,"undefined")?!1:s,!i(r,"undefined")){var l=h(e,r);if(!i(l,"undefined"))return l}for(var u,d,c,p,g,v=["modernizr","tspan","samp"];!O.style&&v.length;)u=!0,O.modElem=o(v.shift()),O.style=O.modElem.style;for(c=e.length,d=0;c>d;d++)if(p=e[d],g=O.style[p],f(p,"-")&&(p=m(p)),O.style[p]!==n){if(s||i(r,"undefined"))return a(),"pfx"==t?p:!0;try{O.style[p]=r}catch(y){}if(O.style[p]!=g)return a(),"pfx"==t?p:!0}return a(),!1}
6function y(e,t,n,r,s){var o=e.charAt(0).toUpperCase()+e.slice(1),a=(e+" "+N.join(o+" ")+o).split(" ");return i(t,"string")||i(t,"undefined")?v(a,t,r,s):(a=(e+" "+j.join(o+" ")+o).split(" "),p(a,t,n))}function w(e,t,r){return y(e,n,n,t,r)}var S=[],C=[],x={_version:"3.6.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var n=this;setTimeout(function(){t(n[e])},0)},addTest:function(e,t,n){C.push({name:e,fn:t,options:n})},addAsyncTest:function(e){C.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=x,Modernizr=new Modernizr,Modernizr.addTest("svg",!!t.createElementNS&&!!t.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect);var b=x._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):["",""];x._prefixes=b;var T="CSS"in e&&"supports"in e.CSS,_="supportsCSS"in e;Modernizr.addTest("supports",T||_);var z=t.documentElement,k="svg"===z.nodeName.toLowerCase();Modernizr.addTest("cssgradients",function(){for(var e,t="background-image:",n="gradient(linear,left top,right bottom,from(#9f9),to(white));",r="",s=0,i=b.length-1;i>s;s++)e=0===s?"to ":"",r+=t+b[s]+"linear-gradient("+e+"left top, #9f9, white);";Modernizr._config.usePrefixes&&(r+=t+"-webkit-"+n);var a=o("a"),l=a.style;return l.cssText=r,(""+l.backgroundImage).indexOf("gradient")>-1}),Modernizr.addTest("preserve3d",function(){var t,n,r=e.CSS,s=!1;return r&&r.supports&&r.supports("(transform-style: preserve-3d)")?!0:(t=o("a"),n=o("a"),t.style.cssText="display: block; transform-style: preserve-3d; transform-origin: right; transform: rotateY(40deg);",n.style.cssText="display: block; width: 9px; height: 1px; background: #000; transform-origin: right; transform: rotateY(40deg);",t.appendChild(n),z.appendChild(t),s=n.getBoundingClientRect(),z.removeChild(t),s=s.width&&s.width<4)});var P=x.testStyles=d;Modernizr.addTest("touchevents",function(){var n;if("ontouchstart"in e||e.DocumentTouch&&t instanceof DocumentTouch)n=!0;else{var r=["@media (",b.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");P(r,function(e){n=9===e.offsetTop})}return n}),Modernizr.addTest("hiddenscroll",function(){return P("#modernizr {width:100px;height:100px;overflow:scroll}",function(e){return e.offsetWidth===e.clientWidth})}),P("#modernizr { height: 50vh; }",function(t){var n=parseInt(e.innerHeight/2,10),s=parseInt(r(t,null,"height"),10);Modernizr.addTest("cssvhunit",l(s,n))});var E="Moz O ms Webkit",N=x._config.usePrefixes?E.split(" "):[];x._cssomPrefixes=N;var j=x._config.usePrefixes?E.toLowerCase().split(" "):[];x._domPrefixes=j;var A={elem:o("modernizr")};Modernizr._q.push(function(){delete A.elem});var O={style:A.elem.style};Modernizr._q.unshift(function(){delete O.style}),x.testAllProps=y,x.testAllProps=w,Modernizr.addTest("backgroundsize",w("backgroundSize","100%",!0)),Modernizr.addTest("cssanimations",w("animationName","a",!0)),Modernizr.addTest("bgsizecover",w("backgroundSize","cover")),Modernizr.addTest("csstransforms",function(){return-1===navigator.userAgent.indexOf("Android 2.")&&w("transform","scale(1)",!0)}),Modernizr.addTest("csstransitions",w("transition","all",!0)),Modernizr.addTest("csstransforms3d",function(){return!!w("perspective","1px",!0)}),Modernizr.addTest("adownload",!e.externalHost&&"download"in o("a")),a(),s(S),delete x.addTest,delete x.addAsyncTest;for(var q=0;q<Modernizr._q.length;q++)Modernizr._q[q]();e.Modernizr=Modernizr}(window,document); 7/** 8 * what-input - A global utility for tracking the current input method (mouse, keyboard or touch). 9 * @version v5.2.1 10 * @link https://github.com/ten1seven/what-input 11 * @license MIT 12 */ 13(function webpackUniversalModuleDefinition(root, factory) { 14 if(typeof exports === 'object' && typeof module === 'object') 15 module.exports = factory(); 16 else if(typeof define === 'function' && define.amd) 17 define("whatInput", [], factory); 18 else if(typeof exports === 'object') 19 exports["whatInput"] = factory(); 20 else 21 root["whatInput"] = factory(); 22})(this, function() { 23return /******/ (function(modules) { // webpackBootstrap 24/******/ // The module cache 25/******/ var installedModules = {}; 26 27/******/ // The require function 28/******/ function __webpack_require__(moduleId) { 29 30/******/ // Check if module is in cache 31/******/ if(installedModules[moduleId]) 32/******/ return installedModules[moduleId].exports; 33 34/******/ // Create a new module (and put it into the cache) 35/******/ var module = installedModules[moduleId] = { 36/******/ exports: {}, 37/******/ id: moduleId, 38/******/ loaded: false 39/******/ }; 40 41/******/ // Execute the module function 42/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 43 44/******/ // Flag the module as loaded 45/******/ module.loaded = true; 46 47/******/ // Return the exports of the module 48/******/ return module.exports; 49/******/ } 50 51 52/******/ // expose the modules object (__webpack_modules__) 53/******/ __webpack_require__.m = modules; 54 55/******/ // expose the module cache 56/******/ __webpack_require__.c = installedModules; 57 58/******/ // __webpack_public_path__ 59/******/ __webpack_require__.p = ""; 60 61/******/ // Load entry module and return exports 62/******/ return __webpack_require__(0); 63/******/ }) 64/************************************************************************/ 65/******/ ([ 66/* 0 */
67/***/ (function(module, exports) { 68 69 'use strict'; 70 71 module.exports = function () { 72 /* 73 * bail out if there is no document or window 74 * (i.e. in a node/non-DOM environment) 75 * 76 * Return a stubbed API instead 77 */ 78 if (typeof document === 'undefined' || typeof window === 'undefined') { 79 return { 80 // always return "initial" because no interaction will ever be detected 81 ask: function ask() { 82 return 'initial'; 83 }, 84 85 // always return null 86 element: function element() { 87 return null; 88 }, 89 90 // no-op 91 ignoreKeys: function ignoreKeys() {}, 92 93 // no-op 94 specificKeys: function specificKeys() {}, 95 96 // no-op 97 registerOnChange: function registerOnChange() {}, 98 99 // no-op 100 unRegisterOnChange: function unRegisterOnChange() {} 101 }; 102 } 103 104 /* 105 * variables 106 */ 107 108 // cache document.documentElement 109 var docElem = document.documentElement; 110 111 // currently focused dom element 112 var currentElement = null; 113 114 // last used input type 115 var currentInput = 'initial'; 116 117 // last used input intent 118 var currentIntent = currentInput; 119 120 // UNIX timestamp of current event 121 var currentTimestamp = Date.now(); 122 123 // check for sessionStorage support 124 // then check for session variables and use if available 125 try { 126 if (window.sessionStorage.getItem('what-input')) { 127 currentInput = window.sessionStorage.getItem('what-input'); 128 } 129 130 if (window.sessionStorage.getItem('what-intent')) { 131 currentIntent = window.sessionStorage.getItem('what-intent'); 132 } 133 } catch (e) {} 134 135 // form input types 136 var formInputs = ['button', 'input', 'select', 'textarea']; 137 138 // empty array for holding callback functions 139 var functionList = []; 140 141 // list of modifier keys commonly used with the mouse and 142 // can be safely ignored to prevent false keyboard detection 143 var ignoreMap = [16, // shift 144 17, // control 145 18, // alt 146 91, // Windows key / left Apple cmd 147 93 // Windows menu / right Apple cmd 148 ]; 149 150 var specificMap = []; 151 152 // mapping of events to input types 153 var inputMap = { 154 keydown: 'keyboard', 155 keyup: 'keyboard', 156 mousedown: 'mouse', 157 mousemove: 'mouse', 158 MSPointerDown: 'pointer', 159 MSPointerMove: 'pointer', 160 pointerdown: 'pointer', 161 pointermove: 'pointer', 162 touchstart: 'touch', 163 touchend: 'touch' 164 165 // boolean: true if the page is being scrolled 166 };var isScrolling = false; 167 168 // store current mouse position 169 var mousePos = { 170 x: null, 171 y: null 172 173 // map of IE 10 pointer events 174 };var pointerMap = { 175 2: 'touch', 176 3: 'touch', // treat pen like touch 177 4: 'mouse' 178 179 // check support for passive event listeners 180 };var supportsPassive = false; 181 182 try { 183 var opts = Object.defineProperty({}, 'passive', { 184 get: function get() { 185 supportsPassive = true; 186 } 187 }); 188 189 window.addEventListener('test', null, opts); 190 } catch (e) {} 191 192 /* 193 * set up 194 */ 195 196 var setUp = function setUp() { 197 // add correct mouse wheel event mapping to `inputMap` 198 inputMap[detectWheel()] = 'mouse'; 199 200 addListeners(); 201 doUpdate('input'); 202 doUpdate('intent'); 203 }; 204 205 /* 206 * events 207 */ 208 209 var addListeners = function addListeners() { 210 // `pointermove`, `MSPointerMove`, `mousemove` and mouse wheel event binding 211 // can only demonstrate potential, but not actual, interaction 212 // and are treated separately 213 var options = supportsPassive ? { passive: true } : false; 214 215 // pointer events (mouse, pen, touch) 216 if (window.PointerEvent) { 217 window.addEventListener('pointerdown', setInput); 218 window.addEventListener('pointermove', setIntent); 219 } else if (window.MSPointerEvent) { 220 window.addEventListener('MSPointerDown', setInput); 221 window.addEventListener('MSPointerMove', setIntent); 222 } else { 223 // mouse events 224 window.addEventListener('mousedown', setInput); 225 window.addEventListener('mousemove', setIntent); 226 227 // touch events 228 if ('ontouchstart' in window) { 229 window.addEventListener('touchstart', setInput, options); 230 window.addEventListener('touchend', setInput); 231 } 232 } 233 234 // mouse wheel 235 window.addEventListener(detectWheel(), setIntent, options); 236 237 // keyboard events 238 window.addEventListener('keydown', setInput); 239 window.addEventListener('keyup', setInput); 240 241 // focus events 242 window.addEventListener('focusin', setElement); 243 window.addEventListener('focusout', clearElement); 244 }; 245 246 // checks conditions before updating new input 247 var setInput = function setInput(event) { 248 var eventKey = event.which; 249 var value = inputMap[event.type]; 250 251 if (value === 'pointer') { 252 value = pointerType(event); 253 } 254 255 var ignoreMatch = !specificMap.length && ignoreMap.indexOf(eventKey) === -1; 256 257 var specificMatch = specificMap.length && specificMap.indexOf(eventKey) !== -1; 258 259 var shouldUpdate = value === 'keyboard' && eventKey && (ignoreMatch || specificMatch) || value === 'mouse' || value === 'touch'; 260 261 // prevent touch detection from being overridden by event execution order 262 if (validateTouch(value)) { 263 shouldUpdate = false; 264 } 265 266 if (shouldUpdate && currentInput !== value) { 267 currentInput = value; 268 269 try { 270 window.sessionStorage.setItem('what-input', currentInput); 271 } catch (e) {} 272 273 doUpdate('input'); 274 } 275 276 if (shouldUpdate && currentIntent !== value) { 277 // preserve intent for keyboard interaction with form fields 278 var activeElem = document.activeElement; 279 var notFormInput = activeElem && activeElem.nodeName && formInputs.indexOf(activeElem.nodeName.toLowerCase()) === -1 || activeElem.nodeName.toLowerCase() === 'button' && !checkClosest(activeElem, 'form'); 280 281 if (notFormInput) { 282 currentIntent = value; 283 284 try { 285 window.sessionStorage.setItem('what-intent', currentIntent); 286 } catch (e) {} 287 288 doUpdate('intent'); 289 } 290 } 291 }; 292
293 // updates the doc and `inputTypes` array with new input 294 var doUpdate = function doUpdate(which) { 295 docElem.setAttribute('data-what' + which, which === 'input' ? currentInput : currentIntent); 296 297 fireFunctions(which); 298 }; 299 300 // updates input intent for `mousemove` and `pointermove` 301 var setIntent = function setIntent(event) { 302 var value = inputMap[event.type]; 303 304 if (value === 'pointer') { 305 value = pointerType(event); 306 } 307 308 // test to see if `mousemove` happened relative to the screen to detect scrolling versus mousemove 309 detectScrolling(event); 310 311 // only execute if scrolling isn't happening 312 if (!isScrolling && !validateTouch(value) && currentIntent !== value) { 313 currentIntent = value; 314 315 try { 316 window.sessionStorage.setItem('what-intent', currentIntent); 317 } catch (e) {} 318 319 doUpdate('intent'); 320 } 321 }; 322 323 var setElement = function setElement(event) { 324 if (!event.target.nodeName) { 325 // If nodeName is undefined, clear the element 326 // This can happen if click inside an <svg> element. 327 clearElement(); 328 return; 329 } 330 331 currentElement = event.target.nodeName.toLowerCase(); 332 docElem.setAttribute('data-whatelement', currentElement); 333 334 if (event.target.classList && event.target.classList.length) { 335 docElem.setAttribute('data-whatclasses', event.target.classList.toString().replace(' ', ',')); 336 } 337 }; 338 339 var clearElement = function clearElement() { 340 currentElement = null; 341 342 docElem.removeAttribute('data-whatelement'); 343 docElem.removeAttribute('data-whatclasses'); 344 }; 345 346 /* 347 * utilities 348 */ 349 350 var pointerType = function pointerType(event) { 351 if (typeof event.pointerType === 'number') { 352 return pointerMap[event.pointerType]; 353 } else { 354 // treat pen like touch 355 return event.pointerType === 'pen' ? 'touch' : event.pointerType; 356 } 357 }; 358 359 // prevent touch detection from being overridden by event execution order 360 var validateTouch = function validateTouch(value) { 361 var timestamp = Date.now(); 362 363 var touchIsValid = value === 'mouse' && currentInput === 'touch' && timestamp - currentTimestamp < 200; 364 365 currentTimestamp = timestamp; 366 367 return touchIsValid; 368 }; 369 370 // detect version of mouse wheel event to use 371 // via https://developer.mozilla.org/en-US/docs/Web/Events/wheel 372 var detectWheel = function detectWheel() { 373 var wheelType = void 0; 374 375 // Modern browsers support "wheel" 376 if ('onwheel' in document.createElement('div')) { 377 wheelType = 'wheel'; 378 } else { 379 // Webkit and IE support at least "mousewheel" 380 // or assume that remaining browsers are older Firefox 381 wheelType = document.onmousewheel !== undefined ? 'mousewheel' : 'DOMMouseScroll'; 382 } 383 384 return wheelType; 385 }; 386 387 // runs callback functions 388 var fireFunctions = function fireFunctions(type) { 389 for (var i = 0, len = functionList.length; i < len; i++) { 390 if (functionList[i].type === type) { 391 functionList[i].fn.call(undefined, type === 'input' ? currentInput : currentIntent); 392 } 393 } 394 }; 395 396 // finds matching element in an object 397 var objPos = function objPos(match) { 398 for (var i = 0, len = functionList.length; i < len; i++) { 399 if (functionList[i].fn === match) { 400 return i; 401 } 402 } 403 }; 404 405 var detectScrolling = function detectScrolling(event) { 406 if (mousePos['x'] !== event.screenX || mousePos['y'] !== event.screenY) { 407 isScrolling = false; 408 409 mousePos['x'] = event.screenX; 410 mousePos['y'] = event.screenY; 411 } else { 412 isScrolling = true; 413 } 414 }; 415 416 // manual version of `closest()` 417 var checkClosest = function checkClosest(elem, tag) { 418 var ElementPrototype = window.Element.prototype; 419 420 if (!ElementPrototype.matches) { 421 ElementPrototype.matches = ElementPrototype.msMatchesSelector || ElementPrototype.webkitMatchesSelector; 422 } 423 424 if (!ElementPrototype.closest) { 425 do { 426 if (elem.matches(tag)) { 427 return elem; 428 } 429 430 elem = elem.parentElement || elem.parentNode; 431 } while (elem !== null && elem.nodeType === 1); 432 433 return null; 434 } else { 435 return elem.closest(tag); 436 } 437 }; 438 439 /* 440 * init 441 */ 442 443 // don't start script unless browser cuts the mustard 444 // (also passes if polyfills are used) 445 if ('addEventListener' in window && Array.prototype.indexOf) { 446 setUp(); 447 } 448 449 /* 450 * api 451 */ 452 453 return { 454 // returns string: the current input type 455 // opt: 'intent'|'input' 456 // 'input' (default): returns the same value as the `data-whatinput` attribute 457 // 'intent': includes `data-whatintent` value if it's different than `data-whatinput` 458 ask: function ask(opt) { 459 return opt === 'intent' ? currentIntent : currentInput; 460 }, 461 462 // returns string: the currently focused element or null 463 element: function element() { 464 return currentElement; 465 }, 466 467 // overwrites ignored keys with provided array 468 ignoreKeys: function ignoreKeys(arr) { 469 ignoreMap = arr; 470 }, 471 472 // overwrites specific char keys to update on 473 specificKeys: function specificKeys(arr) { 474 specificMap = arr; 475 }, 476 477 // attach functions to input and intent "events" 478 // funct: function to fire on change 479 // eventType: 'input'|'intent' 480 registerOnChange: function registerOnChange(fn, eventType) { 481 functionList.push({ 482 fn: fn, 483 type: eventType || 'input' 484 }); 485 }, 486 487 unRegisterOnChange: function unRegisterOnChange(fn) { 488 var position = objPos(fn); 489 490 if (position || position === 0) { 491 functionList.splice(position, 1); 492 } 493 } 494 }; 495 }(); 496 497/***/ }) 498/******/ ]) 499}); 500; 501/* 502 * laza.util - miscellaneous utility functions and prototype extensions 503 */ 504 505; 506 507// Ensuring no 'console is undefined' errors happen 508 509window.console = window.console || (function(){ 510 return { 511 log: function(msg) {} 512 }; 513 })(); 514 515// Ensuring log function exists 516var log = function() {}; 517 518/* 519 * Constants 520 */ 521 522var // Typeof check 523 UNDEF = 'undefined', 524 OBJECT = 'object', 525 FUNCTION = 'function', 526 STRING = 'string', 527 NUMBER = 'number', 528 BOOLEAN = 'boolean', 529 530 NOLINK = 'javascript:void(0)', 531 532 LOCAL = document.location.protocol === 'file:', 533 534 ONEDAY_S = 60 * 60 * 24, 535 ONEDAY_MS = 60 * 60 * 24 * 1000, 536 537 STARS = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="80" height="16" viewB
537ox="0 0 80 16" xml:space="preserve"><path d="M12,10.094l0.938,5.5L8,13l-4.938,2.594L4,10.094L0,6.219l5.531-0.813l2.469-5l2.469,5L16,6.219L12,10.094z"/><path d="M28,10.094l0.938,5.5L24,13l-4.938,2.594l0.938-5.5l-4-3.875l5.531-0.813l2.469-5l2.469,5L32,6.219L28,10.094z"/><path d="M44,10.094l0.938,5.5L40,13l-4.938,2.594l0.938-5.5l-4-3.875l5.531-0.813l2.469-5l2.469,5L48,6.219L44,10.094z"/><path d="M60,10.094l0.938,5.5L56,13l-4.938,2.594l0.938-5.5l-4-3.875l5.531-0.813l2.469-5l2.469,5L64,6.219L60,10.094z"/><path d="M76,10.094l0.938,5.5L72,13l-4.938,2.594l0.938-5.5l-4-3.875l5.531-0.813l2.469-5l2.469,5L80,6.219L76,10.094z"/></svg>'; 538 539 DIR_PATH = (function() { // Path to current folder on the site 540 let p = window.location.pathname, 541 level = document.getElementsByTagName('html')[0].getAttribute('data-level') || 0; 542 543 for (let i = 0; i < level; i++) { 544 p = p.substring(0, p.lastIndexOf('/')); 545 } 546 547 return p + '/'; 548 })(), 549 550 LOCALSTORAGE = (function() { // Test for local storage 551 try { 552 localStorage.setItem('_t', UNDEF); 553 localStorage.removeItem('_t'); 554 return true; 555 } catch(e) { 556 return false; 557 } 558 })(), 559 560 HISTORY = (function() { // Test for history 561 // Taken from Modernizr 3.1 562 let ua = navigator.userAgent; 563 564 if ((ua.indexOf('Android 2.') !== -1 || 565 (ua.indexOf('Android 4.0') !== -1)) && 566 ua.indexOf('Mobile Safari') !== -1 && 567 ua.indexOf('Chrome') === -1 && 568 ua.indexOf('Windows Phone') === -1) { 569 return false; 570 } 571 572 return (window.history && 'pushState' in window.history); 573 })(), 574 575 VEND = (function() { // Browser vendor test 576 let ua = navigator.userAgent; 577 /* 578 PC: 579 IE 8: "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)" 580 IE 9: "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0; .NET4.0C)" 581 IE 10: "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; .NET4.0E; .NET4.0C)" 582 Edge: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299" 583 Opera 12: "Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.15" 584 Firefox 21: "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0" 585 Chrome 27: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36" 586 Mac: 587 Chrome 27: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36" 588 Firefox 21: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0" 589 Safari 6: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/536.26.14 (KHTML, like Gecko) Version/6.0.1 Safari/536.26.14" 590 */ 591 if (ua.indexOf('Trident') > 0 || ua.indexOf('Edge') > 0) { 592 return 'ms'; 593 } else if (ua.indexOf('AppleWebKit') > 0) { 594 return 'webkit'; 595 } else if (ua.indexOf('Gecko') > 0) { 596 return 'moz'; 597 } else if (ua.indexOf('Presto') > 0) { 598 return 'o'; 599 } else if (ua.indexOf('Blink') > 0) { 600 return 'webkit'; 601 } 602 return ''; 603 })(), 604 605 JAVAFXBROWSER = navigator.userAgent.indexOf('JavaFX/14') > 0, 606 607 BACKFACEBUG = (function() { 608 var m = navigator.userAgent.match(/Edge\/(\d+\.\d+)/); 609 return m && parseInt(m[1]) <= 16; 610 })(), 611 612 TOUCH = (function() { // Touch event naming 613 614 if (/Trident|Edge/.test(navigator.userAgent)) { 615 // Setting MS events 616 if (window.navigator.pointerEnabled) { 617 return { 618 'START': 'pointerdown', 619 'MOVE': 'pointermove', 620 'END': 'pointerup', 621 'CANCEL': 'pointercancel' 622 }; 623 } 624 return { 625 'START': 'MSPointerDown', 626 'MOVE': 'MSPointerMove', 627 'END': 'MSPointerUp', 628 'CANCEL': 'MSPointerCancel' 629 }; 630 } 631 632 return { 633 'START': 'touchstart', 634 'MOVE': 'touchmove', 635 'END': 'touchend', 636 'CANCEL': 'touchcancel' 637 }; 638 })(), 639 640 TOUCHENABLED = function() { // Test for touch support 641 642 if (/Trident/.test(navigator.userAgent)) { 643 return (typeof navigator['maxTouchPoints'] !== UNDEF && navigator.maxTouchPoints); // || /IEMobile/.test(navigator.userAgent); 644 } else if (/Edge/.test(navigator.userAgent)) { 645 return (scrollbarWidth() == 0); 646 } else if (/(Chrome|CriOS)/.test(navigator.userAgent)) { 647 return /Mobile/.test(navigator.userAgent) || 'ontouchstart' in window; 648 } 649 return 'ontouchstart' in window; 650 }(), 651 652 ISIOSDEVICE = /^iP/.test(navigator.platform) || /^Mac/.test(navigator.platform) && navigator.maxTouchPoints && (navigator.maxTouchPoints > 4), 653 654 ISSEPARATEHOVER = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream, 655 656 SMALLSCREEN = window.innerWidth <= 480 || window.innerHeight <= 480 || (window.innerWidth <= 640 && window.innerHeight <= 640), 657 658 PIXELRATIO = window.hasOwnProperty('devicePixelRatio')? window.devicePixelRatio : 1, 659
660 ZOOM = window.hasOwnProperty('visualViewport')? window.visualViewport.scale : 1, 661 662 HIDPI = PIXELRATIO > 1.9, 663 //matchMedia("(-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2), (min-resolution: 192dpi)").matches; 664 665 HASPDFVIEWER = navigator.pdfViewerEnabled, 666 667 LANGUAGE = (navigator.hasOwnProperty('languages')? navigator.languages[0] : navigator.language), 668 669 WEBP_LOSSY = false, // WebP support detected asynchronously 670 WEBP_LOSSLESS = false, 671 checkWebpSupport = function(test, callback) { 672 let img = new Image(); 673 674 img.onload = function () { 675 callback((img.width > 0) && (img.height > 0)); 676 }; 677 678 img.onerror = function () { 679 callback(false); 680 }; 681 682 img.src = "data:image/webp;base64," + test; 683 }; 684 685// Checking WebP support by loading minimal images 686checkWebpSupport('UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA', function(r) { WEBP_LOSSY = r; }); 687checkWebpSupport('UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==', function(r) { WEBP_LOSSLESS = r; }); 688 689if (typeof DEBUG === UNDEF) { 690 var DEBUG = false; 691} 692 693if (typeof REL_PATH === UNDEF) { 694 var REL_PATH = ''; 695} 696 697if (typeof PAGE_NAME === UNDEF) { 698 var PAGE_NAME = 'index.html'; 699} 700 701if (typeof THUMBS_DIR === UNDEF) { 702 var THUMBS_DIR = 'thumbs'; 703} 704 705if (typeof SLIDES_DIR === UNDEF) { 706 var SLIDES_DIR = 'slides'; 707} 708 709/* 710 * Extending prototypes 711 */ 712 713if (!String.prototype.hasOwnProperty('trim')) { 714 715 String.wsp = []; 716 String.wsp[0x0009] = true; 717 String.wsp[0x000a] = true; 718 String.wsp[0x000b] = true; 719 String.wsp[0x000c] = true; 720 String.wsp[0x000d] = true; 721 String.wsp[0x0020] = true; 722 String.wsp[0x0085] = true; 723 String.wsp[0x00a0] = true; 724 String.wsp[0x1680] = true; 725 String.wsp[0x180e] = true; 726 String.wsp[0x2000] = true; 727 String.wsp[0x2001] = true; 728 String.wsp[0x2002] = true; 729 String.wsp[0x2003] = true; 730 String.wsp[0x2004] = true; 731 String.wsp[0x2005] = true; 732 String.wsp[0x2006] = true; 733 String.wsp[0x2007] = true; 734 String.wsp[0x2008] = true; 735 String.wsp[0x2009] = true; 736 String.wsp[0x200a] = true; 737 String.wsp[0x200b] = true; 738 String.wsp[0x2028] = true; 739 String.wsp[0x2029] = true; 740 String.wsp[0x202f] = true; 741 String.wsp[0x205f] = true; 742 String.wsp[0x3000] = true; 743 744 String.prototype.trim = function() { 745 var str = this + '', 746 j = str.length; 747 if (j) { 748 var ws = String.wsp, 749 i = 0; 750 --j; 751 while (j >= 0 && ws[str.charCodeAt(j)]) { 752 --j; 753 } 754 ++j; 755 while (i < j && ws[str.charCodeAt(i)]) { 756 ++i; 757 } 758 str = str.substring(i, j); 759 } 760 return str; 761 }; 762} 763 764if (!String.prototype.hasOwnProperty('trunc')) { 765 766 String.prototype.trunc = function( n ) { 767 var t = this + ''; 768 769 if (t.length <= n) { 770 return t.toString(); 771 } 772 773 var s = t.substring(0, n - 1), 774 i = s.lastIndexOf(' '); 775 776 return ((i > 6 && (s.length - i) < 20)? s.substring(0, i) : s) + '...'; 777 }; 778} 779 780if (!String.prototype.hasOwnProperty('startsWith')) { 781 782 String.prototype.startsWith = function( s ) { 783 return (this + '').substring(0, s.length) === s; 784 }; 785} 786 787if (!String.prototype.hasOwnProperty('endsWith')) { 788 789 String.prototype.endsWith = function( s ) { 790 return (this + '').substring(this.length - s.length) === s; 791 }; 792} 793 794 String.prototype.capitalize = function() { 795 return this.charAt(0).toUpperCase() + this.slice(1); 796 }; 797 798 String.prototype.unCamelCase = function() { 799 return this.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); 800 }; 801 802 String.prototype.getExt = function() { 803 var t = this + '', 804 i = t.lastIndexOf('.'); 805 return (i <= 0 || i >= t.length - 1)? '' : t.substring(i + 1); 806 }; 807 808 String.prototype.stripExt = function() { 809 var t = this + '', 810 i = t.lastIndexOf('.'); 811 return (i <= 0 || i > t.length - 1)? t : t.substring(0, i); 812 }; 813 814 String.prototype.hasExt = function(x) { 815 var t = (this + ''), 816 i = t.lastIndexOf('.'); 817 if (i >= 0) { 818 t = t.substring(i + 1).toLowerCase(); 819 return (x + ',').indexOf(t + ',') >= 0; 820 } 821 return !1; 822 }; 823 824 String.prototype.replaceExt = function( s ) { 825 var t = this + '', 826 i = t.lastIndexOf('.'); 827 return (i <= 0)? t : (t.substring(0, i + 1) + s); 828 }; 829 830 String.prototype.fixExtension = function() { 831 return (this + '').replace(/.gif$/gi, '.png').replace(/.tif+$/gi, '.jpg'); 832 }; 833 834 String.prototype.getDir = function() { 835 var u = (this + '').split('#')[0]; 836 return u.substring(0, u.lastIndexOf('/') + 1); 837 }; 838 839 String.prototype.getFile = function() { 840 var u = (this + '').split('#')[0]; 841 return u.substring(u.lastIndexOf('/') + 1); 842 }; 843 844 String.prototype.getRelpath = function(level) { 845 var t = (this + ''), 846 i = t.lastIndexOf('#'); 847 if (i === -1) { 848 i = t.length - 1; 849 } else { 850 i--; 851 } 852 for (; i >= 0; i--) { 853 if (t[i] === '/' && (level--) === 0) 854 break; 855 } 856 return t.substring(i + 1); 857 }; 858 859 String.prototype.fixUrl = function() { 860 var i, 861 j, 862 t = this + ''; 863 while ((i = t.indexOf('../')) > 0) { 864 if (i === 1 || (j = t.lastIndexOf('/', i - 2)) === -1) { 865 return t.substring(i + 3); 866 } 867 t = t.substring(0, j) + t.substring(i + 2); 868 } 869 return t; 870 }; 871 872 String.prototype.fullUrl = function() { 873 var t = this + ''; 874 if (!t.match(/^(http|ftp|file)/)) { 875 t = window.location.href.getDir() + t; 876 } 877 return t.fixUrl(); 878 }; 879 880 String.prototype.cleanupHTML = function() { 881 var htmlregex = [ 882 [ /<(b|h)r\/?>/gi, '\n' ], 883 [ /\&/g, '&' ], 884 [ /\ /g, ' ' ], 885 [ /\</g, '<' ], 886 [ /\>/g, '>' ], 887 [ /\&(m|n)dash;/g , '-' ], 888 [ /\'/g, '\'' ], 889 [ /\"/g, '"' ] 890 ], 891 t = this + ''; 892 893 for (var i = htmlregex.length - 1; i >= 0; i--) { 894 t = t.replace(htmlregex[i][0], htmlregex[i][1]); 895 } 896 897 return t; 898 }; 899 900 String.prototype.stripHTML = function(format) { 901 var s = (this + ''); 902 903 if (format) { 904 s = s.cleanupHTML(); 905 } 906 907 return s.replace(/<\/?[^>]+>/g, ' '); 908 }; 909 910 String.prototype.stripQuote = function() { 911 return (this + '').replace(/\"/gi, '"'); 912 }; 913 914 String.prototype.appendSep = function(s, sep) { 915 return (this.length? (this + (sep || ' · ')) : '') + s; 916 }; 917 918 String.prototype.rgb2hex = function() { 919 var t = this + ''; 920 if (t.charAt(0) === '#' || t === 'transparent') { 921 return t; 922 } 923 var n, r = t.match(/\d+/g), h = ''; 924 if ( r ) { 925 for (var i = 0; i < r.length && i < 3; ++i) { 926 n = parseInt( r[i], 10 ).toString(16); 927 h += ((n.length < 2)? '0' : '') + n; 928 } 929 return '#' + h; 930 } 931 return 'transparent'; 932 }; 933 934 String.prototype.template = function(s) { 935 if (typeof s === UNDEF || !this) { 936 return this; 937 } 938 if (!isNaN(parseFloat(s)) && isFinite(s)) { 939 s = s + ''; 940 } 941 var t = this + ''; 942 if (s.constructor === Array) { 943 for (var i = 0; i < s.length; ++i) { 944 t = t.replace(new RegExp('\\{' + i + '\\}', 'gi'), s[i]); 945 } 946 } else { 947 t = t.replace( /\{0\}/gi, s ); 948 } 949 return t; 950 }; 951 952 String.prototype.getSearchTerms = function() { 953 var t = this + ''; 954 955 if (t.indexOf('"') === -1) { 956 return t.split(' '); 957 } else { 958 var a = [], 959 i; 960 961 do {
962 if ((i = t.indexOf('"')) > 0) { 963 a.push.apply(a, t.substring(0, i).split(' ')); 964 } 965 t = t.substring(i + 1); 966 i = t.indexOf('"'); 967 if (i < 0) { 968 a.push(t); 969 break; 970 } 971 a.push(t.substring(0, i)); 972 t = t.substring(i + 1); 973 974 } while (t.length); 975 976 return a; 977 } 978 }; 979 980 String.prototype.objectify = function() { 981 982 if (!this || !this.length) { 983 return this; 984 } 985 986 var t = this + ''; 987 988 if (t.charAt(0) === '?' || t.charAt(0) === '#') { 989 t = t.substring(1); 990 } 991 992 var r = {}, 993 o, 994 os = t.split('&'); 995 996 for (var i = 0, l = os.length; i < l; ++i) { 997 o = os[i].split('='); 998 if (o.length > 1) { 999 // Assing value 1000 if (o[0] === 'img') { 1001 // No decoding for image name 1002 r.img = o[1]; 1003 } else { 1004 r[o[0]] = decodeURIComponent(o[1]); 1005 } 1006 } else { 1007 // No value provided 1008 r[o[0]] = null; 1009 } 1010 } 1011 return r; 1012 }; 1013 1014 // Test if 's' is in the string 1015 String.prototype.testIn = function(s) { 1016 if (typeof s !== 'string') { 1017 s = s + ''; 1018 } 1019 return (new RegExp(this, 'i')).test(s); 1020 }; 1021 1022 // Testing exact match 1023 String.prototype.testExactMatch = function(s) { 1024 if (s.constructor !== Array) { 1025 return this == s + ''; 1026 } else { 1027 for (var i = 0, l = s.length; i < l; ++i) { 1028 if (this == s[i]) { 1029 return true; 1030 } 1031 } 1032 } 1033 return false; 1034 }; 1035 1036 // Fixing jAlbum paths 1037 String.prototype.fixjAlbumPaths = function(resPath, rootPath, relPath) { 1038 var s = (this + '').replace(/\%24/g, '\$'); 1039 1040 if (resPath) { 1041 s = s.replace(/\${resPath}/g, resPath); 1042 } 1043 if (rootPath) { 1044 s = s.replace(/\${rootPath}/g, rootPath); 1045 } 1046 if (relPath) { 1047 s = s.replace(/\${relPath}/g, relPath); 1048 } 1049 return s; 1050 }; 1051 1052 // Ignore case but exact match 1053 String.prototype.testMatch = function(s) { 1054 if (typeof s === UNDEF) { 1055 return false; 1056 } 1057 1058 var t = this.toLowerCase(); 1059 1060 if (s.constructor !== Array) { 1061 return t === (s + '').toLowerCase(); 1062 } else { 1063 for (var i = 0, l = s.length; i < l; ++i) { 1064 if (t === s[i].toLowerCase()) { 1065 return true; 1066 } 1067 } 1068 } 1069 1070 return false; 1071 }; 1072 1073 /* Search term in string (case insensitive) :: searching terms only in word beginnings 1074 this = target string, 1075 s = serch term(s), 1076 exact = matches word boundaries, 1077 conj = all terms must match (default: match any) 1078 */ 1079 1080 String.prototype.searchTerm = function(s, exact, conj, caseSensitive) { 1081 if (typeof s === UNDEF || !this.length) { 1082 return false; 1083 } 1084 1085 var cs = (typeof caseSensitive !== UNDEF) ? caseSensitive : false, 1086 t = cs ? (this + '') : (this + '').toLowerCase(), 1087 exact = (typeof exact !== UNDEF) ? exact : false, 1088 conj = (typeof conj !== UNDEF) ? conj : false, 1089 1090 // Split target into word tokens once 1091 tWords = t.split(/\W+/).filter(Boolean), 1092 1093 _find = function(term) { 1094 term = cs ? term : term.toLowerCase(); 1095 if (exact) { 1096 return tWords.some(function(w) { return w === term; }); 1097 } else { 1098 // Match any word in t that *starts with* term 1099 return tWords.some(function(w) { return w.indexOf(term) === 0; }); 1100 } 1101 }; 1102 1103 // Normalise s into an array of terms 1104 if (!Array.isArray(s)) { 1105 s = s.split(/\W+/).filter(Boolean); 1106 } else { 1107 // Flatten: each element may itself contain separators 1108 s = s.reduce(function(acc, item) { 1109 return acc.concat(item.split(/\W+/).filter(Boolean)); 1110 }, []); 1111 } 1112 1113 if (conj) { 1114 return s.every(function(term) { return _find(term); }); 1115 } else { 1116 return s.some(function(term) { return _find(term); }); 1117 } 1118 }; 1119 1120 // Creating hash code 1121 String.prototype.hashCode = function() { 1122 for (var h = 0, i = 0, l = this.length; i < l; ++i) { 1123 h = (h << 5) - h + this.charCodeAt(i); 1124 h &= h; 1125 } 1126 return h; 1127 }; 1128 1129 // > Min && < Max 1130 Math.minMax = function(a, b, c) { 1131 if (typeof a === STRING) a = parseFloat(a); 1132 if (typeof b === STRING) b = parseFloat(b); 1133 if (typeof c === STRING) c = parseFloat(c); 1134 return (b < a)? a : ((b > c)? c : b); 1135 }; 1136 1137 // Gets range 1138 Math.getRange = function(a, r) { 1139 if (r.constructor !== Array) { 1140 return (a >= r)? 1 : 0; 1141 } 1142 if (r.length === 1) { 1143 return (a >= r[0])? 1 : 0; 1144 } 1145 if (a < r[0]) { 1146 return 0; 1147 } 1148 for (var i = 1; i < r.length; i++) { 1149 if (a >= r[i - 1] && a < r[i]) { 1150 break; 1151 } 1152 } 1153 return i; 1154 }; 1155 1156/* 1157 * New functions and variables for the global context - no jQuery depen
1157dency 1158 */ 1159 1160var encodeJ = []; 1161 encodeJ[33] = '%21'; // ! 1162 encodeJ[35] = '%23'; // # 1163 encodeJ[36] = '%24'; // $ 1164 encodeJ[38] = '%26'; // & 1165 encodeJ[39] = '%27'; // ' 1166 encodeJ[40] = '%28'; // ( 1167 encodeJ[41] = '%29'; // ) 1168 encodeJ[43] = '%2B'; // + 1169 encodeJ[44] = '%2C'; // , 1170 encodeJ[59] = '%3B'; // ; 1171 encodeJ[60] = '%3C'; // < 1172 encodeJ[61] = '%3D'; // = 1173 encodeJ[62] = '%3E'; // > 1174 encodeJ[63] = '%3F'; // ? 1175 encodeJ[64] = '%40'; // @ 1176 encodeJ[123] = '%7B'; // { 1177 encodeJ[124] = '%7C'; // | 1178 encodeJ[125] = '%7D'; // } 1179 1180var transCodeJ = []; 1181 transCodeJ[33] = '%21'; // ! 1182 transCodeJ[39] = '%27'; // ' 1183 transCodeJ[40] = '%28'; // ( 1184 transCodeJ[41] = '%29'; // ) 1185 1186 1187 // Unique ID generator 1188var uidCnt = 0, 1189 getUniqueId = function(pfx) { 1190 return (pfx || 'uid') + '_' + (++uidCnt); 1191 }, 1192 1193 // Object is empty? 1194 1195 isEmpty = function(o) { 1196 1197 if (o == null) { 1198 return true; 1199 } 1200 return (Object.getOwnPropertyNames(o)).length === 0; 1201 }, 1202 1203 // Parametrize 1204 1205 paramize = function(o) { 1206 1207 if (typeof o === 'number') { 1208 return '' + o; 1209 } else if (typeof o === 'string') { 1210 return o; 1211 } else if (typeof o === 'object') { 1212 // 1 level depth only 1213 var s = '', 1214 op = Object.getOwnPropertyNames(o), 1215 ol = op.length; 1216 for (var i = 0; i < ol; i++) { 1217 if (o[op[i]] !== null) { 1218 if (op[i] === 'img') { 1219 s += '&img=' + o[op[i]]; 1220 } else { 1221 s += '&' + op[i] + '=' + encodeURIComponent(o[op[i]]); 1222 } 1223 } 1224 } 1225 if (s.length) { 1226 return s.substring(1); 1227 } 1228 } 1229 return ''; 1230 }, 1231 1232 // All elements are true in an array 1233 1234 allTrue = function(o) { 1235 1236 if (o && o.constructor === Array) { 1237 for (var i = 0; i < o.length; ++i) { 1238 if (!o[i]) { 1239 return false; 1240 } 1241 } 1242 return true; 1243 } 1244 return o === true; 1245 }, 1246 1247 // Remove empty strings from array 1248 1249 removeEmpty = function(a) { 1250 1251 if (a && a.constructor === Array) { 1252 var b = new Array(), 1253 i; 1254 1255 for (i = 0; i < a.length; i++) { 1256 if (typeof a[i] === STRING && a[i].length) { 1257 b.push(a[i]); 1258 } 1259 } 1260 1261 return b; 1262 } 1263 1264 return a; 1265 }, 1266 1267 // Get event coordinates 1268 1269 getCoords = function(e) { 1270 var x, y; 1271 1272 if (!e.touches) { 1273 // No touch 1274 return { 1275 x: e.clientX, 1276 y: e.clientY 1277 }; 1278 } else if (e.touches.length == 1) { 1279 return { 1280 x: e.touches[0].clientX, 1281 y: e.touches[0].clientY 1282 } 1283 } else if (e.changedTouches && e.changedTouches.length == 1) { 1284 return { 1285 x: e.changedTouches[0].clientX, 1286 y: e.changedTouches[0].clientY 1287 } 1288 } 1289 return null; 1290 }, 1291 1292 /* 1293 * Translate function 1294 * returns a translated key, or the default value (if exists), or the key itself de-camelcased 1295 */ 1296 1297 translate = function(key, def) { 1298 1299 key = key.trim(); 1300 1301 if (typeof Texts !== UNDEF) { 1302 if (Texts.hasOwnProperty(key)) { 1303 return Texts[key]; 1304 } 1305 } 1306 1307 if (typeof def !== UNDEF) { 1308 // Using the default 1309 if (DEBUG && console) { 1310 console.log('Using default translation: '+key+'='+def); 1311 } 1312 return def; 1313 } 1314 1315 if (DEBUG && console) { 1316 console.log('Missing translation: '+key); 1317 } 1318 1319 var s = key.replace(/([A-Z])/g, ' $1').toLowerCase(); 1320 1321 return s.charAt(0).toUpperCase() + s.slice(1); 1322 }, 1323 1324 /* 1325 * Simpler method 1326 * text = getKeys('key1,key2,key3', [defaults]) 1327 */ 1328 1329 getKeys = function(keys, def) { 1330 var t = {}, i, k = keys.split(','), l = k.length; 1331 1332 for (i = 0; i < l; i++) { 1333 t[k[i]] = translate(k[i], def[k[i]]); 1334 } 1335 1336 return t; 1337 }, 1338 1339 /* 1340 * Finds translation for each key in def Object 1341 * def should contain only elements that need translation 1342 */ 1343 1344 getTranslations = function(def) { 1345 var t = {}, k; 1346 1347 for (k in def) { 1348 if (typeof def[k] === 'object') { 1349 t[k] = getTranslations(def[k]); 1350 } else { 1351 t[k] = translate(k, def[k]); 1352 } 1353 } 1354 1355 return t; 1356 }, 1357 1358 // Reading keys: k="name1,name2,... from attr="data-k" into m 1359 1360 readData = function(el, k) { 1361 var o = {}; 1362 1363 if (el && el.length && k) { 1364 k = k.split(','); 1365 var v; 1366 for (var i = 0; i < k.length; i++) { 1367 if ((v = el.data(k[i])) != null) { 1368 o[k[i]] = v; 1369 } 1370 } 1371 } 1372 return o; 1373 }, 1374 1375 /* 1376 * Nice float numbers 1377 */ 1378 1379 niceByte = function(val) { 1380 var v = parseFloat(val), 1381 va = Math.abs(v), 1382 round = function(v) { 1383 return (v < 10)? v.toFixed(1) : Math.round(v); 1384 }; 1385 1386 if (va < 0.0001) { 1387 return '0'; 1388 } else if (va < 0.1) { 1389 return v.toFixed(3); 1390 } else if (va < 1) { 1391 return v.toFixed(2); 1392 } else if (va < 1000) { 1393 return round(v); 1394 } else if (va < 1000000) { 1395 return round(v / 1000) + 'k'; 1396 } else if (va < 1000000000) { 1397 return round(v / 1000000) + 'M'; 1398 } else if (va < 1000000000000) { 1399 return round(v / 1000000000) + 'G'; 1400 } 1401 1402 return v.toExponential(); 1403 }, 1404 1405 /* 1406 * Nice time formatting 1407 */ 1408 1409 niceTime = function(val) { 1410 var v = parseFloat(val), 1411 va = Math.abs(v), 1412 s, 1413 m, 1414 addZero = function(v) { 1415 return (v < 10)? ('0' + v) : ('' + v); 1416 }, 1417 round = function(v) { 1418 return (v < 10)? v.toFixed(1) : Math.round(v); 1419 }; 1420 1421 if (va < 0.000001) { 1422 // Don't care about smaller 1423 return '0s'; 1424 } else if (va < 0.001) { 1425 // microseconds 1426 return round(v * 1000000) + 'µs'; 1427 } else if (va < 1) { 1428 // milliseconds 1429 return round(v * 1000) + 'ms'; 1430 } else if (va < 60) { 1431 // secoonds 1432 return round(v) + 's'; 1433 } else { 1434 // hh mm ss 1435 s = Math.round(va) % 60; 1436 m = Math.floor(va) / 60 % 60; 1437 if (va < 3600) { 1438 return m + 'm' + addZero(s) + 's'; 1439 } 1440 return Math.floor(va / 3600) + 'h' + addZero(m) + 'm' + addZero(s) + 's'; 1441 } 1442 }, 1443 1444 /* 1445 * Currency 1446 */ 1447 1448 toCurrency = function(n, curr) { 1449 var v = parseFloat(n); 1450
1451 return v.toLocaleString(LANGUAGE, { 1452 style: 'currency', 1453 currency: curr || 'EUR', 1454 }); 1455 }, 1456 1457 /* 1458 * Relative date 1459 */ 1460 1461 getRelativeDate = function(days) { 1462 1463 if (!days) { 1464 return translate('today'); 1465 } 1466 1467 if (days === 1) { 1468 return translate('yesterday'); 1469 } 1470 1471 var s, n; 1472 1473 if (days >= 730) { 1474 s = translate('yearsAgo'); 1475 n = Math.round(days / 365); 1476 } else if (days >= 60) { 1477 s = translate('monthsAgo'); 1478 n = Math.round(days / 30.5); 1479 } else { 1480 s = translate('daysAgo'); 1481 n = days; 1482 } 1483 1484 return s.replace('{0}', n); 1485 }, 1486 1487 /* 1488 * Timespan 1489 */ 1490 1491 getTimespan = function(days) { 1492 1493 if (!days) { 1494 return translate('inThePast24Hours'); 1495 } 1496 1497 if (days < 2) { 1498 return translate('inThePast48Hours'); 1499 } 1500 1501 var s, n; 1502 1503 if (days >= 730) { 1504 s = translate('inThePastNYears'); 1505 n = Math.round(days / 365); 1506 } else if (days >= 60) { 1507 s = translate('inThePastNMonths'); 1508 n = Math.round(days / 30.42); 1509 } else { 1510 s = translate('inThePastNDays'); 1511 n = Math.round(days); 1512 } 1513 1514 return s.replace('{0}', n); 1515 }, 1516 1517 /* 1518 * Reads float from a fraction notation 1519 */ 1520 1521 getFractionValue = function(s) { 1522 var val = s.match(/^([\d\.]+)\/([\d\.]+)/); 1523 1524 if (val && val.length > 1 && parseFloat(val[2]) !== 0) { 1525 return val[1] / val[2]; 1526 } 1527 1528 return s; 1529 }, 1530 1531 /* 1532 * Converts exposure text to ms 1533 */ 1534 1535 getExposure = function(s) { 1536 1537 s = s.includes('/')? getFractionValue(s) : parseFloat(s); 1538 1539 return s; 1540 }, 1541 1542 /* 1543 * Relative path:: path/to/folder/ or ../../path/to/folder/ 1544 * always ends with '/' unless current folder 1545 */ 1546 1547 getRelativePath = function(from, to) { 1548 1549 if (typeof to === UNDEF || typeof from === UNDEF) { 1550 // invalid 1551 return ''; 1552 } 1553 1554 if (from === './') { 1555 // Current folder 1556 from = REL_PATH; 1557 } 1558 1559 if (to === './') { 1560 // Current folder 1561 to = REL_PATH; 1562 } 1563 1564 if (!to.endsWith('/')) { 1565 to += '/'; 1566 } 1567 1568 if (!from.endsWith('/')) { 1569 from += '/'; 1570 } 1571 1572 if (from === to) { 1573 // Same folder 1574 return ''; 1575 } 1576 1577 if (from === '/') { 1578 // From root 1579 return to; 1580 } 1581 1582 var fp = from.split('/').filter(Boolean); 1583 1584 if (to === '/') { 1585 // To root 1586 return getParentFolderLink(fp.length); 1587 } 1588 1589 // Generic case 1590 var tp = to.split('/').filter(Boolean); 1591 1592 while (fp.length && tp.length && fp[0] === tp[0]) { 1593 fp.shift(); 1594 tp.shift(); 1595 } 1596 1597 return getParentFolderLink(fp.length) + (tp.length? (tp.join('/') + '/') : ''); 1598 }, 1599 1600 getParentFolderLink = function(depth) { 1601 return (depth > 0)? '../../../../../../../../../../../../../../../../../../../../'.substring(0, 3 * depth) : ''; 1602 }, 1603 1604 /* 1605 * Encode the same as Java 1606 */ 1607 1608 encodeAsJava = function(s) { 1609 var r = '', 1610 ap = /^(https?:|file:)?\/\//i; 1611 1612 s = ap.test(s)? encodeURI(s) : encodeURIComponent(s); 1613 for (var i = 0; i < s.length; i++) { 1614 r += encodeJ[s.charCodeAt(i)] || s.charAt(i); 1615 } 1616 1617 return r; 1618 }, 1619 1620 /* 1621 * Encode the same as Java 1622 */ 1623 1624 transcodeJavaURIComponent = function(s) { 1625 var r = ''; 1626 1627 for (var i = 0; i < s.length; i++) { 1628 r += transCodeJ[s.charCodeAt(i)] || s.charAt(i); 1629 } 1630 1631 return r; 1632 }, 1633 1634 1635 /* 1636 * Pure JS shallow extend function 1637 */ 1638 1639 extend = function() { 1640 1641 if (arguments.length < 2) { 1642 return arguments[0] || {}; 1643 } 1644 1645 var r = arguments[0]; 1646 1647 for (var i = 1; i < arguments.length; i++) { 1648 if (arguments[i] != null) { 1649 for (var key in arguments[i]) { 1650 if (arguments[i].hasOwnProperty(key)) { 1651 r[key] = arguments[i][key]; 1652 } 1653 } 1654 } 1655 } 1656 1657 return r; 1658 }, 1659 1660 /* 1661 * Passing defaults to libraries 1662 */ 1663 1664 passDefaults = function(src, dst, props) { 1665 1666 if (src && dst) { 1667 if (typeof props !== UNDEF) { 1668 // Copy the requested properties 1669 props = props.split(','); 1670 1671 for (var i = 0; i < props.length; i++) { 1672 if (src.hasOwnProperty(props[i])) { 1673 dst[props[i]] = src[props[i]]; 1674 } 1675 } 1676 } else { 1677 // Copy all 1678 for (var prop in src) { 1679 dst[prop] = src[prop]; 1680 } 1681 } 1682 } 1683 }, 1684 1685 /* 1686 * Reading user preferences from cookies 1687 */ 1688 1689 readUserPrefs = function(dst, props) { 1690 1691 if (typeof props !== UNDEF && dst) { 1692 props = props.split(','); 1693 1694 var p; 1695 1696 for (var i = 0; i < props.length; i++) { 1697 if ((p = $.cookie(props[i])) !== null) { 1698 dst[props[i]] = p; 1699 } 1700 } 1701 } 1702 }, 1703 1704 // Saving preferences 1705 1706 savePrefs = function(settings, prefs, name) { 1707 var pref = {}, 1708 n; 1709 1710 for (var i = 0; i < prefs.length; i++) { 1711 1712 n = prefs[i];
1713 1714 if (settings.hasOwnProperty(n)) { 1715 pref[n] = settings[n]; 1716 } 1717 } 1718 1719 if (pref) { 1720 $.cookie(typeof name === UNDEF? 'lb_pref' : name, pref); 1721 } 1722 }, 1723 1724 // Loading preferences 1725 1726 loadPrefs = function(settings, prefs, name) { 1727 var pref = $.cookie(typeof name === UNDEF? 'lb_pref' : name), 1728 p, 1729 n; 1730 1731 if (pref) { 1732 1733 for (p in prefs) { 1734 1735 n = prefs[p]; 1736 1737 if (pref.hasOwnProperty(n)) { 1738 settings[n] = (pref[n] === 'true' || pref[n] === 'false')? !!pref[n] : pref[n]; 1739 } 1740 } 1741 } 1742 }, 1743 1744 /* 1745 * History management 1746 */ 1747 1748 // Adds one new state component 1749 1750 addParam = function(indexName, params, title) { 1751 1752 if (HISTORY) { 1753 var hash = window.location.hash; 1754 1755 if (hash) { 1756 // Already has hash, adding to params 1757 if (history.state) { 1758 if (typeof history.state === STRING) { 1759 history.state = history.state.objectify(); 1760 } 1761 if (history.state.hasOwnProperty('img')) { 1762 history.state.img = encodeURIComponent(history.state.img); 1763 } 1764 params = extend(history.state, params); 1765 } else { 1766 if (hash.charAt(0) === '#') { 1767 hash = hash.substring(1); 1768 } 1769 params = extend(hash.objectify(), params); 1770 } 1771 } 1772 1773 hash = '#' + paramize(params); 1774 1775 if (hash !== window.location.hash) { 1776 history.pushState(params, (typeof title === UNDEF)? '' : title, (hash.length > 1)? hash : (indexName || 'index.html')); 1777 } 1778 } 1779 }, 1780 1781 // Sets a new state 1782 1783 setParam = function(indexName, params, title) { 1784 1785 if (HISTORY) { 1786 var hash = '#' + paramize(params); 1787 1788 if (hash !== window.location.hash) { 1789 history.pushState(params, (typeof title === UNDEF)? '' : title, (hash.length > 1)? hash : (indexName || 'index.html')); 1790 } 1791 } 1792 }, 1793 1794 // Removes one parameter (String) 1795 1796 removeParam = function(indexName, param, title) { 1797 1798 if (HISTORY) { 1799 var hash = window.location.hash, 1800 params; 1801 1802 if (hash) { 1803 if (typeof param === UNDEF) { 1804 hash = ''; 1805 history.pushState('', '', indexName || 'index.html'); 1806 } else { 1807 if (hash.charAt(0) === '#') { 1808 hash = hash.substring(1); 1809 } 1810 params = hash.objectify(); 1811 if (params.hasOwnProperty(param)) { 1812 delete params[param]; 1813 hash = '#' + paramize(params); 1814 } 1815 history.pushState(params, (typeof title === UNDEF)? '' : title, (hash.length > 1)? hash : (indexName || 'index.html')); 1816 } 1817 } 1818 } 1819 }, 1820 1821 // Reading state object 1822 1823 readParam = function() { 1824 1825 if (HISTORY) { 1826 if (history.state) { 1827 if (typeof history.state === STRING) { 1828 history.state = history.state.objectify(); 1829 } 1830 if (history.state.hasOwnProperty('img')) { 1831 history.state.img = encodeURIComponent(history.state.img); 1832 } 1833 return history.state; 1834 } 1835 1836 var hash = window.location.hash; 1837 if (hash.charAt(0) === '#') { 1838 hash = hash.substring(1); 1839 } 1840 return hash.objectify(); 1841 } 1842 1843 return null; 1844 }, 1845 1846 // Removing ?search 1847 1848 removeSearch = function(title) { 1849 1850 if (HISTORY) { 1851 history.replaceState(history.state, (typeof title === UNDEF)? '' : title, window.location.href.replace(window.location.search, '')); 1852 } 1853 }, 1854 1855 // Restoring scroll position 1856 1857 restoreScrollPosition = function() { 1858 1859 if (LOCALSTORAGE) { 1860 var lt = $.cookie('last-touch'), 1861 dt = (new Date()).valueOf(), 1862 p = $.cookie(REL_PATH + PAGE_NAME + ':scroll-pos'), 1863 setTop = function(p, tries) { 1864 if ($('html').height() >= p) { 1865 $('html').scrollTop(p); 1866 } else if (tries) { 1867 setTimeout(setTop, 1000, p, tries - 1); 1868 } 1869 }; 1870 1871 if (lt && ((dt - lt) < 2000) && p) { 1872 // Restore only if within 2 secs of leaving another page in the same album 1873 setTop(p, 6); 1874 } 1875 } 1876 }, 1877 1878 // Saving scroll position 1879 1880 saveScrollPosition = function() { 1881 1882 if (LOCALSTORAGE) { 1883 var p = $('html').scrollTop(); 1884 1885 $.cookie(REL_PATH + PAGE_NAME + ':scroll-pos', p? p : null, 300); 1886 $.cookie('last-touch', (new Date()).valueOf(), 300); 1887 } 1888 }, 1889 1890 // Push all elements of an array into another array 1891 1892 pushAll = function(a, b) { 1893 if (a instanceof Array) { 1894 if (b instanceof Array) { 1895 for (var i = 0, l = b.length; i < l; i++) { 1896 a.push(b[i]); 1897 } 1898 } else { 1899 a.push(b); 1900 } 1901 } 1902 }, 1903 1904 // Push only elements of an array into another array which are absent 1905 1906 pushNew = function(a, b) { 1907 if (a instanceof Array) { 1908 if (b instanceof Array) { 1909 for (var i = 0, l = b.length; i < l; i++) { 1910 if (!a.includes(b[i])) { 1911 a.push(b[i]); 1912 } 1913 } 1914 } else { 1915 if (!a.includes(b)) { 1916 a.push(b); 1917 } 1918 } 1919 } 1920 }, 1921 1922 // Video duration in ms 1923 1924 videoDurationMs = function(d) { 1925 var m = d.match(/(\d{2})\:(\d{2})\:(\d{2})(\.(\d+))?/); 1926 return m? ((m[5]? Math.round(parseFloat('0.' + m[5]) * 1000) : 0) + parseInt(m[3]) * 1000 + parseInt(m[2]) * 60000 + parseInt(m[1]) * 3600000) : null; 1927 }, 1928 1929 // Guess width / height from HTML code 1930 1931 guessDimensions = function(c) { 1932 var mw, 1933 mh, 1934 d = []; 1935 1936 if (!c) { 1937 return null; 1938 } 1939 1940 mw = c.match(/<\w+\s[^>]*\swidth="([\d\.]+)"/i) || 1941 c.match(/<\w+\s[^>]*\sstyle=".*width:\s?([\d\.]+)(r?em|px)"/i);
1942 1943 mh = c.match(/<\w+\s[^>]*\sheight="([\d\.]+)"/i) || 1944 c.match(/<\w+\s[^>]*\sstyle=".*height:\s?([\d\.]+)(r?em|px)"/i); 1945 1946 if (mw) { 1947 d[0] = parseFloat(mw[1]); 1948 if (mw.length > 2 && mw[2] !== 'px') { 1949 d[0] *= 16; 1950 } 1951 1952 if (mh) { 1953 d[1] = parseFloat(mh[1]); 1954 if (mh.length > 2 && mh[2] !== 'px') { 1955 d[1] *= 16; 1956 } 1957 } else { 1958 d[1] = .75 * d[0]; 1959 } 1960 1961 return d; 1962 } 1963 1964 return null; 1965 1966 }, 1967 1968 // Currency codes 1969 1970 currencyToHtml = { 1971 'USD': 'US$', 1972 'EUR': '€', 1973 'GBP': 'GB£', 1974 'JPY': '¥', 1975 'HUF': 'Ft', 1976 'CAD': 'CAN$', 1977 'AUD': 'AU$', 1978 'RUB': '₽' 1979 }, 1980 1981 codeToCurrency = function(code) { 1982 1983 if (currencyToHtml.hasOwnProperty(code)) { 1984 return currencyToHtml[code]; 1985 } 1986 1987 return code; 1988 }, 1989 1990 currencyToHtmlShort = { 1991 'USD': '$', 1992 'EUR': '€', 1993 'GBP': '£', 1994 'JPY': '¥', 1995 'HUF': 'Ft', 1996 'CAD': '$', 1997 'AUD': '$', 1998 'RUB': '₽' 1999 }, 2000 2001 codeToShortCurrency = function(code) { 2002 2003 if (currencyToHtmlShort.hasOwnProperty(code)) { 2004 return currencyToHtmlShort[code]; 2005 } 2006 2007 return code; 2008 }, 2009 2010 // Printing an image with caption 2011 2012 printImage = function(src, title, caption) { 2013 2014 if (!src) { 2015 return; 2016 } 2017 2018 var pw = window.open('about:blank', 'print', 'location=no,status=no,titlebar=no'); 2019 2020 pw.document.open();
2021 pw.document.write('<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8">\n<title>' + (title || 'Print') + '</title>' + 2022 '\n<script>printOut=function(){window.print();window.close();}</scr' + 'ipt>' + 2023 '\n<style>body{margin:0;padding:0;text-align:center;overflow:hidden;}\nimg{display:block;width:100%;height:auto;vertical-align:top;}</style>' + 2024 '\n</head>\n<body onLoad="setTimeout(printOut,100)">' + 2025 '<img src="' + src + '">' + (caption || '') + 2026 '</body>\n</html>'); 2027 pw.document.close(); 2028 }, 2029 2030 // Getting file size asynchronously and executing the passed function when ready 2031 2032 getFileSize = function(url, doneFn, args) { 2033 var http = new XMLHttpRequest(); 2034 2035 // Opening HEAD, asynchronous way 2036 http.open('HEAD', url, true); 2037 2038 // Handling ready state 2039 http.onreadystatechange = function() { 2040 if (this.readyState == this.DONE) { 2041 if (this.status === 200) { 2042 if (typeof doneFn === FUNCTION) { 2043 if (typeof args !== UNDEF) { 2044 doneFn.call(this, [ this.getResponseHeader('content-length') ], args); 2045 } else { 2046 doneFn.call(this, [ this.getResponseHeader('content-length') ]); 2047 } 2048 } 2049 } 2050 } 2051 }; 2052 2053 // Submitting request 2054 http.send(); 2055 }, 2056 2057 // Scroll body or html element so el (the target element) get in view 2058 2059 moveInView = function(el) { 2060 var te = $(el); 2061 2062 if (te.length && te.is(':visible')) { 2063 var to = te.offset().top, 2064 target = navigator.userAgent.match(/(iPod|iPhone|iPad)/)? $('body') : $('html'), 2065 st = target.scrollTop(); 2066 2067 if (to < st || to > (st + window.outerHeight - 80)) { 2068 if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) { 2069 window.scrollTo(0, to); 2070 } else { 2071 target.animate({ 2072 scrollTop: to 2073 }, 500, function() { 2074 target.clearQueue(); 2075 }); 2076 } 2077 } 2078 } 2079 }, 2080 2081 // Simple decryption 2082 2083 xDecrypt = function(c) { 2084 2085 if (typeof c !== 'string') { 2086 return ''; 2087 } 2088 2089 var xs = [0x93,0xA3,0x57,0xFE,0x99,0x04,0xC6,0x17], 2090 cl = c.length, 2091 sl = Math.ceil(cl / 8) * 5, 2092 src = new Array(sl), 2093 r = '', 2094 i, 2095 j = 0, 2096 k, 2097 v; 2098 2099 for (i = 0; i < sl; i++) { 2100 src[i] = 0; 2101 } 2102 2103 for (i = 0; i < cl; i++) { 2104 if ((v = c.charCodeAt(i) - 0x30) > 9) { 2105 v -= 7; 2106 } 2107 v <<= 11 - j % 8; 2108 k = j >> 3; 2109 if (k < sl) { 2110 src[k] |= v >> 8; 2111 if (++k < sl) { 2112 src[k] |= v & 0xff; 2113 } 2114 } 2115 j += 5; 2116 } 2117 2118 for (i = 0; i < sl; i++) { 2119 src[i] ^= xs[i % 8]; 2120 } 2121 2122 sl = src[0] | (src[1] << 8); 2123 2124 for (v = 0, i = 4; i < sl; i++) { 2125 r += String.fromCharCode(src[i]); 2126 v += src[i]; 2127 } 2128 2129 if (v != ((src[2] & 0xff) | (src[3] << 8))) 2130 r = ''; 2131 2132 return r; 2133 }, 2134 2135 // Getting computed style 2136 2137 getStyle = function(el, style) { 2138 2139 if (el instanceof Element) { 2140 if (document.defaultView && document.defaultView.getComputedStyle) { 2141 return document.defaultView.getComputedStyle(el, '').getPropertyValue(style.unCamelCase()); 2142 } else if (el.currentStyle) { 2143 return el.currentStyle[style]; 2144 } 2145 } 2146 2147 return null; 2148 }, 2149 2150 // Testing scrollbar width for mobile detection 2151 2152 scrollbarWidth = function() { 2153 2154 var div = document.createElement("div"), 2155 sw = 0; 2156 2157 div.style.cssText = 'width:100px;height:100px;overflow:scroll !important;position:absolute;top:-9999px'; 2158 if (document.body) { 2159 document.body.appendChild(div); 2160 sw = div.offsetWidth - div.clientWidth; 2161 document.body.removeChild(div); 2162 } 2163 2164 return sw; 2165 }, 2166 2167 // Adding class without jQuery (can be used anytime) 2168 2169 addClass = function(el, className) { 2170 2171 if (el.classList) { 2172 el.classList.add(className); 2173 } else { 2174 el.className += ' ' + className; 2175 } 2176 }, 2177 2178 // Is the page zoomed? 2179 2180 pageZoomed = function() { 2181 2182 if (window.hasOwnProperty('visualViewport')) { 2183 return window.visualViewport.scale > ZOOM; 2184 } 2185 2186 return false; 2187 }, 2188 2189 /********************************************************* 2190 * Video play 2191 */ 2192 2193 playVideo = function(el, doneFn) { 2194 2195 if (el && el.nodeName === 'VIDEO') { 2196 2197 if (el.paused) {
2198 let playPromise = el.play(); 2199 2200 if (typeof playPromise !== UNDEF) { 2201 playPromise.then(() => { 2202 // Plays immediately 2203 addClass(el, 'playing'); 2204 if (typeof doneFn === FUNCTION) { 2205 doneFn.call(null); 2206 } 2207 }).catch(err => { 2208 if (err.name === 'NotAllowedError') { 2209 // Trying muted 2210 el.muted = true; 2211 el.play().then(() => { 2212 // Works 2213 console.log('Falling back to muted autoplay! Unmute the video manually!'); 2214 addClass(el, 'playing'); 2215 if (typeof doneFn === FUNCTION) { 2216 doneFn.call(null); 2217 } 2218 }).catch(err => { 2219 // Rejected 2220 console.log('Autoplay rejected. Try starting the video manually!'); 2221 }); 2222 } else { 2223 // Other error 2224 console.log('Video format not supported!'); 2225 } 2226 }); 2227 } 2228 } else { 2229 if (typeof doneFn === FUNCTION) { 2230 doneFn.call(null); 2231 } 2232 } 2233 } 2234 }, 2235 2236 /********************************************************* 2237 * Full Screen API 2238 */ 2239 2240 // Saving the last full screen change date 2241 2242 fullscreenChanged = 0, 2243 2244 // Querying the last full screen change in MS from now 2245 2246 fullscreenChangedSince = function() { 2247 return new Date() - fullscreenChanged; 2248 }, 2249 2250 // Fill screen API 2251 2252 hasFullscreen = function() { 2253 // Has Fullscreen API? 2254 return document.fullscreenEnabled || 2255 document.mozFullScreenEnabled || 2256 document.webkitFullscreenEnabled || 2257 document.msFullscreenEnabled; 2258 }, 2259 2260 isFullscreen = function() { 2261 // Checking state 2262 return !!( document.fullscreenElement || 2263 document.mozFullScreenElement || 2264 document.webkitFullscreenElement || 2265 document.msFullscreenElement); 2266 }, 2267 2268 // Listening to externally triggered full screen exit 2269 2270 onFullscreenChange = function(fn) { 2271 2272 let handleFullscreenChange = function(e) { 2273 fn(isFullscreen()); 2274 }; 2275 2276 if (typeof fn === FUNCTION) { 2277 ["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","msfullscreenchange"].forEach(et => document.addEventListener(et, handleFullscreenChange, false)); 2278 } 2279 }, 2280 2281 // Requesting full screen 2282 2283 requestFullscreen = function(success, fail) { 2284 2285 if (hasFullscreen()) { 2286 if (isFullscreen()) { 2287 // Already in fullscreen 2288 if (typeof success === FUNCTION) { 2289 success.call(); 2290 } 2291 2292 } else { 2293 // Not yet 2294 let de = document.documentElement; 2295 2296 if (de.requestFullscreen) { 2297 // Standard: using promise 2298 de.requestFullscreen().then(success || (() => {})).catch(err => { 2299 if (typeof fail === FUNCTION) { 2300 fail.call(); 2301 } else { 2302 console.log('Full-screen error: ' + err.message + ' (' + err.name + ')'); 2303 } 2304 }); 2305 2306 } else if (de.mozRequestFullscreen) { 2307 // Older Mozilla <= 63 2308 de.mozRequestFullscreen(); 2309 success.call(); 2310 } else if (de.webkitRequestFullscreen) { 2311 // Safari does not return a promise 2312 de.webkitRequestFullscreen(); 2313 success.call(); 2314 } else if (de.msRequestFullscreen) { 2315 // IE11 2316 de.msRequestFullscreen(); 2317 success.call(); 2318 } 2319 fullscreenChanged = new Date(); 2320 } 2321 } 2322 }, 2323 2324 // Requesting exit full screen 2325 2326 exitFullscreen = function(success, fail) { 2327 2328 if (hasFullscreen()) { 2329 if (!isFullscreen()) { 2330 // Not in fullscreen 2331 if (typeof success === FUNCTION) { 2332 success.call(); 2333 } 2334 2335 } else { 2336 // In fullscreen -> Exit 2337 if (typeof success !== FUNCTION) { 2338 success = function() {}; 2339 } 2340 2341 if (typeof fail !== FUNCTION) { 2342 fail = function(err) { console.log('Full-screen error: ' + err.message + ' (' + err.name + ')'); }; 2343 } 2344 2345 if (document.exitFullscreen) { 2346 document.exitFullscreen().then(success).catch(fail); 2347 } else if (document.mozExitFullscreen) { 2348 document.mozExitFullscreen().then(success).catch(fail); 2349 } else if (document.webkitExitFullscreen) { 2350 // Safari fails with promises 2351 document.webkitExitFullscreen(); 2352 success.call(); 2353 } else if (document.msExitFullscreen) { 2354 document.msExitFullscreen().then(success).catch(fail); 2355 } 2356 fullscreenChanged = new Date(); 2357 } 2358 } 2359 }, 2360 2361 // Orientation change listener 2362 2363 orientationChanged = function(e, success) { 2364 if (e.type === 'change') { 2365 if (e.target.type.startsWith('landscape') && !isFullscreen()) { 2366 requestFullscreen(success); 2367 } 2368 } else if (e.type === 'orientationchange') { 2369 if (e.target.innerWidth >
2369 e.target.innerHeight && !isFullscreen()) { 2370 requestFullscreen(success); 2371 } 2372 } 2373 }, 2374 2375 goFullscreenOnLandscapeChange = function() { 2376 2377 if (document.fullscreenEnabled || 2378 document.mozFullScreenEnabled || 2379 document.webkitFullscreenEnabled || 2380 document.msFullscreenEnabled 2381 ) { 2382 if (typeof screen !== UNDEF && screen.hasOwnProperty('orientation')) { 2383 // Has Screen API 2384 screen.orientation.addEventListener('change', orientationChanged); 2385 } else { 2386 // deprecated orientationchange event 2387 window.addEventListener('orientationchange', orientationChanged); 2388 } 2389 } 2390 }, 2391 2392 /********************************************************* 2393 * Wake lock API 2394 */ 2395 2396 // Saving wakelock promise 2397 2398 wakeLock = null, 2399 2400 // Requesting wake lock 2401 2402 requestWakeLock = async (success, fail) => { 2403 2404 if ('wakeLock' in navigator && 'request' in navigator.wakeLock) { 2405 2406 wakeLock = null; 2407 2408 try { 2409 2410 wakeLock = await navigator.wakeLock.request('screen'); 2411 /* 2412 if (DEBUG) { 2413 console.log('Wakelock activated!'); 2414 } 2415 */ 2416 if (typeof success === FUNCTION) { 2417 success.call(); 2418 } 2419 2420 } catch (e) { 2421 /* 2422 if (DEBUG) { 2423 console.log('Wakelock cannot be activated!'); 2424 } 2425 */ 2426 if (typeof fail === FUNCTION) { 2427 fail.call(e); 2428 } 2429 } 2430 } 2431 }, 2432 2433 // Releasing wake lock 2434 2435 releaseWakeLock = (success, fail) => { 2436 2437 if (wakeLock) { 2438 2439 wakeLock.release().then(() => { 2440 2441 wakeLock = null; 2442 /* 2443 if (DEBUG) { 2444 console.log('Wakelock released!'); 2445 } 2446 */ 2447 if (typeof success === FUNCTION) { 2448 success.call(); 2449 } 2450 }); 2451 } 2452 }, 2453 2454 2455 /* 2456 * loadExternal: Trying to load data1.json of an external album 2457 */ 2458 2459 loadExternal = function(url, doneFn) { 2460 2461 if (url.endsWith(PAGE_NAME)) { 2462 url = url.slice(0, - PAGE_NAME.length); 2463 } 2464 2465 if (!url.endsWith('/')) { 2466 url += '/'; 2467 } 2468 2469 // Loading data1.json 2470 return $.getJSON(url + 'data1.json') 2471 .done(function(d) { 2472 if (typeof doneFn === FUNCTION) { 2473 doneFn.call(d); 2474 } 2475 }) 2476 .fail(function(jqxhr, status, error) { 2477 if (DEBUG) { 2478 console.log('Not an album, or cannot load album database for: "' + url + '".'); 2479 } 2480 }); 2481 }, 2482 2483 /********************************************************* 2484 * Loading external API's 2485 */ 2486 2487 // Loading Google Analytics 2488 2489 loadGoogleAnalytics = function(siteID, type, supportDoubleclick) { 2490 2491 if (LOCAL) { 2492 return; 2493 } 2494 var sid = xDecrypt(siteID); 2495 if (type === 'universal') { 2496 // analytics.js 2497 (function(i,s,o,g,r,a,m){ 2498 i['GoogleAnalyticsObject']=r; 2499 i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date(); 2500 a=s.createElement(o),m=s.getElementsByTagName(o)[0]; 2501 a.async=1; 2502 a.src=g; 2503 m.parentNode.insertBefore(a,m) 2504 })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 2505 ga('create', sid, 'auto'); 2506 if (supportDoubleclick) { 2507 ga('require','displayfeatures'); 2508 } 2509 ga('send', 'pageview'); 2510 } else { 2511 window.dataLayer = window.dataLayer || []; 2512 window.gtag = function() {dataLayer.push(arguments);} 2513 window.gtag('js', new Date()); 2514 if (DEBUG) { 2515 window.gtag('config', sid, { 'debug_mode': true }); 2516 console.log('gtag:', typeof gtag); 2517 console.log('dataLayer:', window.dataLayer); 2518 } else { 2519 window.gtag('config', sid); 2520 } 2521 //gtag('config',sid,{'send_page_view':true}); 2522 } 2523 }, 2524 2525 // Loading Google API 2526 2527 loadGoogleAPI = function(badgeId, boxType) { 2528 if (LOCAL) { 2529 return; 2530 } 2531 (function(i,s,o,g,r,a,m){ 2532 a=s.createElement(o); 2533 a.appendChild(s.createTextNode(r)); 2534 m=s.getElementsByTagName(o)[0]; 2535 a.async=1; 2536 a.src=g; 2537 m.parentNode.insertBefore(a,m); 2538 })(window,document,'script',('https:'==document.location.protocol?'https:':'http:')+'//apis.google.com/js/platform.js',"{parsetags:'explicit',lang:'${lang}'}"); 2539 2540 var la = 30, 2541 // Multiple try if ready 2542 launch = function() { 2543 if (typeof gapi === UNDEF) { 2544 if (la--) { 2545 setTimeout(launch, 200); 2546 } else if ('console' in window) { 2547 console.log('Google Plus API failed to load!'); 2548 } 2549 } else { 2550 setTimeout(function() { 2551 gapi[boxType].go(); 2552 }, 200); 2553 } 2554 }; 2555 2556 if (badgeId) { 2557 launch(); 2558 } 2559 }, 2560 2561 // Loading Facebook API 2562 2563 loadFacebookAPI = function(appId,locale) { 2564 if (LOCAL) { 2565 return; 2566 } 2567 window.fbAsyncInit = function(){ 2568 if (typeof jQuery !== UNDEF) { 2569 jQuery('.social-links').trigger('updateLinks'); 2570 } 2571 }; 2572 (function(d,s,id){ 2573 var js, 2574 fjs = d.getElementsByTagName(s)[0]; 2575 if (d.getElementById(id)) return; 2576 js = d.createElement(s); 2577 js.id = id; 2578 js.src = 'https://connect.facebook.net/'+(locale||'en_US')+'/sdk.js#xfbml=1&version=v3.0&appId='+xDecrypt(appId); 2579 fjs.parentNode.insertBefore(js, fjs); 2580 })(document, 'script', 'facebook-jssdk'); 2581 }, 2582 2583 // Loading Disqus API 2584 2585 loadDisqusAPI = function(appId) { 2586 if (LOCAL) { 2587 return; 2588 } 2589 (function(d,s) { 2590 s = d.createElement(s); 2591 s.async = 1; 2592 s.src='https://'+xDecrypt(appId)+'.disqus.com/embed.js'; 2593 s.setAttribute('data-timestamp', +new Date()); 2594 (d.head || d.body).appendChild(s); 2595 })(document,'script'); 2596 }, 2597 2598 // Loading Pinterest API 2599 2600 loadPinterestAPI = function() { 2601 if (LOCAL) { 2602 return; 2603 } 2604 (function(d,s) { 2605 s = d.createElement(s); 2606 s.async = 1; 2607 s.src = 'https://assets.pinterest.com/js/pinit.js'; 2608 (d.head || d.body).appendChild(s); 2609 })(document,'script'); 2610 }, 2611 2612 // Loading all APIs 2613 2614 loadAPIs = function(apis, allowed) { 2615 2616 if (apis && !isEmpty(apis)) { 2617 2618 for (var a in apis) { 2619 2620 if (typeof allowed === UNDEF || allowed[a]) { 2621 2622 switch (a) { 2623 2624 case 'googleAnalytics': 2625 loadGoogleAnalytics(apis[a][0],apis[a][1],apis[a][2]); 2626 break; 2627 2628 case 'googlePlus': 2629 loadGoogleAPI(apis[a][0],apis[a][1]); 2630 break; 2631 2632 case 'facebook': 2633 loadFacebookAPI(apis[a][0]); 2634 break; 2635 2636 case 'disqus': 2637 loadDisqusAPI(apis[a][0]); 2638 break; 2639 2640 case 'pinterest': 2641 loadPinterestAPI(); 2642 break; 2643 } 2644 } 2645 } 2646 } 2647 }, 2648 2649 // Asking permission first to load APIs 2650 2651 askTrackingConsent = function(settings, apis) { 2652
2653 if ((!apis || isEmpty(apis)) && !settings['cookiePolicy']) { 2654 // No API's and no Cookie Policy 2655 return; 2656 } 2657 2658 if (LOCAL) { 2659 // Don't ask for API's locally 2660 return; 2661 } 2662 2663 settings = $.extend({}, { 2664 stay: 15, 2665 buttonClass: 'button' 2666 }, settings); 2667 2668 apis = apis || {}; 2669 2670 var text = getTranslations({ // Translated texts 2671 close: "Close", 2672 cookiePolicyText: "This album is using cookies to remember your preferences. It does not track you, nor does it store personal data.", 2673 cookiePolicyAgree: "Got it", 2674 gdprComplianceText: "This site uses the following external services that may track your visits anonymously. Disabling tracking will stop their functionality.", 2675 allowAll: "Allow all", 2676 denyAll: "Deny all", 2677 allowSelected: "Allow selected", 2678 cookiePolicyLearnMore: "Learn more" 2679 }), 2680 consent = $.albumCookie('trackingConsent'), 2681 showApis = false, 2682 showCp = false, 2683 oncl = 'icon-checkmark', 2684 2685 // Toggle one button 2686 toggleApi = function(e) { 2687 var b = $(e.target), 2688 sel = b.hasClass(oncl); 2689 2690 b.toggleClass(oncl, !sel); 2691 2692 setButtons(); 2693 }, 2694 2695 // Set visibility of "Allow all", "Deny all" and "Allow selected" buttons 2696 setButtons = function() { 2697 var any = $('#trackingConsent span.' + oncl).length > 0; 2698 2699 $('#allow,#deny').toggle(!any); 2700 $('#allowsel').toggle(any); 2701 }, 2702 2703 // Saving the cookie, and launching the APIs 2704 saveCookie = function(e) { 2705 var allowed, 2706 btns = $('#trackingConsent span'); 2707 2708 // Based on the old setting 2709 allowed = $.extend({}, $.albumCookie('trackingConsent'), { cookie: 1 }); 2710 2711 if (this.id === 'allow') { 2712 btns.each(function() { 2713 $(this).addClass(oncl); 2714 allowed[$(this).attr('id')] = 1; 2715 }); 2716 } else if (this.id === 'deny') { 2717 btns.each(function() { 2718 $(this).removeClass(oncl); 2719 allowed[$(this).attr('id')] = 0; 2720 }); 2721 } else if (this.id === 'allowsel') { 2722 btns.each(function(i) { 2723 allowed[$(this).attr('id')] = $(this).hasClass(oncl); 2724 }); 2725 } 2726 2727 $.albumCookie('trackingConsent', allowed, 31622400); // lasts 1 year 2728 2729 removePopup(); 2730 2731 loadAPIs(apis, allowed); 2732 2733 return false; 2734 }, 2735 2736 // Check for missing API consents 2737 matchApis = function() { 2738 2739 for (var a in apis) { 2740 if (!consent.hasOwnProperty(a)) { 2741 return false; 2742 } 2743 } 2744 return true; 2745 }, 2746 2747 // Removing popup 2748 removePopup = function() { 2749 $('#cookiepolicy').on('transitionend', function() { 2750 $(this).remove(); 2751 }).css({ 2752 opacity: 0, 2753 transform: 'translate(-50%,120%)' 2754 }); 2755 }; 2756 2757 if (!consent) { 2758 // No consent asked before 2759 showApis = !isEmpty(apis); 2760 showCp = settings.hasOwnProperty('cookiePolicy') && settings.cookiePolicy; 2761 } else { 2762 // Consent found: checking for changes 2763 showApis = !matchApis(consent, apis); 2764 showCp = settings.hasOwnProperty('cookiePolicy') && settings.cookiePolicy && !consent.hasOwnProperty('cookie'); 2765 } 2766 2767 if (!showApis && !showCp) { 2768 // Already allowed: load immediately 2769 loadAPIs(apis, consent); 2770 2771 } else { 2772 // Ask consent 2773 2774 // Container 2775 var panel = $('<div>', { 2776 id: 'cookiepolicy' 2777 }).appendTo($('body')), 2778 texts = $('<div>', { 2779 'class': 'texts' 2780 }).appendTo(panel), 2781 btns = $('<div>', { 2782 'class': 'buttons' 2783 }).appendTo(panel), 2784 cpt; 2785 2786 // Close button 2787 panel.append($('<a>', { 2788 'class': 'close icon-close ' + settings.buttonClass, 2789 title: text.close 2790 }).on('click', removePopup)); 2791 2792 if (showCp) { 2793 // Showing cookie policy text 2794 cpt = $('<p>', { 2795 id: 'cookiePolicy', 2796 html: text.cookiePolicyText 2797 }).appendTo(texts); 2798 2799 if (!showApis) { 2800 // Only Cookie policy: add Agree button + readmore 2801 btns.append($('<a>', { 2802 id: 'agree', 2803 'class': 'icon-ok ' + settings.buttonClass, 2804 text: ' ' + text.cookiePolicyAgree 2805 }).on('click', saveCookie)); 2806 2807 } 2808 } 2809 2810 if (showApis) { 2811 // Show APIs 2812 cpt = $('<p>', { 2813 id: 'trackingConsent', 2814 html: text.gdprComplianceText 2815 }).appendTo(texts); 2816 2817 for (var a in apis) { 2818 cpt.append($('<span>', { 2819 id: a, 2820 'class': 'checkbox' + ((consent && consent[a])? (' ' + oncl) : ''), 2821 html: ' ' + a.capitalize() 2822 }) 2823 .on('click', toggleApi)); 2824 } 2825 2826 btns.append($('<a>', { 2827 id: 'allow', 2828 'class': 'icon-ok ' + settings.buttonClass, 2829 text: ' ' + text.allowAll 2830 }).on('click', saveCookie)); 2831 2832 btns.append($('<a>', { 2833 id: 'deny', 2834 'class': 'alert icon-close ' + settings.buttonClass, 2835 text: ' ' + text.denyAll 2836 }).on('click', saveCookie)); 2837 2838 btns.append($('<a>', { 2839 id: 'allowsel', 2840 'class': 'icon-ok ' + settings.buttonClass, 2841 text: ' ' + text.allowSelected 2842 }).on('click', saveCookie)); 2843 2844 setButtons(); 2845 } 2846 2847 if (settings.cookiePolicyUrl) { 2848 texts.append(' ').append($('<a>', { 2849 text: text.cookiePolicyLearnMore, 2850 target: '_blank', 2851 href: settings.cookiePolicyUrl 2852 })); 2853 } 2854 2855 panel.css({ 2856 opacity: 1, 2857 transform: 'translate(-50%,0)' 2858 });
2859 2860 setTimeout(removePopup, settings.stay * 1000); 2861 2862 } 2863 }; 2864 2865 2866 // Adding 'touch' : 'no-touch' classes 2867 addClass(document.getElementsByTagName('html')[0], (TOUCHENABLED? '' : 'no-') + 'touch'); 2868 // Adding 'hidpi' : 'no-hdpi' classes 2869 addClass(document.getElementsByTagName('html')[0], (HIDPI? '' : 'no-') + 'hidpi'); 2870 2871/* 2872 * Debugging functions 2873 */ 2874 2875// Waiting for jQuery loaded 2876 2877(function($, undefined) { 2878 'use strict'; 2879 // log: logging function 2880 2881 var _logel, 2882 _logover = false, 2883 _lastlog, 2884 _lastcnt = 1; 2885 2886 log = function(c) { 2887 2888 var resolveObject = function(c) { 2889 if (Array.isArray(c)) { 2890 var s = ''; 2891 for (var i = 0; i < c.length; i++) { 2892 s += resolveObject(c[i]) + ', '; 2893 } 2894 return '[ ' + s.substring(0, s.length-2) + ' ]'; 2895 } else if (typeof c === 'object') { 2896 var s = ''; 2897 for (var i in c) { 2898 s += i + ': ' + resolveObject(c[i]) + ',<br>'; 2899 } 2900 return '{ ' + s + ' }'; 2901 } else if (isNaN(c)) { 2902 return c; 2903 } else { 2904 return (parseInt(c) === c)? c : c.toFixed(4); 2905 } 2906 }; 2907 2908 if (!DEBUG || _logover) { 2909 return; 2910 } 2911 if (!_logel) { 2912 _logel = $('<div id="log" style="position:fixed;left:0;top:0;width:200px;bottom:0;overflow:auto;padding:10px;background-color:rgba(0,0,0,0.5);color:#fff;font-size:0.75em;z-index:999999"></div>').hover(function(){ 2913 _logover = true; 2914 }, function() { 2915 _logover = false; 2916 }).appendTo('body'); 2917 } 2918 if (c === _lastlog) { 2919 _logel.children().first().empty().html(_lastlog + ' <sup>(' + (++_lastcnt) + ')</sup>'); 2920 } else { 2921 $('<div style="height:3em;overflow:auto;">' + resolveObject(c) + '</div>').prependTo(_logel); 2922 _lastlog = c; 2923 _lastcnt = 1; 2924 } 2925 }; 2926 2927 // logEvents :: debugging events 2928 2929 $.fn.logEvents = function( e ) { 2930 2931 if (!DEBUG) { 2932 return; 2933 } 2934 2935 var events = e || 'mousedown mouseup mouseover mouseout mousewheel wheel dragstart click blur focus load unload reset submit change abort cut copy paste selection drag drop orientationchange touchstart touchmove touchend touchcancel pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp gesturestart gesturechange gestureend'; 2936 2937 return this.each(function() { 2938 $(this).on(events, function(e) { 2939 if (typeof e === UNDEF) { 2940 log('Undefined event'); 2941 } else if (e.target) { 2942 if (e.target.id !== 'log') { 2943 log(e.type + ' <span style="padding:0 4px;font-size:0.75em;background-color:#000;border-radius:4px;"><b>' + (e.target.nodeName? e.target.nodeName.toLowerCase() : '???') + '</b>' + (e.target.id? (':'+e.target.id) : '') + '</span>' + 2944 (e.relatedTarget? (' <span style="padding:0 4px;font-size:0.6em;background-color:#800;border-radius:4px;"><b>' + e.relatedTarget.nodeName.toLowerCase() + '</b>' + (e.relatedTarget.id? (':'+e.relatedTarget.id) : '') + '</span>') : '')); 2945 } 2946 } else { 2947 log('No event target!'); 2948 } 2949 return true; 2950 }); 2951 }); 2952 }; 2953 2954 // logCss :: tracks css values until the element is live 2955 2956 $.fn.logCss = function( p, dur, step ) { 2957 2958 if (!DEBUG) { 2959 return; 2960 } 2961 2962 step = step || 20; 2963 dur = dur || 2000; 2964 var t0 = new Date(); 2965 2966 return this.each(function() { 2967 var el = $(this); 2968 var show = function( nm ) { 2969 var t = new Date() - t0; 2970 log(t + ' :: ' + nm + ' = ' + el.css(nm)); 2971 if (t > dur) { 2972 clearInterval(iv); 2973 } 2974 }; 2975 2976 var iv = setInterval(function() { 2977 if ( Array.isArray(p) ) { 2978 for (var i = 0; i < p.length; i++) { 2979 show(p[i]); 2980 } 2981 } 2982 else { 2983 show(p); 2984 } 2985 }, step); 2986 }); 2987 }, 2988 2989 // Focus an element 2990 2991 $.fn.setFocus = function(options) { 2992 2993 if (this.length) { 2994 if (typeof options !== UNDEF) { 2995 this[0].focus(options); 2996 } else { 2997 this[0].focus(); 2998 } 2999 } 3000 }; 3001 3002 // Deserialize a serialized string 3003 3004 if ($.fn.deserialize === undefined) { 3005 $.fn.deserialize = function (s) { 3006 3007 if (!s) { 3008 return this; 3009 } 3010 3011 var self = $(this), 3012 values = s.replace(/\+/g, '%20').split('&'); 3013 3014 $.each(values, function(i, pair) { 3015 var val = pair.split('='), 3016 name = decodeURIComponent(val[0]), 3017 value = (val[1] !== null)? decodeURIComponent(val[1]) : '', 3018 field = self.find('[name=' + name + ']'); 3019 3020 if (!field.length) { 3021 return true; 3022 } 3023 3024 if (field[0].type === 'radio' || field[0].type === 'checkbox') { 3025 3026 var fieldVal = field.filter('[value="' + value + '"]'), 3027 found = fieldVal.length; 3028 3029 if (!found && value === 'on') { 3030 field.eq(0).prop('checked', true); 3031 } else { 3032 fieldVal.prop('checked', found); 3033 } 3034 3035 } else { 3036 // input, textarea 3037 field.val(value); 3038 } 3039 }); 3040 3041 return this; 3042 }; 3043 } 3044 3045 // Making $.when working with arrays 3046 3047 if ($.when.all === undefined) { 3048 $.when.all = function(deferreds) { 3049 var deferred = new $.Deferred(); 3050 $.when.apply($, deferreds).then( 3051 function() { 3052 deferred.resolve(Array.prototype.slice.call(arguments)); 3053 }, 3054 function() { 3055 deferred.reject(Array.prototype.slice.call(arguments)); 3056 } 3057 ); 3058 return deferred; 3059 }; 3060 } 3061 3062 // Get rotation angle in degrees 3063 3064 $.fn.getRotate = function() { 3065 3066 if (this && this.length) { 3067 var st = window.getComputedStyle(this[0], null), 3068 mx = st.getPropertyValue('transform') || st.getPropertyValue('-ms-transform') || st.getPropertyValue('-webkit-transform'), 3069 a; 3070 3071 if (mx && (a = mx.match(/matrix3d\(([^\)]+)\)/)) && a && a.length > 2) { 3072 a = a[1].split(/\s*,/); 3073 return Math.round(Math.atan2(parseFloat(a[1]), parseFloat(a[0])) * (180 / Math.PI)); 3074 } 3075 } 3076 return 0; 3077 }; 3078 3079 // Get 3D or 2D matrix as Array of float numbers 3080 3081 $.fn.getMatrix = function() { 3082 3083 if (this && this.length) { 3084 var st = window.getComputedStyle(this[0], null), 3085 mx = st.getPropertyValue('transform') || st.getPropertyValue('-ms-transform') || st.getPropertyValue('-webkit-transform'), 3086 v, 3087 m = []; 3088 3089 if (mx && (v = mx.match(/matrix(3d)?\(([^\)]+)\)/)) && v && v.length > 2) { 3090 v = v[2].split(/,\s*/); 3091 v.forEach(function(a) { m.push(parseFloat(a)); }); 3092 return m; 3093 } 3094 } 3095 return [ 1, 0, 0, 1, 0, 0 ]; 3096 }; 3097 3098 // Get XY translate coords 3099 3100 $.fn.getTranslate = function() { 3101 3102 if (this && this.length) { 3103 var mx = this.getMatrix(); 3104 return [ mx[4], mx[5] ]; 3105 } 3106 return [ 0, 0 ]; 3107 }; 3108 3109 // Get scale 3110 3111 $.fn.getScale = function() { 3112 3113 if (this && this.length) { 3114 var mx = this.getMatrix(); 3115 return [ mx[0], mx[3] ]; 3116 } 3117 return [ 1, 1 ]; 3118 }; 3119 3120 // Reverse order 3121 3122 $.fn.backwards = [].reverse; 3123 3124 // Waiting for all images 3125
3126 $.fn.waitAllImg = function(doneFn, successFn, failFn) { 3127 3128 if (!this || !this.length) { 3129 doneFn.call(this); 3130 return; 3131 } 3132 3133 var self = $(this), 3134 deferreds = [], 3135 3136 loadImage = function(image) { 3137 var deferred = new $.Deferred(), 3138 el = new Image(); 3139 3140 el.onload = function() { 3141 deferred.resolve(image); 3142 }; 3143 3144 el.onerror = function() { 3145 deferred.reject(new Error('Image not found: ' + image.src)); 3146 }; 3147 3148 el.src = image.src; 3149 3150 return deferred; 3151 }, 3152 3153 loadVideo = function(video) { 3154 var deferred = new $.Deferred(), 3155 el = document.createElement("VIDEO"); 3156 3157 el.addEventListener('loadedmetadata', function() { 3158 deferred.resolve(video); 3159 }); 3160 3161 el.addEventListener('error', function() { 3162 deferred.reject(new Error('Video not found: ' + video.src)); 3163 }); 3164 3165 el.src = video.src; 3166 3167 return deferred; 3168 }, 3169 3170 loadImages = function(items) { 3171 3172 items.filter('img[src]').not('[src=""]').each(function() { 3173 deferreds.push(loadImage(this)); 3174 }); 3175 3176 items.filter('video[src]').not('[src=""]').each(function() { 3177 deferreds.push(loadVideo(this)); 3178 }); 3179 3180 return $.when.all(deferreds); 3181 }; 3182 3183 loadImages(self).then( 3184 3185 function(self) { 3186 if (typeof successFn === FUNCTION && successFn !== doneFn) { 3187 successFn.call(self); 3188 } 3189 }, 3190 3191 function(err) { 3192 if (typeof failFn === FUNCTION) { 3193 failFn.call(err); 3194 } 3195 } 3196 3197 ).then(function() { 3198 if (typeof doneFn === FUNCTION) { 3199 doneFn.call(self); 3200 } 3201 }); 3202 3203 return this; 3204 }; 3205 3206 // Asking permission before following a link 3207 3208 $.fn.askPermission = function(data) { 3209 /* 3210 text.restrictedLinkTitle 3211 text.restrictedLinkQuestion 3212 text.restrictedLinkYes 3213 text.restrictedLinkNo 3214 */ 3215 var text = getTranslations({ // Translated texts 3216 restrictedLinkTitle: 'Restricted material', 3217 restrictedLinkQuestion: 'Please verify your age to proceed!', 3218 restrictedLinkYes: 'I´m over 18', 3219 restrictedLinkNo: 'I´m below 18' 3220 }), 3221 3222 ns = '_lap_', 3223 3224 getOptions = function(el) { 3225 var opt = {}, 3226 o = el.data('ask-permission'); 3227 3228 if (o) { 3229 o = o.split('::'); 3230 if (o[0] !== null) { 3231 opt.restrictedLinkTitle = o[0]; 3232 if (o[1] !== null) { 3233 opt.restrictedLinkQuestion = o[1]; 3234 if (o[2] !== null) { 3235 opt.restrictedLinkYes = o[2]; 3236 if (o[3] !== null) { 3237 opt.restrictedLinkNo = o[3]; 3238 } 3239 } 3240 } 3241 } 3242 } 3243 3244 return opt; 3245 }; 3246 3247 return $(this).filter('[href]').each(function() { 3248 3249 $(this).data('href', $(this).attr('href')); 3250 $(this).attr('href', null); 3251 3252 $(this).on('click.' + ns, function(e) { 3253 var opt = $.extend({}, text, getOptions($(this))); 3254 3255 e.preventDefault(); 3256 opt.link = $(this).data('href'); 3257 3258 $('body').modal($('<div class="text-center">' + opt.restrictedLinkQuestion + '</div>'), 3259 [ 3260 { 3261 t: opt.restrictedLinkYes, 3262 c: 'icon-checkmark', 3263 h: function() { 3264 window.location.href = opt.link; 3265 } 3266 }, 3267 { 3268 t: opt.restrictedLinkNo, 3269 c: 'icon-close alert', 3270 h: function() { 3271 // do nothing: close dialog 3272 return true; 3273 } 3274 } 3275 ], 3276 { 3277 icon: 'warning', 3278 'class': 'small warning', 3279 title: opt.restrictedLinkTitle 3280 } 3281 ); 3282 3283 return false; 3284 }); 3285 }); 3286 }; 3287 3288})(jQuery); 3289; 3290/* 3291 * The Album model 3292 * 3293 * (c) Laszlo Molnar, 2015, 2017, 2020, 2021 3294 * Licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3295 * <http://creativecommons.org/licenses/by-nc-sa/3.0/> 3296 * 3297 * requires: jQuery, laza.util 3298 */ 3299 3300 3301/* 3302 * Constants 3303 */ 3304 3305const J = { 3306 // jAlbum variables 3307 ALBUM: 'album', // jAlbum's album data 3308 FOLDERS: 'folders', // Folders array 3309 NAME: 'name', // FileName of the item 3310 PATH: 'path', // Path to file 3311 THUMB: 'thumb', // Thumbnail properties object 3312 IMAGE: 'image', // Image properties object 3313 WIDTH: 'width', // Width 3314 HEIGHT: 'height', // Height 3315 RENDITIONS: 'renditions', // Variant renditions 3316 ORIGINAL: 'original', // Original object 3317 OBJECTS: 'objects', // Objects array (not folders) 3318 FILEDATE: 'fileDate', // File modified date 3319 COMMENT: 'comment', // Comment 3320 TITLE: 'title', // Title 3321 KEYWORDS: 'keywords', // Keywords 3322 COUNTERS: 'counters', // Counters object 3323 DEEPCOUNTERS: 'deepCounters', // Deepcounters object 3324 FILESIZE: 'fileSize', // File size (int) 3325 CATEGORY: 'category', // Category 3326 RATING: 'rating', // Rating 3327 PRICE: 'price', // Price 3328 CAMERA: 'camera', // Camera data object 3329 VIDEO: 'video', // Video data object 3330 DURATION: 'duration', // Video duration 3331 FPS: 'fps', // Video frame per sec. 3332 HIDDEN: 'hidden', // Hidden 3333 VIEW: 'view', // View type in case the folder is a view 3334 INTERNALLINK: 'internalLink', // Internally linked object 3335 // extra vars 3336 LEVEL: 'level', // Folder depth level 3337 PATHREF: 'pathRef', // Path from root 3338 PARENTREF: 'parentRef', // Path to parent from root 3339 RELPATH: 'relPath', // Relative path from currentFolder to this folder 3340 FOLDERCAPTION: 'folderCaption', // Folder caption derived from template 3341 IMAGECAPTION: 'imageCaption', // Image caption derived from template 3342 THUMBCAPTION: 'thumbCaption', // Thumbnail caption derived from template 3343 PHOTODATA: 'photodata', // Formatted photo data 3344 LOCATION: 'location', // GPS location 3345 HIDELOCATION: 'hideLocation', // Hide GPS location? 3346 DISABLEFEEDBACK: 'disableFeedback', // Hide from Feedback? 3347 REGIONS: 'regions', // Regions (face tags) 3348 SHOP: 'shop', // Global or individual shop options 3349 EXTERNAL: 'external', // External (link or content)
3350 PROJECTIONTYPE: 'projectionType', // 3D image projection type (equirectangular) 3351 GPANO: 'gpano', // Globe panorama (360°) 3352 ORIGINALFILE: 'originalFile', // Path to the original file in case of GIF's 3353 ORIGINALPAGENAME: 'originalPageName', // The original name of a custom page template (before renamed) 3354 EMBEDDED: 'embedded', // Custom page is embedded? 3355 CUSTOMPAGECONTENT: 'customPageContent', // Custom page content (used when pages displayed in the lightbox) 3356 SLIDESHOWINTERVAL: 'slideshowInterval', // Custom slideshow delay 3357 TRANSITIONSPEED: 'transitionSpeed', // Custom transotoin speed 3358 DATES: 'dates', // Dates object 3359 ADDED: 'added', // Added date 3360 DATETAKEN: 'dateTaken', // Date taken 3361 FILEMODIFIED: 'fileModified', // File modified 3362 DATERANGE: 'dateRange', // Date range (folders) 3363 MOSTPHOTOS: 'mostphotos', // Mostphotos ID 3364 HIDEFOTOMOTO: 'hideFotomoto', // Hide from Fotomoto 3365 FOTOMOTOCOLLECTION: 'fotomotoCollection', // Fotomoto collection type 3366 AUDIOCLIP: 'audioClip', // Attached audio clip (mp3) 3367 PANORAMA: 'panorama', // To be treated as panorama? 3368 FILTERS: 'filters', // Filters array (global or folders) 3369 SORT: 'sort', // Sort array (global or folders) 3370 VISITORRATING: 'visitorRating', // Ratings from jAlbum 3371 OBJ: 'obj', // Store item as data attr of an element: el.data(J.OBJ) 3372 LOADCOUNTER: 'loadcounter', // Load counter array by category 3373 TOTAL: 'total', // Total items loaded 3374 FOLDERINDEX: 'folderindex', // Numbering folders: 0 ... N 3375 READY: 'ready', // Data has been loaded 3376 ONREADY: 'onReady', // Cached functions for data loaded event 3377 DEEP: 'deep', // Using tree or deep-data structure? 3378 SIZE: 'size', // External content embed size 3379 CONT: 'cont', // External content 3380 TYPE: 'type' // E.g. for audioClip.type 3381 }, 3382 3383 JCAMERAFIELDS = [ 3384 'aperture', 3385 'exposureTime', 3386 'originalDate', 3387 'cameraModel', 3388 'location', 3389 'focusDistance', 3390 'focalLength35mm', 3391 'cameraMake', 3392 'resolution', 3393 'isoEquivalent', 3394 'flash', 3395 'focalLength' 3396 ]; 3397 3398/* 3399 * Album object :: use 3400 * myAlbum = new Album({settings}); 3401 * or 3402 * myAlbum = new Album(); 3403 * myAlbum.init({settings}); 3404 */ 3405 3406var Album = function($, options) { 3407 3408 let instance = null, 3409 3410 settings = { 3411 // Name of the tree file 3412 treeFile: 'tree.json', 3413 // Name of the folder data 3414 dataFile: 'data1.json', 3415 // Name of the master file 3416 deepDataFile: 'deep-data.json', 3417 // index file name 3418 indexName: 'index.html',
3419 //Folder image file name 3420 folderImageFile: 'folderimage.jpg', 3421 // Folder image dimensions 3422 folderImageDims: [ 1200, 800 ], 3423 // Folder thumbnail file name 3424 folderThumbFile: 'folderthumb.jpg', 3425 // Folder thumbnail dimanesions 3426 folderThumbDims: [ 1024, 768 ], 3427 // Thumbnail dimensions 3428 thumbDims: [ 240, 180 ], 3429 // Name of the thumbs folder 3430 thumbsDir: 'thumbs', 3431 // Name of the slides folder 3432 slidesDir: 'slides', 3433 // Name of the hires folder 3434 hiresDir: 'hi-res', 3435 // High DPI thumbnails 3436 hiDpiThumbs: false, 3437 // High DPI images 3438 hiDpiImages: false, 3439 // Use originals? 3440 useOriginals: false, 3441 // Do thumbs fill the frame or fit 3442 thumbsFillFrame: true, 3443 // Default poster images 3444 audioPoster: 'audio.poster.png', 3445 defaultAudioPosterSize: [ 628, 360 ], 3446 videoPoster: 'video.poster.png', 3447 defaultVideoPosterSize: [ 628, 360 ], 3448 // Path to root from current folder (eg. "../../") 3449 rootPath: '', 3450 // Relative path to the current folder (eg. "folder/subfolder") 3451 relPath: '', 3452 // Loading the whole data tree 3453 loadDeep: false, 3454 // Lazy load :: loads folder data only when requested or at the time of initialization 3455 lazy: true, 3456 // Show pages in the Lightbox? 3457 showPagesInLightbox: false, 3458 // Fatal error handling 3459 fatalError: (txt, attr) => log('Error: ' + ((typeof attr === UNDEF)? translate(txt) : translate(txt).replace(/\{0\}/g, attr))), 3460 // Possible object types 3461 possibleTypes: [ 'folder', 'webPage', 'webLocation', 'image', 'video', 'audio', 'other' ], 3462 locale: 'en-US' 3463 }, 3464 3465 // Texts translated 3466 text = getTranslations({ 3467 and: "and", 3468 from: "From {0}" 3469 }), 3470 // Creating RegExp with localized AND 3471 andRegExp = new RegExp(`\\s+(?:&|${text.and})\\s+`, 'gu'), 3472 // Global variables 3473 // URL of to top level album page (null => we're in the album's subfolder, using the relative "rootPath") 3474 albumPath = null, 3475 // Absolute URL of the top level page 3476 absolutePath, 3477 // Cache buster 3478 cacheBuster = '', 3479 // The container for the entire tree 3480 tree, 3481 // Collection of album paths from root in order to store only the references 3482 paths = [], 3483 // Collection of relative paths in order to store only the references 3484 relPaths = [], 3485 // Path to current folder 3486 currentFolder, 3487 // Collection the JSON promises 3488 defer = [], 3489 // Album ready state: tree.json and data1.json is loaded 3490 ready = false, 3491 // Deep ready: all the data structure is ready 3492 deepReady = false, 3493 // Collecting deep ready callbacks to execute on deep ready event 3494 deepReadyFunctions = [], 3495 // Is deep ready? 3496 isDeep = () => (tree && tree.hasOwnProperty(J.DEEP) && tree[J.DEEP]), 3497 3498 /*********************************************** 3499 * Debug 3500 */ 3501 3502 // Logging 3503 scriptName = 'jalbum-album.js', 3504 3505 log = txt => { 3506 3507 if (console && typeof txt !== UNDEF) { 3508 3509 if (txt.match(/^Error\:/i)) { 3510 console.error(scriptName + ' ' + txt); 3511 } 3512 3513 if (typeof DEBUG !== UNDEF && DEBUG) { 3514 if (txt.match(/^Warning\:/i)) { 3515 console.warn(scriptName + ' ' + txt); 3516 } else if (txt.match(/^Info\:/i)) { 3517 console.info(scriptName + ' ' + txt); 3518 } else { 3519 console.log(scriptName + ' ' + txt); 3520 } 3521 } 3522 } 3523 }, 3524 3525 // Utility functions 3526 getFilenameFromURL = n => decodeURIComponent(n.slice(n.lastIndexOf('/') + 1)), 3527 3528 // File name without extension 3529 getBasename = o => { 3530 var m = getItemName(o).match(/^(.+)\./); 3531 return m? m[1] : ''; 3532 }, 3533 3534 // File extension 3535 getExtension = o => { 3536 var m = getItemName(o).match(/\.(\w+)$/); 3537 return m? m[1] : ''; 3538 }, 3539 3540 // Can the browser display this image type? 3541 isBrowserFriendly = n => { 3542 var m = n.match(/\.\w+$/); 3543 return m? ('.jpg.jpeg.png.webp.webm.gif'.indexOf(m[0].toLowerCase()) !== -1) : false; 3544 }, 3545 3546 /*********************************************** 3547 * Type check 3548 */ 3549 3550 // Image? 3551 isImage = o =>
3551 o.hasOwnProperty(J.CATEGORY) && o[J.CATEGORY] === 'image', 3552 3553 // Audio? 3554 isAudio = o => o.hasOwnProperty(J.CATEGORY) && o[J.CATEGORY] === 'audio', 3555 3556 // Video? 3557 isVideo = o => o.hasOwnProperty(J.CATEGORY) && o[J.CATEGORY] === 'video', 3558 3559 // Page? 3560 isPage = o => o.hasOwnProperty(J.CATEGORY) && o[J.CATEGORY] === 'webPage', 3561 3562 // Folder? 3563 isFolder = o => o.hasOwnProperty(J.FOLDERINDEX) || o.hasOwnProperty(J.LEVEL), 3564 3565 // IsView 3566 isView = o => o.hasOwnProperty(J.VIEW), 3567 3568 // Lightboxable custom pages 3569 lightboxablePage = [ 'about', 'contact', 'contents', 'sitemap' ], 3570 3571 // Ordinary object? 3572 isLightboxable = o => (o.hasOwnProperty(J.CATEGORY) && 3573 ( o[J.CATEGORY] === 'image' || 3574 o[J.CATEGORY] === 'video' || 3575 o[J.CATEGORY] === 'audio' || 3576 o[J.CATEGORY] === 'other' || 3577 settings.showPagesInLightbox && 3578 o[J.CATEGORY] === 'webPage' && 3579 (!o.hasOwnProperty(J.ORIGINALPAGENAME) || lightboxablePage.includes(o[J.ORIGINALPAGENAME])) 3580 ) 3581 ), 3582 3583 // Is this the current folder? 3584 isCurrentFolder = o => o === currentFolder, 3585 3586 /*********************************************** 3587 * Access 3588 */ 3589 3590 // Returns path separated with separator 3591 /* '': current, 3592 '/': album root, 3593 'folderName': subfolder, 3594 'name1/name2': deep folder 3595 */ 3596 3597 makePath = function() { 3598 3599 if (arguments.length) { 3600 let p = []; 3601 3602 for (let i = 0, a; i < arguments.length; i++) { 3603 a = arguments[i]; 3604 3605 if (!a.length) { 3606 // Can be empty 3607 continue; 3608 } 3609 3610 if (a === '/') { 3611 // Return to root 3612 p = []; 3613 } else { 3614 // Removing trailing slashes if any 3615 if (a[0] === '/') { 3616 a = a.slice(1); 3617 } 3618 if (a.slice(-1) === '/') { 3619 a = a.slice(0, -1); 3620 } 3621 3622 if (a.length) { 3623 // Adding segment 3624 p.push(a); 3625 } 3626 } 3627 } 3628 3629 return p.join('/'); 3630 } 3631 3632 return ''; 3633 }, 3634 3635 // Get absolute path to folder from relative folder path 3636 3637 getAbsoluteFolderPath = path => { 3638 3639 if (typeof path === UNDEF) { 3640 return window.location.href.substring(0, window.location.href.lastIndexOf('/')); 3641 } else if (path.match(/^https?\:\/\//i)) { 3642 // Absolute 3643 return path; 3644 } else if (path[0] === '/') { 3645 // Site root relative 3646 return window.location.origin + path; 3647 } else { 3648 let url = window.location.href; 3649 // Find last '/' 3650 url = url.substring(0, url.lastIndexOf('/')); 3651 // Go back until ../ 3652 if (path.endsWith('..')) { 3653 // Ensure to avoid ".." ending 3654 path += '/'; 3655 } 3656 while (path.startsWith('../')) { 3657 url = url.substring(0, url.lastIndexOf('/', url.length - 2)); 3658 path = path.slice(3); 3659 } 3660 return url + (path? '/' + path : ''); // ??? 3661 } 3662 }, 3663 3664 // Getting path reference either by searching or adding as new 3665 // 0 => top level folder 3666 3667 getPathRef = p => { 3668 3669 if (typeof p === UNDEF || !p) { 3670 // Top level folder 3671 return 0; 3672 } 3673 3674 if (p.slice(-1) !== '/') { 3675 // Sanitizing paths: adding extra slash at the end 3676 p += '/'; 3677 } 3678 3679 let idx = paths.indexOf(p); 3680 3681 if (idx >= 0) { 3682 // Found => return 3683 return idx + 1; 3684 } 3685 3686 // Add new 3687 return paths.push(p); 3688 }, 3689 3690 // Reverse function 3691 3692 toPath = o => (o && o.hasOwnProperty(J.PATHREF) && o[J.PATHREF])? paths[o[J.PATHREF] - 1] : '', 3693 3694 // Getting path reference either by searching or adding as new 3695 // 0 => current folder 3696 3697 getRelPathRef = p => { 3698 3699 if (typeof p === UNDEF || !p) { 3700 // Current folder 3701 return 0; 3702 } 3703 3704 if (p.slice(-1) !== '/') { 3705 // Sanitizing paths: adding extra slash at the end 3706 p += '/'; 3707 } 3708 3709 let idx = relPaths.indexOf(p); 3710 3711 if (idx >= 0) { 3712 // Found => return 3713 return idx + 1; 3714 } 3715 3716 // Add new 3717 return relPaths.push(p); 3718 }, 3719 3720 // Reverse function 3721 3722 toRelPath = o => (o && o.hasOwnProperty(J.RELPATH) && o[J.RELPATH])? relPaths[o[J.RELPATH] - 1] : '', 3723 3724 // Fixing path containing ../ part(s) 3725 3726 fixUrl = p => { 3727 let i, 3728 j, 3729 t = p + ''; 3730 3731 while ((i = t.indexOf('../')) > 0) { 3732 if (i === 1 || (j = t.lastIndexOf('/', i - 2)) === -1) { 3733 return t.substring(i + 3); 3734 } 3735 t = t.substring(0, j) + t.substring(i + 2); 3736 } 3737 3738 return t; 3739 }, 3740 3741 // Get absolute URL to album's root folder 3742 3743 getAlbumPath = () => { 3744 3745 if (albumPath !== null) { 3746 return albumPath; 3747 } 3748 3749 let p = window.location.pathname, 3750 l = currentFolder[J.LEVEL]; 3751 3752 do { 3753 p = p.substring(0, p.lastIndexOf('/')); 3754 l = l - 1; 3755 } while (l >= 0); 3756 3757 return p; 3758 }, 3759 3760 // Get album root folder 3761 3762 getAlbumRootPath = () => absolutePath, 3763 3764 // Gets the relative path to element for linked objects 3765 3766 getLinkTo = o => { 3767 let p, 3768 d; 3769 3770 if (o.hasOwnProperty(J.THUMB)) { 3771 p = o[J.THUMB][J.PATH]; 3772 d = settings.thumbsDir + '/'; 3773 } else if (o.hasOwnProperty(J.IMAGE)) { 3774 p = o[J.IMAGE][J.PATH]; 3775 d = settings.slidesDir + '/'; 3776 } else { 3777 return ''; 3778 } 3779 3780 if (p.startsWith(d)) { 3781 return ''; 3782 } 3783 3784 return p.slice(0, p.indexOf('/' + d) + 1); 3785 }, 3786 3787 // Getting folder path for an object (absolute or relative) 3788 3789 getPath = o => { 3790 3791 if (typeof o !== UNDEF) { 3792 return (albumPath !== null)? makePath(albumPath, toPath(o)) : toRelPath(o); 3793 } 3794 // Top level or external reference to top level 3795 return albumPath || ''; 3796 }, 3797 3798 // Getting path from album root to the item's folder 3799 3800 getFolderPath = o => (typeof o !== UNDEF)? toPath(o) : '', 3801 3802 // Getting absolute path for an object 3803 3804 getAbsolutePath = o => ( 3805 o.hasOwnProperty(J.LEVEL)? 3806 // Folder 3807 makePath(albumPath || absolutePath, getFolderPath(o)) 3808 : 3809 // Object 3810 makePath(albumPath || absolutePath, getFolderPath(o), settings.indexName + '#img=' + o[J.PATH]) 3811 ), 3812 3813 // Gets the path to item: absolute or relative 3814
3815 getItemPath = o => { 3816 if (typeof o !== UNDEF) { 3817 let p = getPath(o), 3818 c = o[J.CATEGORY] || 'folder'; 3819 3820 if (c === 'folder') { 3821 return p; 3822 } else if (c === 'video') { 3823 return makePath(p, o[J.VIDEO][J.PATH]); 3824 } else if (c === 'audio' || c === 'other' || o.hasOwnProperty(J.ORIGINAL)) { 3825 return makePath(p, o[J.ORIGINAL][J.PATH]); 3826 } else if (c === 'image') { 3827 return makePath(p, o[J.IMAGE][J.PATH]); 3828 } else if (c === 'webPage') { 3829 return makePath(p, o[J.PATH]); 3830 } else { 3831 // webLocation: absolute 3832 return o[J.PATH]; 3833 } 3834 } 3835 3836 return null; 3837 }, 3838 3839 // Gets the audioclip path 3840 3841 getAudioClipPath = o => 3842 (typeof o !== UNDEF && o.hasOwnProperty(J.AUDIOCLIP))? 3843 makePath(getPath(o), o[J.AUDIOCLIP][J.PATH]) 3844 : 3845 null, 3846 3847 // Getting path to object from album root 3848 3849 getObjectPath = o => 3850 (typeof o !== UNDEF)? 3851 (toPath(o) + ((o[J.CATEGORY] !== 'folder')? o[J.PATH] : '')) 3852 : 3853 null, 3854 3855 // Tries to figure out the items dimensions 3856 3857 getDimensions = o => { 3858 3859 if (typeof o === UNDEF || o[J.CATEGORY] === 'webPage' || o[J.CATEGORY] === 'other' && getExtension(o).toLowerCase() === 'pdf') { 3860 // No (valid) object or has no dimensions 3861 return null; 3862 } else if (o.hasOwnProperty(J.EXTERNAL)) { 3863 // External content 3864 let s = o[J.EXTERNAL][J.SIZE]; 3865 3866 if (s) { 3867 // Has manually specified size 3868 s = s.split('x'); 3869 return [ s[0], s[1] || Math.round(s[0] *.75)]; 3870 3871 } else { 3872 s = guessDimensions(o[J.EXTERNAL][J.CONT]); 3873 if (s) { 3874 // Has explicitly given size in the HTML code 3875 return s; 3876 } else { 3877 // No info 3878 if (o[J.EXTERNAL][J.CONT].includes('vimeo.com') || 3879 o[J.EXTERNAL][J.CONT].includes('youtube.com') || 3880 o[J.EXTERNAL][J.CONT].includes('youtu.be')) { 3881 // In case of YouTube or Vimeo, set 720p as default 3882 if ((s = o[J.EXTERNAL][J.CONT].match(/.*style="padding(-bottom)?:\s?([\d\.]+)%/)) && s && (s.length > 1)) { 3883 // Responsive embedding 3884 return [ 1280, Math.round((w * parseFloat(s[2])) / 100) ]; 3885 } else { 3886 return [ 1280, 720 ]; 3887 } 3888 } else { 3889 return [ o[J.IMAGE][J.WIDTH], o[J.IMAGE][J.HEIGHT] ]; 3890 } 3891 } 3892 } 3893 } else if (o[J.CATEGORY] === 'audio' && o[J.IMAGE][J.PATH].endsWith('res/audio.png')) { 3894 // Audio with poster image 3895 return [ settings.defaultAudioPosterSize[0], settings.defaultAudioPosterSize[1] ]; 3896 3897 } else if (o[J.CATEGORY] === 'video') { 3898 // Video 3899 return [ o[J.VIDEO][J.WIDTH], o[J.VIDEO][J.HEIGHT] ]; 3900 3901 } 3902 3903 if (settings.hiDpiImages) { 3904 return [ o[J.IMAGE][J.WIDTH] / 2, o[J.IMAGE][J.HEIGHT] / 2 ]; 3905 } 3906 3907 return [ o[J.IMAGE][J.WIDTH], o[J.IMAGE][J.HEIGHT] ]; 3908 }, 3909 3910 // Tries to figure out the original image dimensions 3911 3912 getOriginalDimensions = o => { 3913 3914 if (typeof o !== UNDEF) { 3915 3916 if (o[J.CATEGORY] === 'video') { 3917 // Video 3918 return [ o[J.VIDEO][J.WIDTH], o[J.VIDEO][J.HEIGHT] ]; 3919 } 3920 3921 if (o.hasOwnProperty(J.ORIGINAL)) { 3922 return [ o[J.ORIGINAL][J.WIDTH], o[J.ORIGINAL][J.HEIGHT] ]; 3923 } 3924 } 3925 3926 return null; 3927 }, 3928 3929 // Tries to figure out the original image dimensions 3930 3931 getMaxDimensions = o => { 3932 3933 if (typeof o !== UNDEF) { 3934 let dim; 3935 3936 if (settings.useOriginals && (dim = getOriginalDimensions(o))) { 3937 // Has original 3938 return dim; 3939 } else if (o.hasOwnProperty(J.IMAGE)) { 3940 // Has image 3941 let img = o[J.IMAGE]; 3942 3943 if (img.hasOwnProperty(J.RENDITIONS)) { 3944 // Has renditions (variants) 3945 dim = [ img[J.RENDITIONS][0][J.WIDTH], img[J.RENDITIONS][0][J.HEIGHT] ]; 3946 3947 for (let i = 1; i < img[J.RENDITIONS].length; i++) { 3948 if (img[J.RENDITIONS][i][J.WIDTH] > dim[0]) { 3949 dim = [ img[J.RENDITIONS][i][J.WIDTH], img[J.RENDITIONS][i][J.HEIGHT] ]; 3950 } 3951 } 3952 3953 return dim; 3954 3955 } 3956 3957 return settings.hiDpiImages? 3958 [ 2 * img[J.WIDTH], 2 * img[J.HEIGHT] ] 3959 : 3960 [ img[J.WIDTH], img[J.HEIGHT] ]; 3961 } 3962 } 3963 3964 return null; 3965 }, 3966 3967 // Returns the thumbnail's aspect ratio 3968 3969 getAspectRatio = function(o, thumb) { 3970 3971 if (typeof o !== UNDEF) { 3972 if (typeof thumb === UNDEF) thumb = false; 3973 if (thumb && o.hasOwnProperty(J.THUMB)) { 3974 return o[J.THUMB][J.WIDTH] / o[J.THUMB][J.HEIGHT]; 3975 } 3976 if (!thumb) { 3977 if (o[J.CATEGORY] === 'video') { 3978 return o[J.VIDEO][J.WIDTH] / o[J.VIDEO][J.HEIGHT]; 3979 } else if (o.hasOwnProperty(J.IMAGE)) { 3980 return o[J.IMAGE][J.WIDTH] / o[J.IMAGE][J.HEIGHT]; 3981 } 3982 } 3983 } 3984 3985 return 'auto'; 3986 }, 3987 3988 // Returns the parsed regions array 3989 3990 getRegions = function(o) { 3991 3992 if (typeof o !== UNDEF && o.hasOwnProperty(J.REGIONS)) { 3993 return JSON.parse(o[J.REGIONS]); 3994 } 3995 3996 return []; 3997 }, 3998 3999 // Returns absolute or relative link to object in the real album 4000 4001 getLink = function(o) { 4002 4003 if (typeof o !== UNDEF) { 4004 4005 switch (o[J.CATEGORY]) { 4006 4007 case 'folder': 4008 4009 return getPath(o); 4010 4011 case 'webLocation': 4012 4013 return o[J.PATH]; 4014 4015 case 'webPage': 4016 4017 return makePath(getPath(o), o[J.PATH]); 4018 4019 default: 4020 4021 // Image or other lightboxable item 4022 return makePath(getPath(o), '#img=' + o[J.PATH]); 4023 } 4024 } 4025 4026 return null; 4027 }, 4028 4029 // Get path to item from root 4030 4031 getRootPath = o => (typeof o !== UNDEF)? makePath(getPath(o), o[J.PATH]) : null, 4032 4033 // Get the pointer to a folder in a tree from path reference number 4034 4035 getPointer = n => { 4036 4037 if (typeof n !== NUMBER || n <= 0) { 4038 // root 4039 return tree; 4040 } 4041 4042 n--; 4043 4044 if (n > paths.length) { 4045 log('Error: out of bounds path reference (' + n + ')!'); 4046 return null; 4047 } 4048 4049 return getFolder(paths[n]); 4050 }, 4051 4052 // Gets relative folder path from the current folder (unnecessary) 4053 4054 getRelativeFolderPath = o => (typeof o !== UNDEF)? fixUrl(settings.rootPath + toPath(o)) : null, 4055 4056 // Returns folder object by full path 4057 4058 getFolder = path => { 4059 4060 if (typeof path === UNDEF) { 4061 // Invalid 4062 return null; 4063 } else if (!path.length) { 4064 // Top level 4065 return tree; 4066 } 4067 4068 if (path.endsWith('/')) { 4069 // Sanitize 4070 path = path.slice(0, -1); 4071 } 4072 if (path.startsWith('/')) { 4073 // Sanitize 4074 path = path.slice(1); 4075 } 4076 4077 let f = tree, 4078 p = path.split('/'), 4079 level; 4080 4081 for (level = 0; level < p.length; level++) { 4082 if (isDeep()) { 4083 if (f.hasOwnProperty(J.OBJECTS) && 4084 (f = f[J.OBJECTS].find(o => (o[J.CATEGORY] === 'folder' && o[J.PATH] === p[level])))) { 4085 continue; 4086 } 4087 }
4087 else if (f.hasOwnProperty(J.FOLDERS) && 4088 (f = f[J.FOLDERS].find(o => o[J.PATH] === p[level]))) { 4089 continue; 4090 } 4091 // Not found: return null 4092 return null; 4093 } 4094 4095 // Matched full path 4096 return (level === p.length)? f : null; 4097 }, 4098 4099 // Returns current folder 4100 4101 getCurrentFolder = () => currentFolder, 4102 4103 // Getting the parent 4104 4105 getParent = o => { 4106 4107 if (typeof o === UNDEF) { 4108 // Invalid 4109 o = currentFolder; 4110 } 4111 4112 if (o === tree) { 4113 // At top level 4114 return null; 4115 } 4116 4117 // Getting current folder or the parent in case of a folder 4118 let p = o.hasOwnProperty(J.PARENTREF)? getPointer(o[J.PARENTREF]) : getPointer(o[J.PATHREF]); 4119 4120 // Avoid endless loops 4121 return (p === o)? null : p; 4122 }, 4123 4124 // Getting an item from full path, waits for folder to load if not present 4125 // Optionally passing an extra parameter 4126 4127 getItem = function(path, doneFn, p) { 4128 4129 if (typeof doneFn !== FUNCTION) { 4130 return; 4131 } 4132 4133 let _getItem = function(folder, args) { 4134 let name = args[0], 4135 doneFn = args[1]; 4136 4137 if (folder.hasOwnProperty(J.OBJECTS)) { 4138 let o; 4139 4140 if (name.endsWith('.webloc')) { 4141 // Filtering for "slides/NAME.ext" 4142 name = name.slice(0, -7); 4143 o = folder[J.OBJECTS].find(function(o) { 4144 return o[J.CATEGORY] === 'webLocation' && o[J.IMAGE][J.PATH].slice(7).stripExt() === name; 4145 }); 4146 } else { 4147 o = folder[J.OBJECTS].find(function(o) { 4148 return o.hasOwnProperty(J.ORIGINAL) && o[J.ORIGINAL][J.PATH] === name || // Original path (e.g. Other file types 4149 o.hasOwnProperty(J.IMAGE) && ( 4150 o[J.IMAGE][J.PATH] === name || // Non-converted images, e.g. GIF or PNG 4151 o[J.IMAGE][J.PATH] === SLIDES_DIR + '/' + name // Normal images 4152 ) || 4153 o[J.PATH] === name; // Fallback to Path 4154 }); 4155 } 4156 4157 if (typeof o === UNDEF) { 4158 //log('Missing object "' + name + '"'); 4159 o = null; 4160 } 4161 4162 if (typeof p !== UNDEF) { 4163 doneFn.call(o, p); 4164 } else { 4165 doneFn.call(o); 4166 } 4167 } else { 4168 //log('No "Objects" in folder "' + folder[J.NAME] + '"'); 4169 } 4170 }; 4171 4172 if (!path) { 4173 // Tree 4174 if (typeof p !== UNDEF) { 4175 doneFn.call(tree, p); 4176 } else { 4177 doneFn.call(tree); 4178 } 4179 } else if (path.endsWith('/')) { 4180 // Folder 4181 if (typeof p !== UNDEF) { 4182 doneFn.call(getFolder(path), p); 4183 } else { 4184 doneFn.call(getFolder(path)); 4185 } 4186 } else { 4187 // An object 4188 let i = path.lastIndexOf('/'), 4189 folder = (i === -1)? tree : getFolder(path.substring(0, i)), 4190 name = path.substring(i + 1); 4191 4192 if (folder) { 4193 // Exists 4194 if (deepReady || folder.hasOwnProperty(J.READY) && folder.ready) { 4195 // Objects already loaded 4196 //log('Accessing "' + name + '" in folder "' + folder[J.NAME] + '"'); 4197 _getItem(folder, [ name, doneFn ]); 4198 4199 } else if (isDeep()) { 4200 // Loads deep, but database hasn't been loaded yet 4201 //log('Not yet deep ready when accessing "' + name + '" in folder "' + folder[J.NAME] + '"'); 4202 onDeepReady(folder, _getItem, [ name, doneFn ]); 4203 } else { 4204 // Have to wait until objects get loaded 4205 //log('Folder "' + folder[J.NAME] + '" is not yet ready when accessing "' + name + '"'); 4206 onFolderReady(folder, _getItem, [ name, doneFn ]); 4207 } 4208 } 4209 } 4210 4211 return null; 4212 }, 4213 4214 // Returns all objects in a folder but subfolders 4215 4216 getObjects = f => { 4217 let folder = (typeof f === UNDEF)? currentFolder : f, 4218 objects = []; 4219 4220 if (folder.hasOwnProperty(J.OBJECTS)) { 4221 folder[J.OBJECTS].forEach(o => { 4222 if (o[J.CATEGORY] !== 'folder') { 4223 objects.push(o); 4224 } 4225 }); 4226 } 4227 4228 return objects; 4229 }, 4230 4231 // Returns all objects in a folder 4232 4233 getAllObjects = f => { 4234 let folder = (typeof f === UNDEF)? currentFolder : f, 4235 objects = []; 4236 4237 if (folder.hasOwnProperty(J.OBJECTS)) {
4238 folder[J.OBJECTS].forEach(o => { 4239 if (o.hasOwnProperty(J.FOLDERINDEX)) { 4240 objects.push(folder[J.FOLDERS][o[J.FOLDERINDEX]]); 4241 } else { 4242 objects.push(o); 4243 } 4244 }); 4245 } 4246 4247 return objects; 4248 }, 4249 4250 // Returns folders and weblocations 4251 4252 getFoldersAndWeblocs = f => { 4253 let folder = (typeof f === UNDEF)? currentFolder : f, 4254 objects = []; 4255 4256 if (folder.hasOwnProperty(J.OBJECTS)) { 4257 folder[J.OBJECTS].forEach(o => { 4258 if (o.hasOwnProperty(J.FOLDERINDEX)) { 4259 objects.push(folder[J.FOLDERS][o[J.FOLDERINDEX]]); 4260 } else if (o[J.CATEGORY] === 'folder' || o[J.CATEGORY] === 'webLocation') { 4261 objects.push(o); 4262 } 4263 }); 4264 } 4265 4266 return objects; 4267 }, 4268 4269 // Returns only the lightboxable items 4270 4271 getImages = f => { 4272 let folder = (typeof f === UNDEF)? currentFolder : f, 4273 objects = []; 4274 4275 if (folder.hasOwnProperty(J.OBJECTS)) { 4276 folder[J.OBJECTS].forEach(o => { 4277 if (isLightboxable(o)) { 4278 objects.push(o); 4279 } 4280 }); 4281 } 4282 4283 return objects; 4284 }, 4285 4286 // Returns the folders as an array. No hidden folders. Avoid copying the child folders. 4287 4288 getFolders = f => { 4289 let folder = (typeof f === UNDEF)? currentFolder : f, 4290 folders = [], 4291 4292 // Avoid copying deep structure; only the immediate properties 4293 copyProps = f => { 4294 let o = {}; 4295 4296 for (let prop in f) { 4297 // Copying folder variables besides OBJECTS and ALBUM (only those missing) 4298 if (prop !== J.OBJECTS && prop !== J.ALBUM && prop !== J.FOLDERS) { 4299 o[prop] = f[prop]; 4300 } 4301 } 4302 4303 return o; 4304 }; 4305 4306 if (folder) { 4307 if (isDeep()) { 4308 if (folder.hasOwnProperty(J.OBJECTS)) { 4309 folder[J.OBJECTS].forEach(o => { 4310 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o.hidden)) { 4311 folders.push(copyProps(o)); 4312 } 4313 });
4314 } 4315 } else if (folder.hasOwnProperty(J.FOLDERS)) { 4316 folder[J.FOLDERS].forEach(f => { 4317 if (!(f.hasOwnProperty(J.HIDDEN) && f.hidden)) { 4318 folders.push(copyProps(f)); 4319 } 4320 }); 4321 } 4322 } 4323 4324 return folders; 4325 }, 4326 4327 /*********************************************** 4328 * 4329 * Properties 4330 * 4331 */ 4332 4333 // Album make date/time in UTC 4334 4335 getMakeDate = () => new Date(tree[J.FILEDATE]), 4336 4337 // Album title 4338 4339 getAlbumTitle = () => tree[J.TITLE] || tree[J.NAME], 4340 4341 // Filename for images, originalname for other 4342 4343 getItemName = o => ( 4344 (o[J.CATEGORY] === 'video')? 4345 getFilenameFromURL(o[J.VIDEO][J.PATH]) 4346 : 4347 (o.hasOwnProperty(J.ORIGINAL)? 4348 getFilenameFromURL(o[J.ORIGINAL][J.PATH]) 4349 : 4350 o[J.NAME] 4351 ) 4352 ), 4353 4354 // Level? 4355 4356 getLevel = o => { 4357 o = o || currentFolder; 4358 return o.hasOwnProperty(J.LEVEL)? o[J.LEVEL] : getLevel(getParent(o)); 4359 }, 4360 4361 // Title 4362 4363 getTitle = o => (o || currentFolder)[J.TITLE] || '', 4364 4365 // Name 4366 4367 getName = o => (o || currentFolder)[J.NAME] || '', 4368 4369 // Label: underscores replaced with spaces, file extension removed 4370 4371 getLabel = o => ((o || currentFolder)[J.NAME] || '').replace(/\.\w+$/, '').replace(/_/g, ' '), 4372 4373 // Title or name for ALT tags 4374 4375 getAlt = o => getTitle(o) || getLabel(o), 4376 4377 // Comment 4378 4379 getComment = o => (o || currentFolder)[J.COMMENT] || '', 4380 4381 // Thumbnail path (folder thumbs located one level up!) 4382 4383 getThumbPath = o => makePath(getPath(o), 4384 o.hasOwnProperty(J.LEVEL)? 4385 // Folder 4386 o[J.THUMB][J.PATH].replace(o[J.PATH] + '/', '') 4387 : 4388 o[J.THUMB][J.PATH] 4389 ), 4390 4391 // Icon path? 4392 4393 getIconPath = o => o[J.THUMB][J.PATH].match(/res\/\w+\.png$/)? o[J.THUMB][J.PATH] : '', 4394 4395 // Image path (falls back to thumb path, e.g. PDF files) 4396 4397 getImagePath = o => makePath(getPath(o), 4398 o.hasOwnProperty(J.LEVEL)? 4399 // Folder: grab the same image as thumb from slides 4400 o[J.THUMB][J.PATH].replace(settings.thumbsDir + '/', settings.slidesDir + '/') 4401 : 4402 // Image or other 4403 (o.hasOwnProperty(J.ORIGINALFILE)? 4404 o[J.ORIGINALFILE] 4405 : 4406 (o.hasOwnProperty(J.IMAGE)? 4407 o[J.IMAGE][J.PATH] 4408 : 4409 o[J.THUMB][J.PATH] 4410 ) 4411 ) 4412 ), 4413 4414 // Image path from root 4415 4416 getImagePathFromRoot = o => makePath(toPath(o), 4417 o.hasOwnProperty(J.ORIGINALFILE)? 4418 o[J.ORIGINALFILE] 4419 : 4420 (o.hasOwnProperty(J.IMAGE)? 4421 o[J.IMAGE][J.PATH] 4422 : 4423 o[J.THUMB][J.PATH] 4424 ) 4425 ), 4426 4427 // Absolute image path 4428 4429 getAbsoluteImagePath = o => makePath(absolutePath, getImagePathFromRoot(o)), 4430 4431 // Theme image path 4432 4433 getThemeImagePath = o => makePath(getPath(o), settings.folderImageFile), 4434 4435 // Original path 4436 4437 getOriginalPath = o => (o.hasOwnProperty(J.ORIGINALFILE)? 4438 makePath(getPath(o), o[J.ORIGINALFILE]) 4439 : 4440 (o.hasOwnProperty(J.ORIGINAL)? 4441 makePath(getPath(o), o[J.ORIGINAL][J.PATH]) 4442 : 4443 null 4444 ) 4445 ), 4446 4447 // Original custom page name (before renamed) 4448 4449 getOriginalPageName = o => ((o[J.CATEGORY] === 'webPage' && o.hasOwnProperty(J.ORIGINALPAGENAME))? 4450 o[J.ORIGINALPAGENAME] 4451 : 4452 null 4453 ), 4454 4455 // Video path 4456 4457 getVideoPath = o => (o.hasOwnProperty(J.VIDEO)? 4458 makePath(getPath(o), o[J.VIDEO][J.PATH]) 4459 : 4460 null 4461 ), 4462 4463 // Poster path for audio and video files 4464 4465 getPosterPath = o => { 4466 let c = o[J.CATEGORY], 4467 ip = o[J.IMAGE][J.PATH]; 4468 4469 // Audio and video: using default poster graphics if iconpath was used 4470 if ((c === 'audio' && ip.endsWith('res/audio.png')) || (c === 'video' && ip.endsWith('res/movie.png'))) { 4471 return makePath(settings.rootPath, 'res', settings[c + 'Poster']); 4472 } 4473 4474 return makePath(getPath(o), o[J.IMAGE][J.PATH]); 4475 }, 4476 4477 // Finding closest rendition 4478 4479 getClosestRendition = (r, dim, fill) => { 4480 4481 if (!WEBP_LOSSY) { 4482 // No webp support: filtering out 4483 r = r.filter(o => !o.name.endsWith('.webp')); 4484 } 4485 4486 if (r.length > 1) { 4487 let w = dim[0] * PIXELRATIO, 4488 h = dim[1] * PIXELRATIO; 4489 4490 // Variants: calculate scale ratio 4491 r.forEach(o => { 4492 let s = fill? Math.max(w / o.width, h / o.height) : Math.min(w / o.width, h / o.height); 4493 4494 o.match = (s > 1)? 4495 // Upscaling penalized by factor 3: 1x -> 120% == 2x -> 50% 4496 (3 * (1 - 1 / s)) 4497 : 4498 // Downscaling 4499 (1 - s) 4500 });
4501 4502 // Sort by scale 4503 r.sort((o1, o2) => o1.match - o2.match); 4504 } 4505 4506 return r[0]; 4507 }, 4508 4509 // Get path to optimum image. If no dim specified the image is optimized for the screen size. 4510 4511 getOptimalImagePath = (o, dim, useOriginal, fill) => { 4512 var fill = (typeof fill === UNDEF)? false : fill; 4513 4514 if (o.hasOwnProperty(J.LEVEL)) { 4515 // A folder 4516 return toRelPath(o) + '/' + settings.folderImageFile; 4517 } 4518 4519 if (o[J.CATEGORY] === 'webLocation') { 4520 // Web location 4521 return getImagePath(o); 4522 } 4523 4524 if (o[J.CATEGORY] === 'webPage') { 4525 // Web location 4526 return getThumbPath(o); 4527 } 4528 4529 if (o.hasOwnProperty(J.ORIGINALFILE)) { 4530 // Non-image with original 4531 return getOriginalPath(o); 4532 } 4533 4534 let r = o[J.IMAGE].hasOwnProperty(J.RENDITIONS)? 4535 getClosestRendition(o[J.IMAGE][J.RENDITIONS], dim || [ window.outerWidth, window.outerHeight ], fill) 4536 : 4537 (settings.hiDpiImages? 4538 { 4539 width: 2 * o[J.IMAGE][J.WIDTH], 4540 height: 2 * o[J.IMAGE][J.HEIGHT] 4541 } 4542 : 4543 { 4544 width: o[J.IMAGE][J.WIDTH], 4545 height: o[J.IMAGE][J.HEIGHT] 4546 } 4547 ); 4548 4549 if (typeof useOriginal !== UNDEF && useOriginal && 4550 o.hasOwnProperty(J.ORIGINAL) && 4551 (dim[0] > r.width || dim[1] > r.height) && 4552 o[J.ORIGINAL][J.WIDTH] > o[J.IMAGE][J.WIDTH] && 4553 isBrowserFriendly(o[J.ORIGINAL][J.PATH])) { 4554 // Using the original 4555 return getOriginalPath(o); 4556 } 4557 4558 // No original: Using the closest rendition 4559 4560 if (r.hasOwnProperty(J.NAME)) { 4561 // It's a HiDPI image 4562 return replaceFile(getImagePath(o), r[J.NAME]); 4563 } 4564 4565 return getImagePath(o); 4566 }, 4567 4568 // Replace file name in a path 4569 4570 replaceFile = (p, fn) => (p.slice(0, p.lastIndexOf('/') + 1) + fn), 4571 4572 // Check if image dimensions are large enough to fit or fill the target 4573 4574 hasEnoughPixels = (tdim, idim, mxz, fill) => (fill? 4575 idim[0] * mxz >= tdim[0] && idim[1] * mxz >= tdim[1] 4576 : 4577 idim[0] * mxz >= tdim[0] || idim[1] * mxz >= tdim[1] 4578 ), 4579 4580 // Get path to optimum thumb 4581 4582 getOptimalThumbPath = (o, dim, useSlides) => { 4583 4584 if (o[J.THUMB].hasOwnProperty(J.RENDITIONS)) { 4585 // Has renditions 4586 let r = getClosestRendition(o[J.THUMB][J.RENDITIONS], dim, settings.thumbsFillFrame); 4587 4588 if (o[J.CATEGORY] === 'folder') { 4589 // Folder: only thumb path exists 4590 return makePath(getPath(o), replaceFile(o[J.THUMB][J.PATH], r[J.NAME])); 4591 } else if (o[J.CATEGORY] === 'video' || hasEnoughPixels(dim, [r[J.WIDTH], r[J.HEIGHT]], 1.07, settings.thumbsFillFrame)) { 4592 // Thumb is large enough: return thumb path 4593 return replaceFile(getThumbPath(o), r[J.NAME]); 4594 } else if (typeof useSlides !== UNDEF && useSlides && o[J.IMAGE].hasOwnProperty(J.RENDITIONS)) { 4595 // Try with image renditions 4596 r = getClosestRendition(o[J.IMAGE][J.RENDITIONS], dim, settings.thumbsFillFrame); 4597 return replaceFile(getImagePath(o), r[J.NAME]); 4598 } 4599 } 4600 4601 // No renditions 4602 let idim = [ o[J.THUMB][J.WIDTH], o[J.THUMB][J.HEIGHT] ]; 4603 4604 if (settings.hiDpiTHumbs) { 4605 idim[0] *= 2; 4606 idim[1] *= 2; 4607 } 4608 4609 if (typeof useSlides !== UNDEF && useSlides && 4610 (o[J.CATEGORY] !== 'video' && o.hasOwnProperty(J.IMAGE)) && 4611 !hasEnoughPixels(dim, idim, 1.15, settings.thumbsFillFrame)) { 4612 // The thumb is too small, use the image 4613 return getImagePath(o); 4614 } 4615 4616 // Returning the thumb 4617 return getThumbPath(o); 4618 }, 4619 4620 // Original or source path 4621 4622 getSourcePath = (o) => makePath(getPath(o), 4623 (o.hasOwnProperty(J.ORIGINAL)? 4624 o[J.ORIGINAL][J.PATH] 4625 : 4626 (o.hasOwnProperty(J.IMAGE)? 4627 o[J.IMAGE][J.PATH] 4628 : 4629 o[J.THUMB][J.PATH] 4630 ) 4631 ) 4632 ), 4633 4634 // Absolute path to an object as HTML page 4635
4636 getAbsoluteItemPath = (o) => makePath(absolutePath, getItemPath(o)), 4637 4638 // Get video duration in ms 4639 4640 getVideoDuration = o => { 4641 let v = o[J.VIDEO]; 4642 4643 if (v && v.hasOwnProperty(J.DURATION)) { 4644 return videoDurationMs(v[J.DURATION]); 4645 } 4646 4647 return null; 4648 }, 4649 4650 // Has shop options? 4651 4652 hasShop = o => { 4653 if (typeof o === UNDEF) { 4654 // Should not happen, but returns true if the global shop option exists 4655 return tree.hasOwnProperty(J.SHOP) && tree.shop.hasOwnProperty('options') && tree.shop.options.trim() !== '-'; 4656 } 4657 4658 let shop = getInheritedPropertyObject(o, J.SHOP); 4659 4660 return shop && ( 4661 shop.hasOwnProperty('usePrice') && shop.usePrice && o.hasOwnProperty(J.PRICE) && o.price 4662 || 4663 shop.hasOwnProperty('options') && shop.options.trim() !== '-' 4664 ); 4665 }, 4666 4667 // Has map location? 4668 4669 hasLocation = o => (o.hasOwnProperty(J.HIDELOCATION) && o[J.HIDELOCATION]) ? 4670 false 4671 : 4672 ( o.hasOwnProperty(J.LOCATION) || 4673 o.hasOwnProperty(J.CAMERA) && o[J.CAMERA].hasOwnProperty(J.LOCATION) 4674 ), 4675 4676 // Get location in NN.NNN,NN.NNN format (Lat,long) 4677 4678 getLocation = o => (o.hasOwnProperty(J.HIDELOCATION) && o[J.HIDELOCATION]) ? 4679 null 4680 : 4681 o.hasOwnProperty(J.LOCATION)? 4682 o[J.LOCATION] 4683 : 4684 ( 4685 (o.hasOwnProperty(J.CAMERA) && o[J.CAMERA].hasOwnProperty(J.LOCATION) && 4686 o[J.CAMERA][J.LOCATION].hasOwnProperty('lat') && o[J.CAMERA][J.LOCATION].hasOwnProperty('long')) ? 4687 (o[J.CAMERA][J.LOCATION]['lat'] + ',' + o[J.CAMERA][J.LOCATION]['long']) 4688 : 4689 null 4690 ), 4691 4692 // Disable Feedback? 4693 4694 disableFeedback = o => o.hasOwnProperty(J.DISABLEFEEDBACK)? 4695 o[J.DISABLEFEEDBACK] 4696 :
4697 (getInheritedProperty(o, J.DISABLEFEEDBACK) || false) 4698 , 4699 4700 // Hide from Fotomoto? 4701 4702 hideFotomoto = o => o.hasOwnProperty(J.HIDEFOTOMOTO)? 4703 o[J.HIDEFOTOMOTO] 4704 : 4705 (getInheritedProperty(o, J.HIDEFOTOMOTO) || false) 4706 , 4707 4708 // Get the lowest price of an item 4709 4710 getPriceRange = o => { 4711 let p = getInheritedPropertyObject(o || tree, J.SHOP); 4712 4713 if (p && p['options'] !== '-' && p['showPriceRange']) { 4714 let opt = p.options.split('::'), 4715 min = Number.MAX_VALUE, 4716 max = Number.MIN_VALUE; 4717 4718 if (opt.length > 1) { 4719 for (let i = 0; i < opt.length; i++) { 4720 min = Math.min(parseFloat(opt[i].split('=')[1].split('+')[0]), min); 4721 } 4722 if (p.showPriceRange === 'minmax') { 4723 for (let i = 0; i < opt.length; i++) { 4724 max = Math.max(parseFloat(opt[i].split('=')[1].split('+')[0]), max); 4725 } 4726 return toCurrency(min, p['currency']) + '–' + toCurrency(max, p['currency']); 4727 } 4728 return text.from.template(toCurrency(min, p['currency'])); 4729 } else { 4730 return toCurrency(opt[0].split('=')[1].split('+')[0], p['currency']); 4731 } 4732 } 4733 4734 return ''; 4735 }, 4736 4737 // Get shopping cart's currency, falls back to EUR 4738 4739 getCurrency = () => getRootProperty(J.SHOP)['currency'] || 'EUR', 4740 4741 // Get custom page content (HTML) 4742 4743 getCustomPageContent = (o) => { 4744 return (o && o.hasOwnProperty(J.CUSTOMPAGECONTENT))? o[J.CUSTOMPAGECONTENT] : ''; 4745 }, 4746 4747 // Counting folders recursively (current folder included) 4748 4749 getDeepFolderCount = f => { 4750 let c = 0, 4751 folder = (typeof f === UNDEF)? currentFolder : f; 4752 4753 if (folder.hasOwnProperty(J.DEEPCOUNTERS) && folder[J.DEEPCOUNTERS].hasOwnProperty(J.FOLDERS)) { 4754 // Has deep folder count 4755 return folder[J.DEEPCOUNTERS][J.FOLDERS] + 1; 4756 } 4757 4758 if (isDeep()) { 4759 if (folder.hasOwnProperty(J.OBJECTS)) { 4760 folder[J.OBJECTS].forEach(o => { 4761 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN])) { 4762 c += getDeepFolderCount(o); 4763 } 4764 }); 4765 } 4766 } else if (folder.hasOwnProperty(J.FOLDERS)) { 4767 folder[J.FOLDERS].forEach(o => { 4768 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN])) { 4769 c += getDeepFolderCount(o); 4770 } 4771 }); 4772 } 4773 4774 // Adding missing deep counter 4775 if (!folder.hasOwnProperty(J.DEEPCOUNTERS)) { 4776 folder[J.DEEPCOUNTERS] = {}; 4777 } 4778 4779 // Saving deep folder count 4780 folder[J.DEEPCOUNTERS][J.FOLDERS] = c; 4781 4782 return c + 1; 4783 }, 4784 4785 // Getting folder count with limited folder depth 4786 4787 getFolderCount = (f, levels) => { 4788 let folder = (typeof f === UNDEF)? currentFolder : f, 4789 maxLevel = folder[J.LEVEL] + ((typeof levels === UNDEF)? 0 : levels), 4790 4791 getCount = folder => { 4792 let c = 1; 4793 4794 if (folder[J.LEVEL] <= maxLevel) { 4795 // Count only within max levels 4796 if (isDeep()) { 4797 if (folder.hasOwnProperty(J.OBJECTS)) { 4798 folder[J.OBJECTS].forEach(o => { 4799 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN])) { 4800 c += getCount(o); 4801 } 4802 }); 4803 } 4804 } else if (folder.hasOwnProperty(J.FOLDERS)) { 4805 folder[J.FOLDERS].forEach(o => { 4806 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN])) { 4807 c += getCount(o); 4808 } 4809 }); 4810 } 4811 } 4812 4813 return c; 4814 }; 4815 4816 return getCount(folder); 4817 }, 4818 4819 /******************************************** 4820 * 4821 * Generic property functions 4822 * 4823 */ 4824 4825 // Returns a property from the root level 4826 4827 getRootProperty = a => tree.hasOwnProperty(a)? tree[a] : null, 4828 4829 // Retrieving an Object property of an element with fallback to upper level folders 4830 4831 getInheritedPropertyObject = (o, a) => { 4832 let p = {}; 4833 4834 do { 4835 if (o.hasOwnProperty(a)) { 4836 p = $.extend(true, {}, o[a], p); 4837 } 4838 } while (o = getParent(o)); 4839 4840 return (Object.getOwnPropertyNames(p)).length? p : null; 4841 }, 4842 4843 // Retrieving a single property of an element with fallback to upper level folders 4844 4845 getInheritedProperty = (o, a) => { 4846 4847 if (a.indexOf('.') >= 0) { 4848 a = a.split('.'); 4849 4850 if (a[0] === 'album') { 4851 return getRootProperty(a[1]); 4852 } 4853 4854 do { 4855 if (o.hasOwnProperty(a[0])) { 4856 return $.extend(true, {}, o[a[0]][a[1]]); 4857 } 4858 } while (o = getParent(o)); 4859 4860 return null; 4861 } 4862 4863 do { 4864 if (o.hasOwnProperty(a)) { 4865 return $.extend(true, {}, o[a]); 4866 } 4867 } while (o = getParent(o)); 4868 4869 return null; 4870 }, 4871 4872 // Returns a property (normal or inherited way) 4873 4874 getProperty = (o, a, inherit) => { 4875 let r; 4876 4877 if (inherit) { 4878 r = getInheritedProperty(o, a); 4879 } else if (a.indexOf('.') > 0) { 4880 a = a.split('.'); 4881 r = (o.hasOwnProperty(a[0]))? o[a[0]][a[1]] : null; 4882 } else if (o.hasOwnProperty(a)) { 4883 r = o[a]; 4884 } 4885 4886 return $.extend(true, {}, r); 4887 }, 4888 4889 // Returns an Object property (normal or inherited way) 4890 4891 getPropertyObject = (o, a, inherit) => ( 4892 inherit? getInheritedPropertyObject(o, a) : (o.hasOwnProperty(a)? $.extend(true, {}, o[a]) : null) 4893 ), 4894 4895 // Getting folder's index in Folders array 4896 4897 _getFolderIndex = (parent, f) => { 4898 let p = f[J.PATH], 4899 idx = -1; 4900 4901 if (isDeep()) { 4902 if (parent.hasOwnProperty(J.OBJECTS)) { 4903 idx = parent[J.OBJECTS].findIndex(o => (o[J.CATEGORY] === 'folder' && o[J.PATH] === p)); 4904 } 4905 } else {
4906 if (parent.hasOwnProperty(J.FOLDERS)) { 4907 idx = parent[J.FOLDERS].findIndex(f => f[J.PATH] === p); 4908 } 4909 } 4910 4911 return idx; 4912 }, 4913 4914 // Returns the next folder 4915 4916 _getNextFolder = function(f) { 4917 let folder = f || currentFolder, 4918 parent = getParent(folder), 4919 idx = _getFolderIndex(parent, folder); 4920 4921 if (idx >= 0) { 4922 if (settings.loadDeep) { 4923 if (parent.hasOwnProperty(J.OBJECTS)) { 4924 for (let i = idx + 1; i < parent[J.OBJECTS].length; i++) { 4925 if (parent[J.OBJECTS][i][J.CATEGORY] === 'folder' && 4926 !(parent[J.OBJECTS][i].hasOwnProperty(J.HIDDEN) && parent[J.OBJECTS][i][J.HIDDEN])) { 4927 return parent[J.OBJECTS][i]; 4928 } 4929 } 4930 } 4931 } else { 4932 if (parent.hasOwnProperty(J.FOLDERS)) { 4933 for (let i = idx + 1; i < parent[J.FOLDERS].length; i++) { 4934 if (!(parent[J.FOLDERS][i].hasOwnProperty(J.HIDDEN) && parent[J.FOLDERS][i][J.HIDDEN])) { 4935 return parent[J.FOLDERS][i]; 4936 } 4937 } 4938 } 4939 } 4940 } 4941 4942 return null; 4943 }, 4944 4945 // Returns the previous folder 4946 4947 _getPreviousFolder = function(f) { 4948 let folder = f || currentFolder, 4949 parent = getParent(folder), 4950 idx = _getFolderIndex(parent, folder); 4951 4952 if (idx >= 0) { 4953 if (settings.loadDeep) { 4954 if (parent.hasOwnProperty(J.OBJECTS)) { 4955 for (let i = idx - 1; i >= 0; i--) { 4956 if (parent[J.OBJECTS][i][J.CATEGORY] === 'folder' && 4957 !(parent[J.OBJECTS][i].hasOwnProperty(J.HIDDEN) && parent[J.OBJECTS][i][J.HIDDEN])) { 4958 return parent[J.OBJECTS][i]; 4959 } 4960 } 4961 } 4962 } else { 4963 if (parent.hasOwnProperty(J.FOLDERS)) { 4964 for (let i = idx - 1; i >= 0; i--) { 4965 if (!(parent[J.FOLDERS][i].hasOwnProperty(J.HIDDEN) && parent[J.FOLDERS][i][J.HIDDEN])) { 4966 return parent[J.FOLDERS][i]; 4967 } 4968 } 4969 } 4970 } 4971 } 4972 4973 return null; 4974 }, 4975 4976 // Get next folder's first image 4977 4978 getNextFoldersFirstImage = function(readyFn) { 4979 let folder = _getNextFolder(currentFolder), 4980 4981 _getFirstImage = function(folder) { 4982 if (folder.hasOwnProperty(J.OBJECTS)) { 4983 for (let i = 0; i < folder[J.OBJECTS].length; i++) { 4984 if (isLightboxable(folder[J.OBJECTS][i])) { 4985 return folder[J.OBJECTS][i]; 4986 } 4987 } 4988 } 4989 }; 4990 4991 if (folder) { 4992 4993 if (folder.hasOwnProperty(J.READY) && folder.ready) { 4994 readyFn(_getFirstImage(folder)); 4995 } else { 4996 onFolderReady(folder, function(folder, args) { 4997 args[0](_getFirstImage(folder)); 4998 }, [ readyFn ]); 4999 } 5000 } 5001 }, 5002 5003 // Get previous folder's last image 5004 5005 getPreviousFoldersLastImage = function(readyFn) { 5006 let folder = _getPreviousFolder(currentFolder), 5007 5008 _getLastImage = function(folder) { 5009 if (folder.hasOwnProperty(J.OBJECTS)) { 5010 for (let i = o.length - 1; i >= 0; i--) { 5011 if (isLightboxable(folder[J.OBJECTS][i])) { 5012 return folder[J.OBJECTS][i]; 5013 } 5014 } 5015 } 5016 }; 5017 5018 5019 if (folder) { 5020 5021 if (folder.hasOwnProperty(J.READY) && folder.ready) { 5022 readyFn(_getLastImage(folder)); 5023 } else { 5024 onFolderReady(folder, function(folder, args) { 5025 args[0](_getLastImage(folder)); 5026 }, [ readyFn ]); 5027 } 5028 } 5029 5030 return null; 5031 }, 5032 5033 /*************************************************************** 5034 * 5035 * Sort items: images, folders 5036 * sortBy: sort criteria ('original' | 'random' | 'date' | 'name' | 'fileSize') 5037 * reference: if sort by date ('added' | 'fileModified' | 'dateTaken') 5038 * reverse: false = normal, true = reversed 5039 * foldersFirst: put folders first 5040 */ 5041 5042 sortItems = function(images, folders, opt) { 5043 5044 if (!Array.isArray(images)) { 5045 // Images array is mandatory 5046 return null; 5047 } 5048 5049 if (!Array.isArray(folders)) { 5050 // No folders provided 5051 opt = folders || {}; 5052 folders = null; 5053 } 5054 5055 let options = $.extend({ 5056 sortBy: 'original', // No change 5057 reference: J.DATETAKEN, // Taken date 5058 reverse: false, // Not reversed 5059 foldersFirst: true // Put folders first 5060 }, opt); 5061 5062 // Ordering items 5063 switch (options.sortBy) { 5064 5065 case 'random': 5066 5067 // Random 5068 5069 images.sort(() => (0.5 - Math.random())); 5070 5071 if (folders) { 5072 folders.sort(() => (0.5 - Math.random())); 5073 } 5074 5075 break; 5076 5077 case 'date': 5078 5079 // Date 5080 5081 let ref = options['reference']; 5082 5083 if (options.reverse) { 5084 5085 images.sort((a, b) => ( 5086 (a.hasOwnProperty(J.DATES)? a[J.DATES][ref] : a[J.FILEDATE]) - 5087 (b.hasOwnProperty(J.DATES)? b[J.DATES][ref] : b[J.FILEDATE]) 5088 )); 5089 5090 if (folders) { 5091 folders.sort((a, b) => (a[J.FILEDATE] - b[J.FILEDATE])); 5092 } 5093 5094 } else { 5095 5096 images.sort((a, b) => ( 5097 (b.hasOwnProperty(J.DATES)? b[J.DATES][ref] : b[J.FILEDATE]) - 5098 (a.hasOwnProperty(J.DATES)? a[J.DATES][ref] : a[J.FILEDATE]) 5099 )); 5100 5101 if (folders) { 5102 folders.sort((a, b) => (b[J.FILEDATE] - a[J.FILEDATE])); 5103 } 5104 } 5105 5106 break; 5107 5108 case J.NAME: 5109 5110 // File name 5111 5112 if (!options.reverse) { 5113 5114 images.sort((a, b) => ('' + a[J.NAME]).localeCompare('' + b[J.NAME])); 5115 5116 if (folders) { 5117 folders.sort((a, b) => ('' + a[J.NAME]).localeCompare('' + b[J.NAME])); 5118 } 5119 5120 } else { 5121 5122 images.sort((a, b) => ('' + b[J.NAME]).localeCompare('' + a[J.NAME])); 5123 5124 if (folders) { 5125 folders.sort((a, b) => ('' + b[J.NAME]).localeCompare('' + a[J.NAME])); 5126 } 5127 5128 } 5129 5130 break; 5131 5132 case J.FILESIZE: 5133 5134 // File size: only images 5135 5136 if (options.reverse) { 5137 images.sort((a, b) => (a[J.FILESIZE] - b[J.FILESIZE])); 5138 } else { 5139 images.sort((a, b) => (b[J.FILESIZE] - a[J.FILESIZE])); 5140 } 5141 5142 break; 5143 5144 default: 5145 5146 // Descending? 5147 if (options.reverse) { 5148 5149 images.reverse(); 5150 5151 if (folders) { 5152 folders.reverse(); 5153 } 5154 } 5155 5156 break; 5157 5158 } 5159 5160 // Concatenating folders and images arrays 5161 5162 if (folders) { 5163 if (options.foldersFirst) { 5164 return folders.concat(images); 5165 } else { 5166 return images.concat(folders); 5167 } 5168 } 5169 5170 return images; 5171 5172 }, 5173 5174 /*************************************************************** 5175 * 5176 * collectByPath: Collect items by their paths within the album structure 5177 * paths: array of root paths 5178 * sortBy: sort criteria ('original' | 'random' | 'date' | 'name' | 'fileSize') 5179 * reference: if sort by date ('added' | 'fileModified' | 'dateTaken') 5180 * reverse: false = normal, true = reversed 5181 * ready: the function to call after items ready 5182 */ 5183 5184 collectByPath = function(opt) { 5185 5186 //log('collectByPath(' + JSON.stringify(opt) + ')'); 5187 if (typeof opt === UNDEF || !opt.hasOwnProperty('paths') || !Array.isArray(opt.paths) || typeof opt['ready'] !== FUNCTION) { 5188 return []; 5189 } 5190 5191 let items = [], 5192 options = $.extend({ 5193 folder: '', // Root folder 5194 levels: 0, // Current folder only 5195 sortBy: 'original', // No sort 5196 reference: 'dateTaken', // Reference 5197 reverse: false // Ascending 5198 }, opt), 5199 max = options.paths.length, 5200 counter = 0, 5201 fto, 5202 folder = getFolder(options.folder), 5203 5204 _skipEmpty = function(items) { 5205 let arr = []; 5206 5207 for (let i of items) { 5208 i && arr.push(i); 5209 } 5210 5211 return arr; 5212 }, 5213 5214 _finished = function() { 5215 5216 clearTimeout(fto); 5217 5218 if (counter < max) { 5219 log('Error: Timeout collecting items. Image set is incomplete (' + counter + '/' + max + ')!'); 5220 items = _skipEmpty(items) 5221 } 5222 5223 items = sortItems(items, { 5224 sortBy: options['sortBy'], 5225 reference: options['reference'], 5226 reverse: options['reverse'] 5227 });
5228 5229 options.ready.call(items, options); 5230 }; 5231 5232 // Allowing 25ms / item to collect, so it won't run infinitely if some items missing 5233 fto = setTimeout(_finished, max * 25); 5234 5235 for (let n = 0; n < max; n++) { 5236 5237 getItem(options.paths[n], function(i) { 5238 // Non-linear 5239 if (this && typeof i !== UNDEF && this !== window) { 5240 items[i] = this; 5241 if (++counter === max) { 5242 // All items got collected 5243 clearTimeout(fto); 5244 _finished(); 5245 } 5246 } 5247 }, n); 5248 } 5249 5250 5251 }, 5252 5253 /*************************************************************** 5254 * 5255 * collectNItem: Collect items from folder 5256 * folder: start folder 5257 * levels: depth below the start folder 5258 * max: maximum number 5259 * arRange: [ min, max ] - aspect ratio range 5260 * sortBy: sort criteria ('original' | 'random' | 'date' | 'name' | 'fileSize') 5261 * reference: if sort by date ('added' | 'fileModified' | 'dateTaken') 5262 * sortOrder: 1: ascending 0: descending 5263 * quick: true: stops when enough element has been gathered 5264 * false: loads all folders before it selects the "max" elements based on sort 5265 */ 5266 5267 collectNItem = function(opt) { 5268 5269 //log('collectNItem(' + JSON.stringify(opt) + ')'); 5270 if (typeof opt === UNDEF || typeof opt['ready'] !== FUNCTION) { 5271 return; 5272 } 5273 5274 let options = $.extend({ 5275 folder: '', // Root folder 5276 levels: 0, // Current folder only 5277 include: 'images', // Items to include 5278 max: 0, // No limit 5279 sortBy: 'original', // No sort 5280 sortOrder: 0 // Ascending 5281 }, opt), 5282 items = [], 5283 folders = [], 5284 folder = getFolder(options.folder), 5285 foldersToLoad = options.levels? getFolderCount(folder, options.levels) : 1, 5286 foldersLoaded = 0, 5287 completed = false, 5288 needsDeepData = false, //options.levels > 0 && folder.hasOwnProperty(J.FOLDERS), 5289 useImages = options.include.indexOf('images') !== -1, 5290 useFolders = options.include.indexOf('folders') !== -1, 5291 5292 _addItems = function(folder) { 5293 // Adding images in a folder 5294 folder[J.OBJECTS].forEach(o => { 5295 if (o[J.CATEGORY] === 'image' && 5296 ( 5297 !options['arRange'] || 5298 (o[J.IMAGE][J.WIDTH] / o[J.IMAGE][J.HEIGHT] >= options.arRange[0] && 5299 o[J.IMAGE][J.WIDTH] / o[J.IMAGE][J.HEIGHT] <= options.arRange[1]) 5300 ) 5301 ) { 5302 items.push(o); 5303 } 5304 });
5305 }, 5306 5307 _addFolder = function(folder) { 5308 5309 if (useFolders) { 5310 folders.push(folder); 5311 } 5312 5313 if (useImages) { 5314 // Adding objects of one folder 5315 if (folder.hasOwnProperty(J.OBJECTS)) { 5316 // Already loaded 5317 _addItems(folder); 5318 } else if (!folder[J.READY]) { 5319 // Wait to be loaded 5320 onFolderReady(folder, _addItems); 5321 //loadFolder(folder); 5322 } 5323 } 5324 5325 if (folder[J.LEVEL] <= maxLevel) { 5326 // recursive to subfolders 5327 if (isDeep()) { 5328 if (folder.hasOwnProperty(J.OBJECTS)) { 5329 folder[J.OBJECTS].forEach(o => { 5330 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5331 _addFolder(o); 5332 } 5333 }); 5334 } 5335 } else if (folder.hasOwnProperty(J.FOLDERS)) { 5336 folder[J.FOLDERS].forEach(o => { 5337 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5338 _addFolder(o); 5339 } 5340 }); 5341 } 5342 } 5343 5344 foldersLoaded++; 5345 }, 5346 5347 // Using deep-data 5348 _readyDeep = function() { 5349 5350 _addFolder(folder); 5351 5352 setTimeout(_finished, 20); 5353 }, 5354 5355 // Loading data1.json files one-by-one 5356 _loadAll = function() { 5357 5358 // Deep data already loaded or data1.json is enough 5359 _addFolder(folder); 5360 5361 // Allow some time to add the first defer 5362 setTimeout(function() { 5363 if (defer.length) { 5364 $.when.apply($, defer).done(_finished); 5365 } else { 5366 _finished(); 5367 } 5368 }, 20); 5369 }, 5370 5371 _finished = function() { 5372 5373 if (completed) { 5374 return; 5375 } 5376 5377 if (options.max && options.quick && (items.length + folders.length) >= options.max || 5378 !defer.length || (foldersLoaded >= foldersToLoad)) { 5379 5380 items = sortItems(items, folders, { 5381 sortBy: (options['sortOrder'] === -1)? 'random' : (options['sortBy'] || 'original'), 5382 reference: options['reference'] || J.FILEDATE, 5383 reverse: options['sortOrder'] === 0, 5384 foldersFirst: options['include'].startsWith('folders') 5385 }); 5386 5387 // Chopping the extra items 5388 if (options.max && options.max < items.length) { 5389 items = items.slice(0, options.max); 5390 } 5391 5392 completed = true; 5393 options.ready.call(items, options); 5394 5395 } else { 5396 setTimeout(_finished, 50); 5397 return; 5398 } 5399 }; 5400 5401 // Starting a new promise collect 5402 defer = []; 5403 completed = false; 5404 5405 if (!options.hasOwnProperty('quick')) { 5406 options.quick = options.max && options.sortBy !== 'original'; 5407 } 5408 5409 maxLevel = folder[J.LEVEL] + options.levels; 5410 5411 random = options.sortOrder === -1; 5412 if (random) { 5413 settings.sortBy = 'original'; 5414 } 5415 5416 if (needsDeepData && !deepReady) { 5417 // Loading deep data, falling back to recursive data1.json on error 5418 loadDeep(_readyDeep, _loadAll); 5419 5420 } else { 5421 // No deep data needed, loading current and its subtree (if needed) 5422 _loadAll(); 5423 } 5424 }, 5425 5426 /*************************************************************** 5427 * 5428 * collectByDate: Collect items by date 5429 * depth: where to collect ('tree' | 'current' | 'subfolders') 5430 * sortBy: sort criteria ('original' | 'random' | 'date' | 'name' | 'fileSize') 5431 * reference: if sort by date ('added' | 'fileModified' | 'dateTaken') 5432 * reverse: false = normal, true = reversed 5433 * range: time range 5434 * range = now - range ... now 5435 * range, start = start ... start + range 5436 * range, end = end - range ... end 5437 * Where start and end are days since 1900-01-01 5438 * Returns only lightboxable items 5439 */ 5440 5441 collectByDate = function(opt) { 5442 5443 //log('collectByDate(' + JSON.stringify(opt) + ')'); 5444 if (typeof opt === UNDEF || typeof opt['ready'] !== FUNCTION) { 5445 return; 5446 } 5447 5448 let options = $.extend({ 5449 sort: true, 5450 reverse: false, 5451 reference: J.DATETAKEN, 5452 depth: 'current' // 'tree' | 'current' | 'subfolders' 5453 }, opt), 5454 items = [], 5455 start, 5456 end, 5457 foldersToLoad = (options.depth === 'current')? 1 : getDeepFolderCount((options.depth === 'tree')? tree : currentFolder), 5458 foldersLoaded = 0, 5459 completed = false, 5460 needsDeepData = options.depth === 'tree' && tree.hasOwnProperty(J.FOL
5460DERS) || 5461 options.depth === 'subfolders' && currentFolder.hasOwnProperty(J.FOLDERS) && currentFolder[J.LEVEL] < 3, 5462 5463 5464 _findByDate = function(folder) { 5465 5466 // Find images that fall into the date range 5467 5468 if (!folder || !folder.hasOwnProperty(J.OBJECTS)) { 5469 return; 5470 } 5471 5472 folder[J.OBJECTS].forEach(o => { 5473 if (isLightboxable(o) && 5474 (d = o[J.DATES]) && 5475 (d = d[options.reference]) && 5476 (d >= start) && (d <= end)) { 5477 items.push(o); 5478 } 5479 }); 5480 5481 foldersLoaded++; 5482 }, 5483 5484 _addFolder = function(folder) { 5485 // Adding one folder 5486 5487 if (!folder || (folder.hasOwnProperty(J.HIDDEN) && folder.hidden)) { 5488 return; 5489 } 5490 5491 // Adding objects of one folder 5492 if (folder.hasOwnProperty(J.OBJECTS)) { 5493 // Already loaded 5494 _findByDate(folder); 5495 } else if (!folder[J.READY]) { 5496 // Wait to be loaded 5497 onFolderReady(folder, _findByDate); 5498 //loadFolder(folder); 5499 } 5500 5501 if (isDeep()) { 5502 if (folder.hasOwnProperty(J.OBJECTS)) { 5503 folder[J.OBJECTS].forEach(o => { 5504 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5505 _addFolder(o); 5506 } 5507 }); 5508 } 5509 } else if (folder.hasOwnProperty(J.FOLDERS)) { 5510 folder[J.FOLDERS].forEach(o => { 5511 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5512 _addFolder(o); 5513 } 5514 }); 5515 } 5516 }, 5517 5518 _readyDeep = function() { 5519 5520 _addFolder((options.depth === 'tree')? tree : currentFolder); 5521 5522 setTimeout(_finished, 20); 5523 }, 5524 5525 _loadAll = function() { 5526 5527 // Deep data already loaded or data1.json is enough 5528 _addFolder((options.depth === 'tree')? tree : currentFolder); 5529 5530 // Allow some time to add the first defer 5531 setTimeout(function() { 5532 if (defer.length) { 5533 $.when.apply($, defer).done(_finished); 5534 } else { 5535 _finished(); 5536 } 5537 }, 20); 5538 }, 5539 5540 _finished = function() { 5541 5542 if (completed) { 5543 return; 5544 } 5545 5546 //console.log('_finished: toLoad=' + foldersToLoad + ' loaded=' + foldersLoaded); 5547 if (defer.length && foldersToLoad > foldersLoaded) { 5548 setTimeout(_finished, 20); 5549 return; 5550 } 5551 5552 // Ordering items 5553 5554 if (options.sort) { 5555 items = sortItems(items, { 5556 sortBy: options['sortBy'] || 'date', 5557 reference: options['reference'] || J.DATETAKEN, 5558 reverse: options['reverse'] || false 5559 });
5560 } 5561 5562 if (options.max && options.max < items.length) { 5563 items = items.slice(0, options.max); 5564 } 5565 5566 completed = true; 5567 options.ready.call(items, options); 5568 }; 5569 5570 // Starting a new promise collect 5571 defer = []; 5572 completed = false; 5573 5574 // options.start and options.end are days since 1970-01-01, range is number of days 5575 if (options.hasOwnProperty('end')) { 5576 end = options.end * ONEDAY_S; 5577 } 5578 5579 if (options.hasOwnProperty('start')) { 5580 start = options.start * ONEDAY_S; 5581 } 5582 5583 if (options.hasOwnProperty('range')) { 5584 if (typeof start !== UNDEF) { 5585 end = start + options.range * ONEDAY_S; 5586 } else if (typeof end !== UNDEF) { 5587 start = end - options.range * ONEDAY_S; 5588 } else { 5589 // Up to now 5590 end = Math.round(new Date() / 1000); 5591 start = end - options.range * ONEDAY_S; 5592 } 5593 } 5594 5595 if (typeof start === UNDEF) { 5596 start = 0; 5597 } 5598 5599 if (typeof end === UNDEF) { 5600 end = Math.round(new Date() / 1000); 5601 } 5602 5603 if (needsDeepData && !deepReady) { 5604 5605 // Loading deep data, falling back to recursive data1.json on error 5606 loadDeep(_readyDeep, _loadAll); 5607 5608 } else { 5609 5610 _loadAll(); 5611 } 5612 }, 5613 5614 /*************************************************************** 5615 * 5616 * collectItems: Collecting search results 5617 * 5618 * fields: fields to watch 5619 * types: 'all' or comma separated list of allowed types ('image' | 'audio' | 'video' | ...) 5620 * depth: where to collect ('tree' | 'current' | 'subfolders') 5621 * exact: exact search (or conjunctive) 5622 * caseSensitive: case sensitivity: (false by default) 5623 * max: maximum number of results 5624 */ 5625 5626 collectItems = function(opt) { 5627 5628 //log('collectItems(' + JSON.stringify(opt) + ')'); 5629 if (typeof opt === UNDEF || !opt.hasOwnProperty('terms') || typeof opt['ready'] !== FUNCTION) { 5630 return; 5631 } 5632 5633 let options = $.extend({ 5634 fields: 'creator,keywords,name,title,comment,regions', 5635 types: 'all', 5636 depth: 'current', // 'tree' | 'current' | 'subfolders' 5637 divisible: 'name,title,comment,photodata', 5638 exact: false, 5639 caseSensitive: false 5640 }, opt), 5641 items = [], // Found items 5642 terms, // Terms to look for - used for undivisible fields 5643 termsArr, // Terms to look for (Array) - used for divisible fields 5644 fields = Array.isArray(options.fields)? options.fields : options.fields.split(/,\s?/), // fields to check 5645 fieldslength = fields.length, // Fields length 5646 divisible = new Array(fieldslength), // Fields that can be divided 5647 exact = false, // Exact search: ""foo boo"" 5648 conjunctive = false, // Conjunctive search: "foo and boo" 5649 splitRegExp, // Split RegExp 5650 hasSeparator, // Search term contains "-", ".", "/" or "_" 5651 allTypes = options.types === 'all', // Every item type to check 5652 types = {}, // Item types to check 5653 foldersToLoad = (options.depth === 'current')? 5654 1 5655 : 5656 getDeepFolderCount((options.depth === 'tree')? tree : currentFolder), 5657 foldersLoaded = 0, 5658 completed = false, 5659 needsDeepData = options.depth === 'tree' && tree.hasOwnProperty(J.FOLDERS) || 5660 options.depth === 'subfolders' && currentFolder.hasOwnProperty(J.FOLDERS) && currentFolder[J.LEVEL] < 3, 5661 5662 _searchItem = function(o, cat) { 5663 5664 let found = new Array(termsArr.length).fill(false), 5665 5666 foundAny = () => found.some(el => !!el), 5667 5668 foundAll = () => found.every(el => !!el), 5669 5670 //hasFound = () => conjunctive? allTrue() : anyTrue(), 5671 5672 // Finds 5673 find = function(term, s, div) { 5674 5675 s = s.replaceAll('"', ''); 5676 5677 if (!options.caseSensitive) { 5678 s = s.toLowerCase(); 5679 } 5680 5681 if (div) { 5682 // Divisible field 5683 if (!hasSeparator) { 5684 s = s.replace(splitRegExp, ' '); 5685 } 5686 return (' ' + s).includes(' ' + term); 5687 } 5688 5689 if (exact) { 5690 // Exact search 5691 return term === s; 5692 } 5693 5694 // Not exact but must start with the search string 5695 return s.startsWith(term); 5696 }, 5697 5698 // Search for a single term 5699 searchTerm = function(term, s, div) { 5700 5701 if (!Array.isArray(s)) { 5702 // The value is single 5703 if (exact) { 5704 s = s.toLowerCase(); 5705 if (div) { 5706 return s.slice(0, term.length) === term || s.indexOf(' ' + term) !== -1; 5707 } 5708 return s === term; 5709 } 5710 5711 return find(term, s, div); 5712 } 5713 5714 // The value is an Array 5715 let m = false; 5716 5717 for (let i = 0; i < s.length; i++) { 5718 if (find(term, s[i], div)) { 5719 m = true; 5720 } 5721 } 5722 5723 return m; 5724 }, 5725 5726 // Search for terms: single or multiple terms 5727 searchTerms = function(terms, s, div) { 5728 let m = false; 5729 5730 if (exact && s.includes(';')) { 5731 // Treat like an array 5732 s = s.split(';'); 5733 } 5734 5735 if (Array.isArray(terms)) { 5736 // Multi term search 5737 for (let i = 0; i < terms.length; i++) { 5738 if (searchTerm(terms[i], s, div)) { 5739 m = found[i] = true; 5740 if (!conjunctive) { 5741 return true; 5742 } 5743 } 5744 } 5745 //log(o[J.NAME] + ' searchTerms("' + terms + '", "' + s + '", ' + exact + ') ==> ' + m); 5746 } else { 5747 // One term search 5748 if (searchTerm(terms, s, div)) { 5749 m = found[0] = true; 5750 } 5751 } 5752 5753 return m; 5754 }; 5755 5756 for (let i = 0, f, p; i < fieldslength; i++) { 5757 5758 // Category specific field? 5759 if (fields[i].length > 1) { 5760 // e.g. "folder:title" 5761 if (fields[i][0] !== cat) { 5762 // Skip this type 5763 continue; 5764 } 5765 f = fields[i][1]; 5766 } else { 5767 f = fields[i][0]; 5768 } 5769 5770 if (JCAMERAFIELDS.indexOf(f) >= 0 && o.hasOwnProperty(J.CAMERA)) { 5771 // Camera data 5772 p = o[J.CAMERA][f]; 5773 if (typeof p === UNDEF) { 5774 // not found: retry on plain attribute, e.g. "aperture" 5775 p = o[f]; 5776 } 5777 5778 } else if (f === J.NAME) { 5779 // File name 5780 if (o.hasOwnProperty(J.ORIGINAL)) { 5781 // Has original: use that's file name 5782 p = decodeURIComponent(o[J.ORIGINAL][J.PATH].getFile()); 5783 } else { 5784 // Slide image name 5785 p = o[J.NAME]; 5786 } 5787 // Searching for the whole name and parts broken by word boundaries 5788 p = p + ' ' + p.replace(/[\.\-_]/g, ' '); 5789 5790 } else if (f === J.REGIONS) { 5791 // Regions 5792 if (o.hasOwnProperty(J.REGIONS)) { 5793 // Get names 5794 p = [];
5795 JSON.parse(o[J.REGIONS]).forEach(a => p.push(a.split(';')[0] + '')); 5796 p = p.filter(Boolean); 5797 } else { 5798 p = null; 5799 } 5800 } else { 5801 // All other fields 5802 p = o[f]; 5803 } 5804 5805 // Has such a property? 5806 if (typeof p !== UNDEF && p !== null && p.length) { 5807 if (Array.isArray(p) && !divisible[i]) { 5808 // The property is an Array and undivisible 5809 for (let j = 0; j < p.length; j++) { 5810 if (searchTerms((conjunctive || !exact)? termsArr : terms, p[j], divisible[i])) { 5811 if (!conjunctive) { 5812 // Not conjunctive: return on the first found 5813 return true; 5814 } 5815 } 5816 } 5817 5818 } else { 5819 // Not exact or not array 5820 if (f === J.COMMENT || f.endsWith('Caption')) { 5821 // Strip HTML on fields might contain it 5822 p = p.stripHTML(); 5823 } else if (Array.isArray(p)) { 5824 // Array 5825 p = p.join(' '); 5826 } else { 5827 // Otherwise ensure it's a string 5828 p = p + ''; 5829 } 5830 5831 if (searchTerms((conjunctive || !exact)? termsArr : terms, p, divisible[i])) { 5832 if (!conjunctive) { 5833 // Found one: don't care about other fields 5834 return true; 5835 } 5836 } 5837 } 5838 } 5839 } 5840 5841 // all terms found (conjunctive) | any term found otherwise 5842 return conjunctive? foundAll() : foundAny(); 5843 }, 5844 5845 _searchFolder = function(folder) { 5846 /* 5847 if (typeof DEBUG !== UNDEF && DEBUG) { 5848 log('Searching folder "' + folder[J.NAME] + '" ' + (folder[J.OBJECTS]? folder[J.OBJECTS].length : 0) + ' items'); 5849 } 5850 */ 5851 if (!folder) { 5852 return; 5853 } 5854 if (folder.hasOwnProperty(J.OBJECTS)) { 5855 // Objects 5856 folder[J.OBJECTS].forEach(o => { 5857 if (o.hasOwnProperty(J.FOLDERINDEX)) { 5858 // it's just a reference to Folders[] (when using data1.json) 5859 if (allTypes || types['folder']) { 5860 if (_searchItem(folder[J.FOLDERS][o[J.FOLDERINDEX]], 'folder')) { 5861 items.push(o); 5862 } 5863 } 5864 } else { 5865 // Normal tree 5866 if (o.hasOwnProperty(J.CATEGORY) && (allTypes || types[o[J.CATEGORY]])) { 5867 if (_searchItem(o, o[J.CATEGORY])) { 5868 items.push(o); 5869 } 5870 } 5871 } 5872 }); 5873 } 5874 5875 foldersLoaded++; 5876 }, 5877 5878 _addFolder = function(folder) { 5879 // Adding one folder 5880 5881 if (!folder || (folder.hasOwnProperty(J.HIDDEN) && folder.hidden)) { 5882 return; 5883 } 5884 5885 // Adding objects of one folder 5886 if (folder.hasOwnProperty(J.OBJECTS)) { 5887 // Already loaded 5888 _searchFolder(folder); 5889 } else if (!folder[J.READY]) { 5890 // Wait to be loaded 5891 onFolderReady(folder, _searchFolder); 5892 //loadFolder(folder); 5893 } 5894 5895 if (isDeep()) { 5896 if (folder.hasOwnProperty(J.OBJECTS)) { 5897 folder[J.OBJECTS].forEach(o => { 5898 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5899 _addFolder(o); 5900 } 5901 }); 5902 } 5903 } else if (options.depth !== 'current' && folder.hasOwnProperty(J.FOLDERS)) { 5904 folder[J.FOLDERS].forEach(o => { 5905 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 5906 _addFolder(o); 5907 } 5908 }); 5909 } 5910 }, 5911 5912 _readyDeep = function() { 5913 5914 _addFolder((options.depth === 'tree')? tree : currentFolder); 5915 setTimeout(_finished, 20); 5916 }, 5917 5918 _loadAll = function() { 5919 5920 // Deep data already loaded or data1.json is enough 5921 _addFolder((options.depth === 'tree')? tree : currentFolder); 5922 5923 // Allow some time to add the first defer 5924 setTimeout(function() { 5925 if (defer.length) { 5926 $.when.apply($, defer).done(_finished); 5927 } else { 5928 _finished(); 5929 } 5930 }, 20); 5931 }, 5932 5933 _finished = function() { 5934 5935 if (completed) { 5936 return; 5937 } 5938 5939 //console.log('_finished: toLoad=' + foldersToLoad + ' loaded=' + foldersLoaded); 5940 if (defer.length && foldersToLoad > foldersLoaded) { 5941 // Premature call for finished 5942 setTimeout(_finished, 20); 5943 return; 5944 } 5945 5946 if (options.max && options.max < items.length) { 5947 items = items.slice(0, options.max); 5948 } 5949 5950 completed = true; 5951 options.ready.call(items, options); 5952 }; 5953 5954 // Starting a new promise collect 5955 defer = []; 5956 completed = false; 5957 terms = options.terms.trim(); 5958 5959 if (!options.caseSensitive) { 5960 terms = terms.toLowerCase(); 5961 } 5962 5963 if (options.terms[0] === '"' && options.terms[options.terms.length - 1] === '"') { 5964 // Exact search with quotes: "something exact" 5965 terms = terms.slice(1, -1).trim(); 5966 termsArr = [ terms ]; 5967 exact = !opt.hasOwnProperty('exact')? true : opt.exact; 5968 5969 } else { 5970 // Any word or conjunctive 5971 let lct = terms.toLowerCase(); 5972 5973 if (andRegExp.test(lct)) { 5974 // Conjunctive search 5975 termsArr = terms.split(andRegExp); 5976 terms = terms.replace(andRegExp, ' '); 5977 conjunctive = true; 5978 } else { 5979 // Normal search 5980 termsArr = terms.split(' '); 5981 conjunctive = false; 5982 } 5983 // Removing empty elements 5984 termsArr = termsArr.filter(Boolean); 5985 splitRegExp = /^\d[\d\-\/\.\:]+\d?$/.test(terms) ? // RegExp that works as word delimiter 5986 /[,_|\n\t_]+/g : // Search string is date-like 5987 /[.,_\-|:/\n\t]+/g; // Search string is not date 5988 exact = opt.hasOwnProperty('exact')? opt.exact : false; 5989 } 5990 5991 hasSeparator = /[-._/]/.test(terms); 5992 5993 // Determining divisible fields 5994 for (let i = 0, f, d = options.divisible.split(','); i < fieldslength; i++) { 5995 fields[i] = fields[i].split(':'); 5996 divisible[i] = d.indexOf(fields[i][1] || fields[i][0]) >= 0; 5997 } 5998 5999 if (!allTypes) { 6000 if (options.types.charAt(0) === '-') { 6001 // Negative
6002 settings.possibleTypes.forEach(t => { 6003 if (options.types.indexOf(t) === -1) { 6004 types[t] = true; 6005 } 6006 }); 6007 } else { 6008 // Positive 6009 options.types.split(/,\s?/).forEach(t => { types[t] = true; }); 6010 } 6011 } 6012 6013 if (needsDeepData) { 6014 6015 if (!deepReady) { 6016 // Loading deep data, falling back to recursive data1.json on error 6017 loadDeep(_readyDeep, _loadAll); 6018 } else { 6019 _readyDeep(); 6020 } 6021 6022 } else { 6023 6024 _loadAll(); 6025 } 6026 6027 }, 6028 6029 /*************************************************************** 6030 * 6031 * collectTags: Collecting tags for Tag cloud 6032 * fields: fields to watch 6033 * types: 'all' or comma separated list of allowed types ('image' | 'audio' | 'video' | ...) 6034 * depth: where to collect ('tree' | 'current' | 'subfolders') 6035 * divisible: those fields that can be splitted around spaces or dashes 6036 */ 6037 6038 collectTags = function(opt) { 6039 6040 //log('collectTags(' + JSON.stringify(opt) + ')'); 6041 if (typeof opt === UNDEF || typeof opt['ready'] !== FUNCTION) { 6042 return; 6043 } 6044 6045 let options = $.extend({ 6046 fields: 'creator,keywords,folder:title,webLocation:title,regions', 6047 types: 'all', 6048 depth: 'current', // 'tree' | 'current' | 'subfolders' 6049 minLength: 1, 6050 divisible: 'comment,title' 6051 }, opt), 6052 tags = [], 6053 fields = Array.isArray(options.fields)? options.fields : options.fields.split(/,\s?/), 6054 fieldslength = fields.length, 6055 divisible = new Array(fieldslength), 6056 allTypes = options.types === 'all', 6057 types = {}, 6058 //exact = {}, 6059 6060 // Add tags collected from an item 6061 // tags = [ 'tag', paths[], 'TAG' ] 6062 6063 _addTags = function(o, newTags) { 6064 let nt = newTags.split('^').filter(t => t.length >= options.minLength) 6065 6066 for (let i = 0, idx, tag, p; i < nt.length; i++) { 6067 6068 tag = nt[i].toUpperCase(); 6069 p = getObjectPath(o); 6070 6071 if (p !== null) { 6072 if (!tags || !tags.length) { 6073 // Empty 6074 tags = [[ nt[i], [ p ], tag ]]; 6075 } else { 6076 // Does this tag exist? 6077 if ((idx = tags.findIndex(t => t[2] === tag)) >= 0) { 6078 // Yes: add path 6079 if (tags[idx][1].indexOf(p) === -1) { 6080 // This items has been added already? 6081 tags[idx][1].push(p); 6082 } 6083 } else { 6084 // No: add new 6085 tags.push([ nt[i], [ p ], tag ]); 6086 } 6087 } 6088 } 6089 } 6090 }, 6091 6092 // Collects tags from an item 6093 6094 _collectTags = function(o, cat) { 6095 let ctags = '^', // Collected tags = 'tag1^tag2^tag3' 6096 ctagsuc = '^', // Same in uppercase for comparison 6097 6098 add = function(tag, field, divisible) { 6099 6100 if (!tag) { 6101 return; 6102 } 6103 6104 let t, 6105 ta; 6106 6107 if (divisible) { 6108 if (field === 'comment' || field.endsWith('Caption')) { 6109 tag = tag.stripHTML(); 6110 } 6111 //ta = tag.split(/\W+/); 6112 ta = tag.split(/[\s,_\.\?\!\-\(\)\[\]]/); 6113 ta = removeEmpty(ta); 6114 } else { 6115 ta = tag.toString().split(';'); 6116 } 6117 6118 for (let i = 0, l = ta.length, fnd = false; i < l; i++) { 6119 6120 t = ta[i].trim(); 6121 6122 if (t.length < options.minLength || 6123 t.length === 1 && '!@#$%^&*()-_=+[{]};:\'",<.>/?\\|'.indexOf(t.charCodeAt(0)) !== -1) { 6124 // Empty, too short, or special char 6125 continue; 6126 } 6127 6128 if (ctagsuc.indexOf('^' + t.toUpperCase() + '^') === -1) { 6129 ctags += t + '^'; 6130 ctagsuc += t.toUpperCase() + '^'; 6131 } 6132 } 6133 }; 6134 6135 for (let i = 0, f, p; i < fieldslength; i++) { 6136 6137 if (fields[i].length > 1) { 6138 if (fields[i][0] !== cat) { 6139 continue; 6140 } 6141 f = fields[i][1]; 6142 } else { 6143 f = fields[i][0]; 6144 } 6145 6146 6147 if (JCAMERAFIELDS.indexOf(f) >= 0 && o.hasOwnProperty(J.CAMERA)) { 6148 // camera data 6149 p = o[J.CAMERA][f]; 6150 if (typeof p === UNDEF) { 6151 p = o[f]; 6152 } 6153 } else if (f === J.REGIONS) { 6154 // Regions 6155 p = o.hasOwnProperty(J.REGIONS)? JSON.parse(o[J.REGIONS]) : null; 6156 6157 } else { 6158 p = o[f]; 6159 } 6160 6161 if (typeof p !== UNDEF && p != null) { 6162 //log(o['name'] + '[' + f + '] = ' + o[f] + ' (' + (Array.isArray(o[f])? 'array':(typeof o[f])) + ')'); 6163 if (f === J.REGIONS) { 6164 // Checking for the names only 6165 for (let j = 0; j < p.length; j++) { 6166 add(p[j].split(';')[0], f, divisible[i]); 6167 } 6168 } else if (Array.isArray(p)) { 6169 // Array type field 6170 for (let j = 0; j < p.length; j++) { 6171 add(p[j], f, divisible[i]); 6172 } 6173 } else { 6174 add(p, f, divisible[i]); 6175 } 6176 } 6177 } 6178 6179 //log(ctags); 6180 if (ctags.length > 1) { 6181 _addTags(o, ctags); 6182 } 6183 }, 6184 6185 // Collect tags from all objects in a folder 6186 6187 _addItems = function(folder) { 6188 6189 // Adds fields from objects array 6190 if (!folder) { 6191 return; 6192 } 6193 6194 if (folder !== tree && (allTypes || types['folder'])) { 6195 // Current folder 6196 _collectTags(folder, 'folder'); 6197 } 6198 6199 if (folder.hasOwnProperty(J.OBJECTS)) { 6200 // Ordinary objects 6201 for (let i = 0, o = folder[J.OBJECTS], cat; i < o.length; i++) { 6202 if (o[i].hasOwnProperty(J.CATEGORY)) { 6203 cat = o[i][J.CATEGORY]; 6204 if (allTypes || types[cat]) { 6205 _collectTags(o[i], cat); 6206 } 6207 } 6208 } 6209 } 6210 }, 6211 6212 // Queues one folder to collect tags 6213 6214 _addFolder = function(folder) { 6215 // Adding one folder 6216 6217 if (!folder || (folder.hasOwnProperty(J.HIDDEN) && folder.hidden)) { 6218 return; 6219 } 6220 6221 onFolderReady(folder, _addItems); 6222 6223 if (isDeep()) { 6224 if (folder.hasOwnProperty(J.OBJECTS)) {
6225 folder[J.OBJECTS].forEach(o => { 6226 if (o[J.CATEGORY] === 'folder' && !(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 6227 _addFolder(o); 6228 } 6229 }); 6230 } 6231 } else if (options.depth !== 'current' && folder.hasOwnProperty(J.FOLDERS)) { 6232 folder[J.FOLDERS].forEach(o => { 6233 if (!(o.hasOwnProperty(J.HIDDEN) && o[J.HIDDEN]) && !o.hasOwnProperty(J.VIEW)) { 6234 _addFolder(o); 6235 } 6236 }); 6237 } 6238 }, 6239 6240 // Arrange the tags when ready 6241 6242 _finally = function() { 6243 // Sort 6244 if (options.sort === 'name') { 6245 tags.sort((a, b) => ('' + a[2]).localeCompare('' + b[2])); 6246 } else if (options.sort === 'frequency') { 6247 tags.sort((a, b) => (b[1].length - a[1].length)); 6248 } 6249 // Max. 6250 if (options.max && options.max < tags.length) { 6251 tags = tags.slice(0, options.max); 6252 } 6253 // Done functions 6254 options.ready.call(tags, options); 6255 }; 6256 6257 // Starting a new promise collect 6258 6259 defer = []; 6260 6261 // Determining exact/divisible fields 6262 6263 for (let i = 0, f, d = options.divisible.split(','); i < fieldslength; i++) { 6264 fields[i] = fields[i].split(':'); 6265 divisible[i] = d.indexOf(fields[i][1] || fields[i][0]) !== -1; 6266 } 6267 6268 // Creating object types array too look for 6269 6270 if (!allTypes) { 6271 if (options.types.charAt(0) === '-') { 6272 // Negative 6273 settings.possibleTypes.forEach(t => { 6274 if (options.types.indexOf(t) === -1) { 6275 types[t] = true; 6276 } 6277 }); 6278 } else { 6279 // Positive 6280 options.types.split(/,\s?/).forEach(t => { types[t] = true; }); 6281 } 6282 } 6283 6284 // Adding folder(s) 6285 6286 _addFolder((options.depth === 'tree')? tree : currentFolder); 6287 6288 if (defer.length) { 6289 $.when.apply($, defer).done(_finally); 6290 } else { 6291 _finally(); 6292 } 6293 6294 }, 6295 6296 /* 6297 * Getting one key from an object in a processed form, e.g. fileLabel variables are handled 6298 */ 6299 6300 getKey = function(o, k) { 6301 6302 if (typeof o !== UNDEF && typeof k === STRING) { 6303 if (k === 'label') { 6304 return getLabel(o); 6305 } else if (k === 'fileLabel') { 6306 return o.hasOwnProperty(J.TITLE)? o[J.TITLE] : getLabel(o); 6307 } else if (k === 'originalTime' && 6308 o.hasOwnProperty(J.DATES) && 6309 o[J.DATES].hasOwnProperty(J.DATETAKEN) 6310 ) { 6311 return new Date(o[J.DATES][J.DATETAKEN] * 1000).toLocaleString(settings.locale, {month: '2-digit', day: '2-digit', hour12: false, hour: '2-digit', minute:'2-digit'}); 6312 } else if (o.hasOwnProperty(k)) { 6313 return o[k]; 6314 } 6315 } 6316 6317 return ''; 6318 }, 6319 6320 /*************************************************************** 6321 * 6322 * processTemplate: Processing template for an object 6323 * template: string with variables in format ${name} or in ${var1|var2} fallback format 6324 * co: current object 6325 * removeEmpty: removing empty tags around missing/empty variables 6326 */ 6327 6328 processTemplate = function(template, co, removeEmpty) { 6329 6330 let remove = (typeof removeEmpty !== UNDEF)? removeEmpty : false, 6331 o = co || currentFolder, 6332 i0, 6333 i1, 6334 m, 6335 v; 6336 6337 if (template && template.indexOf('${') >= 0) { 6338 6339 while (m = template.match(/\$\{([\w\.|]+)\}/)) { 6340 if (m[1].indexOf('|') > 0) { 6341 // ${var1|var2} fallback format 6342 for (let i = 0, k = m[1].split('|'); i < k.length; i++) { 6343 if (v = getKey(o, k[i])) { 6344 // Found 6345 break; 6346 } 6347 } 6348 } else { 6349 // Single variable 6350 v = getKey(o, m[1]); 6351 } 6352 6353 if (v === null && remove) { 6354 // Remove empty HTML tags 6355 i0 = m.index - 1; 6356 i1 = i0 + m[0].length; 6357 6358 if (i0 > 0 && template[i0] === '>' && i1 < (template.length - 1) && template[i1] === '<') { 6359 6360 i0 = template.lastIndexOf('<', i0); 6361 i1 = template.indexOf('>', i1);
6362 6363 if (i0 >= 0 && i1 >= 0) { 6364 template = template.slice(0, i0) + template.slice(i1); 6365 continue; 6366 } 6367 } 6368 } 6369 // Replacing or removing variable 6370 template = template.slice(0, m.index) + (v || '') + template.slice(m.index + m[0].length); 6371 } 6372 } 6373 6374 return template; 6375 }, 6376 6377 6378 /*************************************************************** 6379 * 6380 * Initial processing of the album 6381 * 6382 */ 6383 6384 _up = '../../../../../../../../../../../../../../../../../../../../', 6385 6386 relativePath = function(from, to) { 6387 if (typeof from === UNDEF || !from.length || from === '/') { 6388 // From root 6389 return to || ''; 6390 } else if (typeof to === UNDEF || !to.length || to === '/') { 6391 // To root 6392 return _up.slice(0, from.split('/').length * 3); 6393 } else if (from === to) { 6394 // Same 6395 return ''; 6396 } 6397 6398 from = from.split('/'); 6399 to = to.split('/'); 6400 6401 while (from.length && to.length && from[0] === to[0]) { 6402 from.shift(); 6403 to.shift(); 6404 } 6405 6406 return _up.slice(0, from.length * 3) + to.join('/'); 6407 }, 6408 6409 /* 6410 * Adding level, parent pointers and relative paths for the whole tree 6411 */ 6412 6413 addExtras = function() { 6414 let addFolder = function(f, level, path, parentRef) { 6415 let fp = level? makePath(path, f[J.PATH]) : '', // folder path: subfolder/subfolder/ 6416 pr = level? getPathRef(fp) : 0, // path reference number 6417 rp = (albumPath === null)? getRelPathRef(relativePath(settings.relPath, fp)) : -1; // Relative path 6418 6419 // Enhancing folders with extra variables 6420 6421 // Level 6422 f[J.LEVEL] = level; 6423 6424 // Folder path reference from root 6425 f[J.PATHREF] = pr; 6426 6427 // Relative path from currentFolder to this folder 6428 if (albumPath === null) { 6429 // if we're inside the album (providing simpler paths) 6430 f[J.RELPATH] = rp; 6431 } 6432 6433 // Check for missing Category 6434 if (!f.hasOwnProperty(J.CATEGORY)) { 6435 f[J.CATEGORY] = 'folder'; 6436 } 6437 6438 // Parent reference 6439 if (level) { 6440 f[J.PARENTREF] = parentRef; 6441 } 6442 6443 // Fixing tree.json anomaly: 6444 // thumb path is different in deep-data.json and data1.json 6445 if (f[J.THUMB][J.PATH].startsWith(f[J.PATH] + '/' + settings.thumbsDir)) { 6446 f[J.THUMB][J.PATH] = f[J.THUMB][J.PATH].slice(f[J.PATH].length + 1); 6447 } 6448 6449 // Recursive to subfolders 6450 if (settings.loadDeep && f.hasOwnProperty(J.OBJECTS)) { 6451 // Adding path references in case of deep-data.json 6452 f[J.OBJECTS].filter(o => o[J.CATEGORY] !== 'folder').forEach((o) => { 6453 o[J.PATHREF] = pr; 6454 if (albumPath === null) { 6455 o[J.RELPATH] = rp; 6456 } 6457 });
6458 6459 // Collecting folders' indexes into FOLDERS array for easier access later 6460 let idx = 0; 6461 f[J.OBJECTS].filter(o => o[J.CATEGORY] === 'folder').forEach(o => { 6462 if (!idx) { 6463 f[J.FOLDERS] = []; 6464 } 6465 f[J.FOLDERS].push(idx++); 6466 // Recursive call for subfolders 6467 addFolder(o, level + 1, fp, pr); 6468 }); 6469 } else { 6470 // Iterating over folders 6471 if (f.hasOwnProperty(J.FOLDERS)) { 6472 f[J.FOLDERS].forEach(o => addFolder(o, level + 1, fp, pr)); 6473 } 6474 } 6475 }; 6476 6477 addFolder(tree, 0, '', 0); 6478 }, 6479 6480 /* 6481 * callOnReady: Calling cached onready functions and removing them 6482 */ 6483 6484 callOnReady = function(folder) { 6485 // firing all cached onFolderReady functions 6486 //log('Firing ' + folder[J.ONREADY].length + ' cached onReady function(s) in folder "' + folder[J.NAME] + '"'); 6487 if (folder.hasOwnProperty(J.ONREADY) && Array.isArray(folder[J.ONREADY])) { 6488 folder[J.ONREADY].forEach(rfn => { 6489 if (rfn.length > 1) { 6490 // With arguments 6491 rfn[0](folder, rfn[1]); 6492 } else { 6493 // No arguments 6494 rfn[0](folder); 6495 } 6496 }); 6497 folder[J.ONREADY] = []; 6498 } 6499 }, 6500 6501 /* 6502 * addObjects: Adding Objects array to tree when using data1.json 6503 * executed after the folder's data file has loaded 6504 * d = JSON data loaded 6505 * folder = target folder 6506 */ 6507 6508 addObjects = function(folder, d) { 6509 let j = 0, // Counting folders 6510 f; 6511 6512 // Ensure it exists 6513 folder[J.OBJECTS] = []; 6514 6515 //log('Adding objects in folder "' + (folder[J.NAME] || folder[J.PATH]) + '"'); 6516 6517 d[J.OBJECTS].forEach(o => { 6518 // Counting loaded object categories 6519 tree[J.LOADCOUNTER][o[J.CATEGORY]]++; 6520 tree[J.LOADCOUNTER][J.TOTAL]++; 6521 6522 if (o[J.CATEGORY] === 'folder') { 6523 // Folder 6524 // Proper way: 6525 //o = { folderIndex: folder[J.FOLDERS].findIndex(f => o[J.PATH] === f[J.PATH]) }; 6526 // Heuristic: (provided tree.json has the same folder ordering as data1.json) 6527 if (j < folder[J.FOLDERS].length) { 6528 6529 f = folder[J.FOLDERS][j]; 6530 6531 // Copying missing folder variables but OBJECTS and ALBUM 6532 for (let prop in o) { 6533 if (prop !== J.OBJECTS && prop !== J.ALBUM && !f.hasOwnProperty(prop)) { 6534 f[prop] = o[prop]; 6535 } 6536 } 6537 6538 // J.FOLDERS array already holds the full info, storing only a pointer 6539 o = {}; 6540 o[J.FOLDERINDEX] = j++; 6541 6542 } else { 6543 6544 log('Database error: Folder count inconsistency in folder "' + folder[J.NAME] + '". tree.json <> data1.json'); 6545 } 6546 6547 } else { 6548 // Not a folder 6549 // Adding absolute and relative paths 6550 o[J.PATHREF] = folder[J.PATHREF]; 6551 o[J.RELPATH] = folder[J.RELPATH]; 6552 } 6553 // Storing object in the tree 6554 folder[J.OBJECTS].push(o); 6555 }); 6556 6557 folder[J.READY] = true; 6558 6559 callOnReady(folder); 6560 }, 6561 6562 /* 6563 * onFolderReady: Checks if folder data has been loaded 6564 * if loaded executes the function immediately 6565 * if not then saves the function for later execution 6566 * fn - is the folder to be called on ready 6567 * params - the parameters array (optional) 6568 */ 6569 6570 onFolderReady = function(folder, fn, params) { 6571 6572 if (folder && typeof fn === FUNCTION && !folder.hasOwnProperty(J.INTERNALLINK)) { 6573 6574 if (deepReady || (folder.hasOwnProperty(J.READY) && folder[J.READY])) { 6575 // Already loaded: fire immediately 6576 if (typeof params !== UNDEF) { 6577 fn(folder, params); 6578 } else { 6579 fn(folder); 6580 } 6581 } else { 6582 // Need to cache 6583 //log('Caching onReady function for folder "' + folder[J.NAME] + '" ' + (folder.hasOwnProperty(J.ONREADY)? ('[' + (folder[J.ONREADY].length + 1) + ']') : '(onReady array is missing)')); 6584 if (!folder.hasOwnProperty(J.ONREADY)) { 6585 // Loading hasn't been initiated, let's load it! 6586 loadFolder(folder); 6587 } 6588 6589 if (typeof params !== UNDEF) { 6590 folder[J.ONREADY].push([ fn, Array.isArray(params)? params : [ params ] ]); 6591 } else { 6592 folder[J.ONREADY].push([ fn ]); 6593 } 6594 } 6595 } 6596 }, 6597 6598 /* 6599 * loadFolder: Loading data for a single folder, optionally recursively 6600 */ 6601 6602 loadFolder = function(folder, deep) { 6603 6604 //log('LoadFolder("' + folder[J.NAME] + '")'); 6605 6606 // Don't bother with lkinked folders 6607 if (folder.hasOwnProperty(J.INTERNALLINK)) { 6608 return; 6609 } 6610 6611 if (!folder.hasOwnProperty(J.ONREADY)) { 6612 // Creating an empty array for onready functions, also signaling that the load has been initiated 6613 folder[J.ONREADY] = []; 6614 } 6615 6616 if (!folder.hasOwnProperty(J.OBJECTS) || !folder[J.OBJECTS].length) { 6617 // we need to load data1.json 6618 let src = makePath(getPath(folder), settings.dataFile) + cacheBuster; 6619 6620 // building defer array to be able to check the full load 6621 if (!defer) { 6622 defer = []; 6623 } 6624 //log('Loading objects for folder "' + folder[J.NAME] + '"'); 6625 // Cache buster with ?makeDate 6626 defer.push($.getJSON(src) 6627 .done(function(d) { 6628 //log('Objects loaded for folder "' + folder[J.NAME] + '"'); 6629 // Copying objects 6630 addObjects(folder, d); 6631 6632 }).fail(function(jqxhr, status, error) { 6633 log('Error: Objects could not be loaded for "' + src + '": ' + status + ', ' + error); 6634 }) 6635 ); 6636
6637 if (deep && folder.hasOwnProperty(J.FOLDERS)) { 6638 folder[J.FOLDERS].forEach(f => loadFolder(f, true)); 6639 } 6640 6641 } else { 6642 callOnReady(folder); 6643 } 6644 }, 6645 6646 /* 6647 * initCounters: Initializing tree-level counters 6648 */ 6649 6650 initCounters = function() { 6651 6652 if (tree) { 6653 // Initializing the load counters 6654 tree[J.LOADCOUNTER] = {}; 6655 tree[J.LOADCOUNTER][J.TOTAL] = 0; 6656 6657 settings.possibleTypes.forEach(t => { 6658 tree[J.LOADCOUNTER][t] = 0; 6659 }); 6660 } 6661 }, 6662 6663 /* 6664 * setCurrentFolder: Sets currentFolder variable as a shortcut 6665 */ 6666 6667 setCurrentFolder = function() { 6668 // Getting the pointer to the current folder 6669 currentFolder = getFolder(settings.relPath); 6670 6671 if (currentFolder === null) { 6672 if (typeof settings.fatalError === FUNCTION) { 6673 settings.fatalError.call(this, 'noSuchFolder', settings.relPath); 6674 } 6675 6676 return false; 6677 } 6678 6679 return true; 6680 }, 6681 6682 /* 6683 * loadNeededFolders: executed when tree.json has loaded 6684 */ 6685 6686 loadNeededFolders = function() { 6687 6688 // Collecting promises for data1.json loads 6689 defer = []; 6690 6691 // Loading current folder (+ deep folders?) 6692 loadFolder(settings.lazy? currentFolder : tree, !settings.lazy); 6693 6694 // Waiting for all requested folders be loaded 6695 $.when.apply($, defer) 6696 .done(function() { 6697 let d = new Date(); 6698 6699 if (typeof DEBUG !== UNDEF && DEBUG) { 6700 log(defer.length + ' folder(s) loaded: ' + (d - instance) + 'ms'); 6701 } 6702 6703 ready = true; 6704 defer = null; 6705 6706 if (typeof settings['ready'] === FUNCTION) { 6707 settings.ready.call(this); 6708 settings.ready = null; 6709 } else if (!tree.hasOwnProperty(J.FOLDERS)) { 6710 // Flat album: calling deep ready immediately 6711 if (typeof settings['deepReady'] === FUNCTION) { 6712 settings.deepReady.call(this); 6713 settings.deepReady = null; 6714 } 6715 } 6716 }) 6717 6718 .fail(function(jqxhr, status, error) { 6719 6720 if (typeof settings['fatalError'] === FUNCTION) { 6721 settings.fatalError.call(this, 'cantLoadDataForFolder', currentFolder[J.PATH]); 6722 } 6723 6724 }); 6725 }, 6726 6727 /* 6728 * initFolderReady: initializing ready state and ready events in a recursive manner (in case tree.json was loaded) 6729 */ 6730 6731 initFolderReady = function(folder) { 6732 6733 // Sets READY property 6734 if (!folder.hasOwnProperty(J.READY)) { 6735 folder[J.READY] = false; 6736 } 6737 6738 // Go deep recursively into subfolders 6739 if (settings.loadDeep) { 6740 if (folder.hasOwnProperty(J.OBJECTS)) { 6741 folder[J.OBJECTS].forEach(o => { if (o[J.CATEGORY] === 'folder') initFolderReady(o); }); 6742 } 6743 } else if (folder.hasOwnProperty(J.FOLDERS)) { 6744 folder[J.FOLDERS].forEach(f => initFolderReady(f)); 6745 } 6746 }, 6747 6748 /* 6749 * loadTree: Loading tree.json from the top level folder 6750 */ 6751 6752 loadTree = function(doneFn) { 6753 6754 let ins = new Date(), 6755 src = makePath(albumPath || settings.rootPath, settings.treeFile) + cacheBuster; 6756 6757 // Set loadDeep to false whatever it was. Means tree.json is used, not deep-data.json 6758 settings.loadDeep = false; 6759 //log('loadTree("' + src + '")'); 6760 6761 // Loading tree.json 6762 return $.getJSON(src) 6763 .done(function(d) { 6764 // tree.json loaded 6765 6766 tree = d; 6767 6768 // Adding counters 6769 initCounters(); 6770 6771 // Initializing ready state and onready events in every folder 6772 initFolderReady(tree); 6773 6774 if (typeof DEBUG !== UNDEF && DEBUG) { 6775 log('Tree loaded: ' + ((new Date()) - ins) + 'ms'); 6776 ins = new Date(); 6777 } 6778 6779 if (setCurrentFolder()) { 6780 6781 // Adding extra variables 6782 addExtras(); 6783 6784 // Calling "done" function 6785 if (typeof doneFn === FUNCTION) { 6786 doneFn.call(this); 6787 } 6788 } 6789 6790 }) 6791 6792 .fail(function(jqxhr, status, error) { 6793 6794 if (typeof settings['fatalError'] === FUNCTION) { 6795 settings.fatalError.call(this, 'localAccessBlocked', src); 6796 } 6797 6798 });
6799 }, 6800 6801 /* 6802 * migrateTree: moves data from tree to the new deep-data structure 6803 * used when deep data is loaded after tree used initially 6804 */ 6805 6806 initDeep = function(d) { 6807 6808 // Set loadDeep to true whatever it was. Means deep-data.json is used, not tree.json 6809 settings.loadDeep = true; 6810 6811 if (typeof tree === UNDEF) { 6812 6813 // First load 6814 tree = d; 6815 6816 tree[J.DEEP] = true; 6817 6818 // Adding counters 6819 initCounters(); 6820 6821 // Enhancing all the folders with PATH and RELPATH variables, counting object types 6822 addExtras(); 6823 6824 } else { 6825 6826 // Migrating from tree.json 6827 let oldTree = tree, 6828 migrateFolder = function(oldFolder, newFolder) { 6829 //log('Migrating folder "' + oldFolder[J.NAME] + '"'); 6830 // Copying missing properties (e.g. addExtras) 6831 for (let prop in oldFolder) { 6832 if (prop !== J.FOLDERS && prop !== J.ALBUM && 6833 prop !== J.OBJECTS && prop !== J.READY && 6834 prop !== J.ONREADY && !newFolder.hasOwnProperty(prop)) { 6835 //log('Copying property "' + prop + '".'); 6836 newFolder[prop] = oldFolder[prop]; 6837 } 6838 } 6839 6840 // Firing ready functions attached to old folder 6841 if (oldFolder.hasOwnProperty(J.ONREADY)) { 6842 //log('Firing ' + oldFolder[J.ONREADY].length + ' ready events.'); 6843 oldFolder[J.ONREADY].forEach(rfn => (rfn.length > 1)? rfn[0](newFolder, rfn[1]) : rfn[0](newFolder)); 6844 } 6845 6846 if (newFolder.hasOwnProperty(J.OBJECTS)) { 6847 let pr = oldFolder[J.PATHREF], 6848 rp = oldFolder[J.RELPATH] || null, 6849 idx = 0; 6850 6851 //log('Adding extras to "' + oldFolder[J.NAME] + '".'); 6852 // Adding path references in case of deep-data.json 6853 // Collecting folders' indexes into FOLDERS array for easier access later 6854 newFolder[J.OBJECTS].forEach(o => { 6855 if (o[J.CATEGORY] !== 'folder') { 6856 o[J.PATHREF] = pr; 6857 if (rp) { 6858 o[J.RELPATH] = rp; 6859 } 6860 } else { 6861 if (!idx) { 6862 newFolder[J.FOLDERS] = []; 6863 } 6864 newFolder[J.FOLDERS].push(idx++); 6865 } 6866 }); 6867 6868 if (oldFolder.hasOwnProperty(J.FOLDERS)) { 6869 oldFolder[J.FOLDERS].forEach(f => { 6870 // Recursive call for subfolders 6871 const nf = newFolder[J.OBJECTS].find(o => (o[J.CATEGORY] === 'folder' && o[J.PATH] === f[J.PATH])); 6872 if (nf) { 6873 migrateFolder(f, nf); 6874 } else { 6875 log('Database inconsistency when accessing folder "' + oldFolder[J.NAME] + '"!'); 6876 } 6877 }); 6878 } 6879 } 6880 6881 }; 6882 6883 // Replacing tree with deep data 6884 tree = d; 6885 6886 // Deep structure flag 6887 tree[J.DEEP] = true; 6888 6889 // Migrating the missing properties 6890 migrateFolder(oldTree, tree); 6891 } 6892 }, 6893 6894 /* 6895 * onDeepReady: Checks if all data has been loaded 6896 * if loaded, executes the function immediately 6897 * if not, then saves the function for later execution 6898 * fn - is the folder to be called on ready 6899 * params - the parameters array (optional) 6900 */ 6901 6902 onDeepReady = function(folder, fn, params) { 6903 6904 if (folder && typeof fn === FUNCTION) { 6905 6906 if (deepReady) { 6907 // Already loaded: fire immediately 6908 if (typeof params !== UNDEF) { 6909 fn(folder, params); 6910 } else { 6911 fn(folder); 6912 } 6913 } else { 6914 // Need to cache 6915 //log('Caching onReady function for folder "' + folder[J.NAME] + '" ' + (folder.hasOwnProperty(J.ONREADY)? ('[' + (folder[J.ONREADY].length + 1) + ']') : '(onReady array is missing)')); 6916 6917 if (typeof params !== UNDEF) { 6918 deepReadyFunctions.push([ folder, fn, Array.isArray(params)? params : [ params ] ]); 6919 } else { 6920 deepReadyFunctions.push([ folder, fn ]); 6921 } 6922 } 6923 } 6924 }, 6925 6926 /*
6927 * loadDeep: Loads deep data structure 6928 */ 6929 6930 loadDeep = function(doneFn, failFn) { 6931 6932 if (deepReady) { 6933 // Already loaded 6934 //log('loadDeep(): already in "deepReady" state'); 6935 doneFn.call(tree); 6936 return; 6937 } 6938 6939 let ins = new Date(), 6940 src = makePath(albumPath || settings.rootPath, settings.deepDataFile) + cacheBuster; 6941 6942 //log('loadDeep("' + src + '")'); 6943 6944 // Loading deep-data.json 6945 return $.getJSON(src) 6946 .done(function(d) { 6947 // deep-data.json loaded 6948 6949 if (typeof DEBUG !== UNDEF && DEBUG) { 6950 log('Deep data loaded: ' + ((new Date()) - ins) + 'ms'); 6951 ins = new Date(); 6952 } 6953 6954 initDeep(d); 6955 6956 // Setting current folder variable 6957 if (setCurrentFolder()) { 6958 6959 deepReady = true; 6960 /* 6961 if (typeof DEBUG !== UNDEF && DEBUG) { 6962 log('Deep data objects are ready: ' + ((new Date()) - ins) + 'ms' + ' total: ' + tree[J.LOADCOUNTER][J.TOTAL] + ' objects'); 6963 } 6964 */ 6965 if (typeof doneFn === FUNCTION) { 6966 doneFn.call(this); 6967 } else { 6968 // Calling folder ready function 6969 if (typeof settings['ready'] === FUNCTION) { 6970 settings.ready.call(this); 6971 settings.ready = null; 6972 } 6973 6974 // Calling deep ready function 6975 if (typeof settings['deepReady'] === FUNCTION) { 6976 settings.deepReady.call(this); 6977 settings.deepReady = null; 6978 } 6979 } 6980 6981 if (deepReadyFunctions.length) { 6982 // [ folder, readyFn, arguments ] 6983 deepReadyFunctions.forEach(rfn => (rfn.length > 2)? rfn[1](rfn[0], rfn[2]) : rfn[1](rfn[0])); 6984 deepReadyFunctions = []; 6985 } 6986 } 6987 6988 }) 6989 .fail(function(jqxhr, status, error) { 6990 6991 if (typeof settings['fatalError'] === FUNCTION) { 6992 settings.fatalError.call(this, 'databaseAccessDenied', src); 6993 } 6994 6995 if (typeof failFn === FUNCTION) { 6996 failFn.call(this); 6997 } 6998 6999 }); 7000 }, 7001 7002 /*************************************************************** 7003 * 7004 * Initializing 7005 * 7006 */ 7007 7008 init = opt => { 7009 7010 if (instance) { 7011 // Already initialized 7012 return instance; 7013 } 7014 7015 // Unique ID 7016 instance = new Date(); 7017 7018 if (typeof opt !== UNDEF) { 7019 // Passed options 7020 $.extend(settings, opt); 7021 } 7022 7023 // Resetting ready state 7024 ready = deepReady = false; 7025 7026 // using albumPath: calling Album from outside 7027 if (settings.hasOwnProperty('albumPath')) { 7028 7029 // Initializing by absolute URL 7030 albumPath = settings.albumPath; 7031 7032 // Sanitizing URL 7033 if (albumPath.slice(-1) !== '/') { 7034 albumPath += '/'; 7035 } 7036 } 7037 7038 // Assigning absolute path 7039 absolutePath = getAbsoluteFolderPath(albumPath || settings.rootPath); 7040 7041 // Using makeDate to get rid of the old version of JSON files 7042 if (settings.hasOwnProperty('makeDate')) { 7043 cacheBuster = '?' + settings.makeDate; 7044 } 7045 7046 // Loading album's JSON file 7047 if (settings.loadDeep || settings.hasOwnProperty('deepReady')) { 7048 // Loading deep-data.json 7049 loadDeep(); 7050 } else { 7051 // Loading tree.jsonm when ready loading the current folder 7052 loadTree(loadNeededFolders); 7053 } 7054 7055 }; 7056 7057 7058 if (options) { 7059 7060 if (typeof DEBUG !== UNDEF && DEBUG) { 7061 log('new Album(' + JSON.stringify(options) + ');'); 7062 } 7063 7064 init(options); 7065 } 7066 7067 // Exposing internal functions 7068 7069 return { 7070 //init: init, 7071 //isReady: isReady, 7072 //isDeepReady: isDeepReady, 7073 7074 // Debug 7075 //getTree: getTree, 7076 //getPaths: getPaths, 7077 7078 // Type checking 7079 isImage: isImage, 7080 isAudio: isAudio, 7081 isVideo: isVideo, 7082 isPage: isPage, 7083 isFolder: isFolder, 7084 isView: isView, 7085 isLightboxable: isLightboxable, 7086 isCurrentFolder: isCurrentFolder, 7087 7088 // Access 7089 getAlbumPath: getAlbumPath, 7090 getAlbumRootPath: getAlbumRootPath, 7091 getPath: getPath, 7092 getAbsolutePath: getAbsolutePath, 7093 getItemPath: getItemPath, 7094 getAudioClipPath: getAudioClipPath, 7095 getDimensions: getDimensions, 7096 getOriginalDimensions: getOriginalDimensions, 7097 getMaxDimensions: getMaxDimensions, 7098 getAspectRatio: getAspectRatio, 7099 getRegions: getRegions, 7100 getLink: getLink, 7101 getRootPath: getRootPath, 7102 getFolderPath: getFolderPath, 7103 getRelativeFolderPath: getRelativeFolderPath, 7104 getFolder: getFolder, 7105 getCurrentFolder: getCurrentFolder, 7106 getParent: getParent, 7107 getItem: getItem, 7108 getObjects: getObjects, 7109 getImages: getImages, 7110 getFolders: getFolders, 7111 getFoldersAndWeblocs: getFoldersAndWeblocs, 7112 getAllObjects: getAllObjects, 7113 7114 // Properties 7115 getMakeDate: getMakeDate, 7116 getAlbumTitle: getAlbumTitle, 7117 getItemName: getItemName, 7118 getExtension: getExtension, 7119 getLevel: getLevel, 7120 getTitle: getTitle, 7121 getName: getName, 7122 getLabel: getLabel, 7123 getAlt: getAlt, 7124 getComment: getComment, 7125 getThumbPath: getThumbPath, 7126 getIconPath: getIconPath, 7127 getImagePath: getImagePath, 7128 getAbsoluteImagePath: getAbsoluteImagePath, 7129 getThemeImagePath: getThemeImagePath, 7130 getOriginalPath: getOriginalPath, 7131 getOriginalPageName: getOriginalPageName, 7132 getVideoPath: getVideoPath, 7133 getPosterPath: getPosterPath, 7134 getOptimalImagePath: getOptimalImagePath, 7135 getOptimalThumbPath: getOptimalThumbPath, 7136 getSourcePath: getSourcePath, 7137 getAbsoluteItemPath: getAbsoluteItemPath, 7138 getVideoDuration: getVideoDuration, 7139 hasShop: hasShop, 7140 hasLocation: hasLocation, 7141 getLocation: getLocation, 7142 disableFeedback: disableFeedback, 7143 hideFotomoto: hideFotomoto, 7144 getPriceRange: getPriceRange, 7145 getCurrency: getCurrency, 7146 getDeepFolderCount: getDeepFolderCount, 7147 getCustomPageContent: getCustomPageContent, 7148 7149 // Generic property 7150 getRootProperty: getRootProperty, 7151 getInheritedPropertyObject: getInheritedPropertyObject, 7152 getInheritedProperty: getInheritedProperty, 7153 getProperty: getProperty, 7154 getPropertyObject: getPropertyObject, 7155 7156 getNextFoldersFirstImage: getNextFoldersFirstImage, 7157 getPreviousFoldersLastImage: getPreviousFoldersLastImage, 7158 7159 sortItems: sortItems, 7160 7161 // Search 7162 collectByPath: collectByPath, 7163 collectNItem: collectNItem,
7164 collectByDate: collectByDate, 7165 collectItems: collectItems, 7166 collectTags: collectTags, 7167 7168 processTemplate: processTemplate 7169 7170 }; 7171 7172}; 7173/* 7174 * cookie() :: Cookie handling - using localStorage if exists 7175 * 7176 * Copyright by Lazaworx 7177 * http://www.lazaworx.com 7178 * Author: Laszlo Molnar 7179 * 7180 * Dual licensed under the MIT and GPL licenses. 7181 * - http://www.opensource.org/licenses/mit-license.php 7182 * - http://www.gnu.org/copyleft/gpl.html 7183 * 7184 * Usage: 7185 * $.cookie( key ) :: returns cookie value or null (scoped to DIR_PATH) 7186 * $.cookie( key, null ) :: deletes cookie (scoped to DIR_PATH) 7187 * $.cookie( key, value [,exp] ) :: saves cookie, exp in seconds, default 1 hour 7188 * 7189 * $.albumCookie( key ) :: same as above, but always globally scoped (path '/') 7190 * $.albumCookie( key, null ) :: 7191 * $.albumCookie( key, value [,exp] ) :: 7192 * 7193 * If DIR_PATH is defined, $.cookie uses that directory; otherwise falls back to '/'. 7194 * $.albumCookie always uses '/' regardless of DIR_PATH. 7195 */ 7196 7197(function($) { 7198 'use strict'; 7199 7200 var LOCALSTORAGE = (function() { 7201 try { 7202 localStorage.setItem('_t', '1'); 7203 localStorage.removeItem('_t'); 7204 return true; 7205 } catch(e) { 7206 return false; 7207 } 7208 }()), 7209 7210 DIR_SCOPE = (typeof DIR_PATH !== 'undefined' && DIR_PATH) ? DIR_PATH : '/', 7211 GLOBAL_SCOPE = '/', 7212 7213 COOKIE_SEP = '; @', 7214 7215 // Parse a stored string value into its native type 7216 getVal = function(v) { 7217 return (/^(true|yes)$/).test(v) ? true : 7218 (/^(false|no)$/).test(v) ? false : 7219 (/^[\d.]+$/).test(v) ? parseFloat(v) : v; 7220 }, 7221 7222 // Deserialize a raw cookie string (handles JSON objects/arrays) 7223 cookieVal = function(v) { 7224 if (typeof v !== 'string') { 7225 return v; 7226 } 7227 if (v[0] === '[' || v[0] === '{') { 7228 v = JSON.parse(v); 7229 for (var o in v) { 7230 if (Object.prototype.hasOwnProperty.call(v, o) && typeof v[o] === 'string') { 7231 v[o] = getVal(v[o]); 7232 } 7233 } 7234 return v; 7235 } 7236 return getVal(v); 7237 }, 7238 7239 // Core read/write/delete â path determines scope 7240 _cookie = function(path, key, value, expire) { 7241 var encodedKey = encodeURIComponent(key), 7242 lsKey = path + encodedKey + '=', 7243 d = new Date(); 7244 7245 if (arguments.length > 2) { 7246 // --- WRITE / DELETE --- 7247 7248 if (value === null) { 7249 // Delete 7250 if (LOCALSTORAGE) { 7251 localStorage.removeItem(lsKey); 7252 } else { 7253 document.cookie = encodedKey + '=; expires=' + d.toGMTString() + 7254 '; path=' + path + '; SameSite=Strict'; 7255 } 7256 return null; 7257 7258 } else { 7259 // Serialize value 7260 var stored = (typeof value === 'object') ? JSON.stringify(value) : String(value); 7261 7262 // Compute expiry 7263 var seconds = (typeof expire === 'number') ? expire : 3600; 7264 d.setTime(d.getTime() + seconds * 1000); 7265 7266 if (LOCALSTORAGE) { 7267 localStorage.setItem(lsKey, stored + COOKIE_SEP + String(d.getTime())); 7268 } else { 7269 document.cookie = encodedKey + '=' + encodeURIComponent(stored) + 7270 '; expires=' + d.toGMTString() + 7271 '; path=' + path + '; SameSite=Strict'; 7272 } 7273 return value; 7274 } 7275 7276 } else { 7277 // --- READ --- 7278 7279 if (LOCALSTORAGE) { 7280 var raw = localStorage.getItem(lsKey); 7281 if (raw) { 7282 var parts = raw.split(COOKIE_SEP); 7283 7284 // Legacy entries stored without our separator (plain '; ' split) 7285 if (parts.length === 1) { 7286 parts = raw.split('; '); 7287 } 7288 7289 if (parts.length > 1) { 7290 if (d.getTime() < parseInt(parts[1], 10)) { 7291 return cookieVal(parts[0]); // not yet expired 7292 } else { 7293 localStorage.removeItem(lsKey); // expired â clean up 7294 } 7295 } else { 7296 return cookieVal(parts[0]); // no expiry stored 7297 } 7298 } 7299 7300 } else { 7301 var cookies = document.cookie.split(/;\s*/), 7302 prefix = encodedKey + '=', 7303 i; 7304 7305 for (i = 0; i < cookies.length; i++) {
7306 if (cookies[i].substring(0, prefix.length) === prefix) { 7307 return cookieVal(decodeURIComponent(cookies[i].substring(prefix.length))); 7308 } 7309 } 7310 } 7311 } 7312 7313 return null; 7314 }; 7315 7316 // --- Public API --- 7317 7318 // Path-scoped cookie (uses DIR_PATH when available) 7319 $.cookie = function(key, value, expire) { 7320 return arguments.length > 1 7321 ? _cookie(DIR_SCOPE, key, value, expire) 7322 : _cookie(DIR_SCOPE, key); 7323 }; 7324 7325 // Global cookie (always scoped to '/') 7326 $.albumCookie = function(key, value, expire) { 7327 return arguments.length > 1 7328 ? _cookie(GLOBAL_SCOPE, key, value, expire) 7329 : _cookie(GLOBAL_SCOPE, key); 7330 }; 7331 7332})(jQuery); 7333/* 7334 * dropdownMenu() :: operates the dropdown menu 7335 * 7336 * Copyright by Lazaworx 7337 * http://www.lazaworx.com 7338 * Author: Laszlo Molnar 7339 * 7340 * Dual licensed under the MIT and GPL licenses. 7341 * - http://www.opensource.org/licenses/mit-license.php 7342 * - http://www.gnu.org/copyleft/gpl.html 7343 * 7344 * Two modes, switched by settings.flyoutQuery: 7345 * - accordion (narrow screens): submenus expand in place, max-height animation 7346 * - flyout (wide screens): submenus are popovers, rendered in the top layer, 7347 * so no ancestor overflow can ever clip them 7348 * 7349 * Usage: $(element).dropdownMenu(options); 7350 * $(element).dropdownMenu('refresh'); // after the markup changed 7351 * $(element).dropdownMenu('close'); 7352 * $(element).dropdownMenu('destroy'); 7353 */ 7354 7355;(function($) { 7356 'use strict'; 7357 7358 var uidCnt = 0, 7359 7360 supportsPopover = (typeof HTMLElement !== 'undefined') && 7361 Object.prototype.hasOwnProperty.call(HTMLElement.prototype, 'popover'), 7362 7363 supportsAnchor = !!(window.CSS && window.CSS.supports && 7364 window.CSS.supports('anchor-name', '--x')), 7365 7366 getUniqueId = function(prefix) { 7367 uidCnt += 1; 7368 return (prefix || 'ldm') + '-' + uidCnt; 7369 }, 7370 7371 // Get event coordinates. Returns null for keyboard-triggered clicks. 7372 7373 getCoords = function(e) { 7374 var oe = e.originalEvent || e, 7375 t = oe.touches || oe.changedTouches; 7376 7377 if (t && t.length) { 7378 return { 7379 x: t[0].clientX, 7380 y: t[0].clientY 7381 }; 7382 } 7383 7384 // A click fired by Enter/Space reports 0,0 - treat it as "no pointer" 7385 if (typeof e.clientX === 'number' && (e.clientX || e.clientY)) { 7386 return { 7387 x: e.clientX, 7388 y: e.clientY 7389 }; 7390 } 7391 7392 return null; 7393 }; 7394 7395 $.fn.dropdownMenu = function(settings) { 7396 7397 // Method calls: $(el).dropdownMenu('refresh') 7398 7399 if (typeof settings === 'string') { 7400 var method = settings; 7401 7402 return this.each(function() { 7403 var api = $(this).data('dropdownMenu'); 7404 7405 if (api && typeof api[method] === 'function') { 7406 api[method](); 7407 } 7408 }); 7409 } 7410 7411 settings = $.extend({}, $.fn.dropdownMenu.defaults, settings); 7412 7413 return this.each(function() { 7414 var self = $(this), 7415 ns = '_ldm_' + (uidCnt += 1), 7416 mql = window.matchMedia(settings.flyoutQuery), 7417 useFlyout = null, // null = not initialised yet 7418 cachedWidth = null, 7419 lastDate = new Date(), 7420 resizeTimer = null, 7421 hoverTimer = null, 7422 rafPending = false, 7423 7424 submenus = function() { 7425 return self.find('li.' + settings.hasDropdownClass + ' > ' + settings.submenuSelector); 7426 }, 7427 7428 subOf = function(li) { 7429 return li.children(settings.submenuSelector); 7430 }, 7431 7432 allMenus = function() { 7433 return self.find(settings.submenuSelector).addBack(settings.submenuSelector); 7434 }, 7435 7436 triggerOf = function(li) { 7437 return li.children('a, button').first(); 7438 }, 7439 7440 openItems = function() { 7441 return self.find('li.' + settings.hasDropdownClass + '.' + settings.openMenuClass); 7442 }, 7443 7444 7445 /* ---------------------------------------------------------- 7446 * Height cache (accordion mode only) 7447 * 7448 * Only the NATURAL height is cached - it depends on the 7449 * container width, the fonts and the DOM, nothing else. 7450 * The viewport clamp (settings.fixed) depends on the scroll 7451 * position and on which siblings happen to be open, so it is 7452 * recomputed on every open and never stored. 7453 * -------------------------------------------------------- */ 7454 7455 naturalHeight = function(menu) { 7456 var h = menu.data('ldmNat'); 7457 7458 if (typeof h !== 'number') { 7459 // Not measured yet: lay it out invisibly 7460 var prev = { 7461 display: menu[0].style.display, 7462 visibility: menu[0].style.visibility, 7463 maxHeight: menu[0].style.maxHeight 7464 }; 7465 7466 menu.css({ 7467 display: 'flex', 7468 visibility: 'hidden', 7469 maxHeight: 'none' 7470 });
7471 7472 h = menu.outerHeight(); 7473 7474 menu.css(prev); 7475 menu.data('ldmNat', h); 7476 } 7477 7478 return h; 7479 }, 7480 7481 openHeight = function(menu) { 7482 var h = naturalHeight(menu), 7483 t; 7484 7485 if (settings.fixed) { 7486 t = menu[0].getBoundingClientRect(); 7487 h = Math.min(window.innerHeight - t.top, h); 7488 } 7489 7490 return h; 7491 }, 7492 7493 invalidateHeights = function() { 7494 allMenus().removeData('ldmNat'); 7495 }, 7496 7497 7498 /* ---------------------------------------------------------- 7499 * Accordion mode 7500 * -------------------------------------------------------- */ 7501 7502 doAnimations = function(el, close) { 7503 var toHide = $(), 7504 toShow = $(), 7505 hasSubmenu = false, 7506 p = el.parents('.' + settings.openMenuClass); // Parent(s): do not hide them 7507 7508 if (close) { 7509 p = p.slice(1); 7510 } 7511 7512 self.find('.' + settings.openMenuClass).not(p).each(function() { 7513 var menu = subOf($(this)); 7514 7515 if (menu.length) { 7516 menu.css('maxHeight', openHeight(menu) + 'px'); 7517 toHide = toHide.add(menu); 7518 } 7519 7520 $(this).removeClass(settings.openMenuClass); 7521 triggerOf($(this)).attr('aria-expanded', 'false'); 7522 }); 7523 7524 toHide.one('transitionend.' + ns, function() { 7525 $(this).css({ 7526 display: 'none', 7527 transition: 'none' 7528 }); 7529 }) 7530 .css({ 7531 overflow: 'hidden' 7532 }); 7533 7534 if (!close) { 7535 p = el.parent(); 7536 7537 if (!p.hasClass(settings.openMenuClass)) { 7538 toShow = subOf(p); 7539 7540 if (toShow.length) { 7541 // Force a measurement now, while the menu is still collapsed 7542 naturalHeight(toShow); 7543 hasSubmenu = toShow.children('.' + settings.hasDropdownClass).length > 0; 7544 } 7545 7546 p.addClass(settings.openMenuClass); 7547 el.attr('aria-expanded', 'true'); 7548 } 7549 7550 toShow.parents(settings.submenuSelector).css({ 7551 maxHeight: 'none' 7552 }); 7553 7554 toShow.on('transitionend.' + ns, function() { 7555 $(this).css({ 7556 display: 'flex', 7557 overflow: hasSubmenu ? 'visible' : 'auto', 7558 maxHeight: 'none' 7559 }); 7560 }) 7561 .css({ 7562 maxHeight: 0, 7563 display: 'flex', 7564 overflow: 'hidden', 7565 visibility: 'visible' 7566 }); 7567 } 7568 7569 window.requestAnimationFrame(function() { 7570 toHide.css({ 7571 transition: 'max-height ' + settings.duration + 'ms ease-out', 7572 maxHeight: 0 7573 }); 7574 7575 toShow.css({ 7576 transition: 'max-height ' + settings.duration + 'ms ease-out', 7577 maxHeight: openHeight(toShow) + 'px' 7578 }); 7579 }); 7580 }, 7581 7582 7583 /* ---------------------------------------------------------- 7584 * Flyout mode (popover) 7585 * -------------------------------------------------------- */ 7586 7587 isTopLevel = function(li) { 7588 return li.parent().closest('li.' + settings.hasDropdownClass).length === 0; 7589 }, 7590 7591 placeFlyout = function(menu, li) { 7592 // Fallback only: used when CSS anchor positioning is unavailable 7593 var trigger = triggerOf(li)[0] || li[0], 7594 r = trigger.getBoundingClientRect(), 7595 pad = settings.viewportPadding, 7596 top = isTopLevel(li), 7597 w, h, x, y; 7598 7599 $(menu).css({ 7600 position: 'fixed', 7601 left: '0px', 7602 top: '0px', 7603 maxHeight: (window.innerHeight - 2 * pad) + 'px' 7604 }); 7605 7606 w = menu.offsetWidth; 7607 h = menu.offsetHeight; 7608 7609 x = top ? r.left : r.right; 7610 y = top ? r.bottom : r.top; 7611 7612 // Flip horizontally if it would leave the viewport 7613 if (x + w > window.innerWidth - pad) { 7614 x = (top ? r.right : r.left) - w; 7615 } 7616 x = Math.max(pad, Math.min(x, window.innerWidth - w - pad)); 7617 7618 // Shift up if it would leave the viewport 7619 if (y + h > window.innerHeight - pad) { 7620 y = Math.max(pad, window.innerHeight - h - pad); 7621 } 7622 7623 $(menu).css({ 7624 left: Math.round(x) + 'px', 7625 top: Math.round(y) + 'px' 7626 });
7627 }, 7628 7629 repositionOpen = function() { 7630 if (supportsAnchor) { 7631 return; // the browser tracks the anchor for us 7632 } 7633 7634 openItems().each(function() { 7635 var li = $(this), 7636 menu = subOf(li)[0]; 7637 7638 if (menu && menu.matches(':popover-open')) { 7639 placeFlyout(menu, li); 7640 } 7641 }); 7642 }, 7643 7644 showFlyout = function(li) { 7645 var menu = subOf(li)[0]; 7646 7647 closeFlyoutsExcept(li); 7648 7649 if (!menu || menu.matches(':popover-open')) { 7650 return; 7651 } 7652 7653 try { 7654 menu.showPopover(); 7655 } catch (err) { 7656 return; 7657 } 7658 7659 li.addClass(settings.openMenuClass); 7660 triggerOf(li).attr('aria-expanded', 'true'); 7661 7662 if (!supportsAnchor) { 7663 placeFlyout(menu, li); 7664 } 7665 }, 7666 7667 hideFlyout = function(li) { 7668 var menu = subOf(li)[0]; 7669 7670 // Close descendants first: a manual popover does not close its 7671 // nested popovers, and hiding the parent would leave them stranded 7672 subOf(li) 7673 .children('.' + settings.hasDropdownClass + '.' + settings.openMenuClass) 7674 .each(function() { 7675 hideFlyout($(this)); 7676 }); 7677 7678 if (menu && menu.matches(':popover-open')) { 7679 try { 7680 menu.hidePopover(); 7681 } catch (err) { 7682 // already hidden 7683 } 7684 } 7685 7686 li.removeClass(settings.openMenuClass); 7687 triggerOf(li).attr('aria-expanded', 'false'); 7688 }, 7689 7690 closeFlyoutsExcept = function(li) { 7691 var keep = li ? li.parents('li.' + settings.hasDropdownClass).add(li) : $(); 7692 7693 openItems().not(keep).each(function() { 7694 if (li && $(this).find(li).length) { 7695 return; 7696 } 7697 hideFlyout($(this)); 7698 }); 7699 }, 7700 7701 7702 /* ---------------------------------------------------------- 7703 * Mode switching 7704 * -------------------------------------------------------- */ 7705 7706 enablePopovers = function() { 7707 submenus().each(function() { 7708 var menu = this, 7709 $menu = $(menu), 7710 li = $menu.parent(), 7711 trigger = triggerOf(li)[0], 7712 id = menu.id || (menu.id = getUniqueId('ldm-menu')), 7713 name; 7714 7715 menu.setAttribute('popover', 'manual'); 7716 $menu.addClass(settings.flyoutClass); 7717 7718 if (supportsAnchor && trigger) { 7719 name = '--' + id; 7720 trigger.style.anchorName = name; 7721 menu.style.positionAnchor = name; 7722 } 7723 7724 if (trigger) { 7725 trigger.setAttribute('aria-haspopup', 'true'); 7726 trigger.setAttribute('aria-expanded', 'false'); 7727 trigger.setAttribute('aria-controls', id); 7728 } 7729 }); 7730 }, 7731 7732 disablePopovers = function() { 7733 submenus().each(function() { 7734 if (this.matches(':popover-open')) { 7735 try { 7736 this.hidePopover(); 7737 } catch (err) { 7738 // ignore 7739 } 7740 } 7741 7742 this.removeAttribute('popover'); 7743 this.style.positionAnchor = ''; 7744 this.style.position = ''; 7745 this.style.left = ''; 7746 this.style.top = ''; 7747 this.style.maxHeight = ''; 7748 7749 $(this).removeClass(settings.flyoutClass) 7750 .parent() 7751 .children('a, button') 7752 .each(function() { 7753 this.style.anchorName = ''; 7754 }); 7755 }); 7756 }, 7757 7758 closeAll = function() { 7759 if (useFlyout) { 7760 closeFlyoutsExcept(null); 7761 } else { 7762 openItems().each(function() { 7763 $(this).removeClass(settings.openMenuClass); 7764 triggerOf($(this)).attr('aria-expanded', 'false'); 7765 }); 7766 7767 allMenus().css({ 7768 display: '', 7769 maxHeight: '', 7770 overflow: '', 7771 transition: '', 7772 visibility: '' 7773 }); 7774 } 7775 }, 7776 7777 setMode = function(flyout) { 7778 flyout = flyout && supportsPopover; 7779 7780 if (flyout === useFlyout) { 7781 return; 7782 } 7783 7784 if (useFlyout !== null) { 7785 closeAll(); 7786 } 7787 7788 useFlyout = flyout; 7789 7790 if (useFlyout) { 7791 allMenus().css({ 7792 display: '', 7793 maxHeight: '', 7794 overflow: '', 7795 transition: '', 7796 visibility: '' 7797 });
7798 enablePopovers(); 7799 } else { 7800 disablePopovers(); 7801 } 7802 7803 self.toggleClass(settings.flyoutModeClass, useFlyout); 7804 }, 7805 7806 7807 /* ---------------------------------------------------------- 7808 * Cache invalidation 7809 * -------------------------------------------------------- */ 7810 7811 onResize = function() { 7812 window.clearTimeout(resizeTimer); 7813 7814 resizeTimer = window.setTimeout(function() { 7815 var w = window.innerWidth; 7816 7817 if (w !== cachedWidth) { 7818 cachedWidth = w; 7819 invalidateHeights(); 7820 closeAll(); // inline max-heights are stale now 7821 } 7822 7823 setMode(mql.matches); 7824 repositionOpen(); 7825 }, settings.resizeDelay); 7826 }, 7827 7828 onScroll = function() { 7829 if (rafPending || !useFlyout || supportsAnchor) { 7830 return; 7831 } 7832 7833 rafPending = true; 7834 7835 window.requestAnimationFrame(function() { 7836 rafPending = false; 7837 repositionOpen(); 7838 }); 7839 }, 7840 7841 refresh = function() { 7842 invalidateHeights(); 7843 closeAll(); 7844 7845 if (useFlyout) { 7846 enablePopovers(); // pick up newly added submenus 7847 } 7848 }, 7849 7850 destroy = function() { 7851 window.clearTimeout(resizeTimer); 7852 window.clearTimeout(hoverTimer); 7853 closeAll(); 7854 disablePopovers(); 7855 7856 self.off('.' + ns); 7857 $(window).off('.' + ns); 7858 $(document).off('.' + ns); 7859 self.removeData('dropdownMenu'); 7860 }; 7861 7862 7863 /* -------------------------------------------------------------- 7864 * Bindings 7865 * ------------------------------------------------------------ */ 7866 7867 cachedWidth = window.innerWidth; 7868 setMode(mql.matches); 7869 7870 self.on('click.' + ns, 'li.' + settings.hasDropdownClass + ' > a, li.' + settings.hasDropdownClass + ' > button', function(e) { 7871 var el = $(this), 7872 li = el.parent(), 7873 open = li.hasClass(settings.openMenuClass), 7874 coords, 7875 arrowClick, 7876 hasDestination; 7877 7878 if (useFlyout) { 7879 7880 hasDestination = el.is('a') && el.attr('href') && el.attr('href') !== '#'; 7881 7882 coords = getCoords(e); 7883 arrowClick = coords ? 7884 (coords.x - el.offset().left) > (el.outerWidth() - settings.arrowZone) : 7885 false; // keyboard here means Enter on a focused link: let it navigate 7886 7887 if (hasDestination && !arrowClick) { 7888 closeFlyoutsExcept(null); // tidy up before leaving the page 7889 return true; // let the browser follow the href 7890 } 7891 7892 if (open) { 7893 hideFlyout(li); 7894 } else { 7895 showFlyout(li); 7896 } 7897 7898 e.stopPropagation(); 7899 e.preventDefault(); 7900 return false; 7901 } 7902 7903 // Accordion mode 7904 if ((new Date() - lastDate) <= settings.tapDelay) { 7905 return true; 7906 } 7907 7908 coords = getCoords(e); 7909 arrowClick = coords ? 7910 (coords.x - el.offset().left) > (el.outerWidth() - settings.arrowZone) : 7911 true; // keyboard: always treat as a toggle 7912 7913 if (!open || arrowClick) { 7914 e.stopImmediatePropagation(); 7915 lastDate = new Date(); 7916 7917 window.requestAnimationFrame(function() { 7918 doAnimations(el, arrowClick && open); 7919 }); 7920 7921 return false; 7922 } 7923 7924 return true; 7925 }); 7926 7927 if (settings.hover) { 7928 self.on('mouseenter.' + ns, 'li.' + settings.hasDropdownClass, function() { 7929 var li = $(this); 7930 7931 if (!useFlyout) { 7932 return; 7933 } 7934 7935 window.clearTimeout(hoverTimer); 7936 closeFlyoutsExcept(li); 7937 hoverTimer = window.setTimeout(function() { 7938 showFlyout(li); 7939 }, settings.hoverDelay); 7940 }) 7941 .on('mouseleave.' + ns, function() { 7942 if (useFlyout) { 7943 window.clearTimeout(hoverTimer); 7944 hoverTimer = window.setTimeout(function() { 7945 closeFlyoutsExcept(null); 7946 }, settings.closeDelay); 7947 } 7948 });
7949 } 7950 7951 // popover="manual" means we own dismissal 7952 $(document).on('pointerdown.' + ns, function(e) { 7953 if (useFlyout && !$(e.target).closest(self).length && 7954 !$(e.target).closest('.' + settings.flyoutClass).length) { 7955 closeFlyoutsExcept(null); 7956 } 7957 }) 7958 .on('keydown.' + ns, function(e) { 7959 if (useFlyout && e.key === 'Escape') { 7960 var deepest = openItems().last(); 7961 7962 if (deepest.length) { 7963 hideFlyout(deepest); 7964 triggerOf(deepest).trigger('focus'); 7965 e.preventDefault(); 7966 } 7967 } 7968 }); 7969 7970 $(window).on('resize.' + ns + ' orientationchange.' + ns, onResize) 7971 .on('scroll.' + ns, onScroll); 7972 7973 // Web fonts change line heights: everything measured before they 7974 // land is a lie 7975 if (document.fonts && document.fonts.ready && document.fonts.ready.then) { 7976 document.fonts.ready.then(function() { 7977 invalidateHeights(); 7978 }); 7979 } 7980 7981 self.data('dropdownMenu', { 7982 refresh: refresh, 7983 close: closeAll, 7984 destroy: destroy 7985 }); 7986 }); 7987 }; 7988 7989 $.fn.dropdownMenu.defaults = { 7990 hasDropdownClass: 'has-submenu', 7991 menuClass: 'menu', // the root <ul> only 7992 submenuSelector: 'ul', // a submenu is simply the li's child list 7993 openMenuClass: 'opened', 7994 flyoutClass: 'is-flyout', // added to every popover submenu 7995 flyoutModeClass: 'flyout-mode', // added to the root in flyout mode 7996 flyoutQuery: '(min-width: 768px)', // below this: accordion 7997 fixed: false, // accordion: clamp to the viewport 7998 hover: true, // flyout: open on hover 7999 hoverDelay: 120, 8000 closeDelay: 2000, // grace period before mouseleave closes a flyout 8001 arrowZone: 32, 8002 viewportPadding: 8, 8003 duration: 500, 8004 tapDelay: 300, 8005 resizeDelay: 150 8006 }; 8007 8008})(jQuery); 8009;(function($, $window) { 8010 'use strict'; 8011 8012 /* 8013 scrollTopBtn.js - add a "scroll to top" button 8014 */ 8015 8016 $.fn.scrollToTopBtn = function() { 8017 8018 var self = $(this).eq(0), 8019 ns = 'lst_' + ($.fn.scrollToTopBtn._counter = ($.fn.scrollToTopBtn._counter || 0) + 1), 8020 text = getTranslations({ 8021 scrollTopTooltip: "Top" 8022 }), 8023 8024 btn = $('<a>', { 8025 'class': 'scrollup icon-arrow-up', 8026 role: 'button', 8027 title: text.scrollTopTooltip 8028 }).appendTo(self); 8029 8030 // Show/hide based on scroll position 8031 $window.on('scroll.' + ns, function() { 8032 btn.toggleClass('show', $window.scrollTop() > 16); 8033 return true; 8034 }); 8035 8036 btn.on('click.' + ns, function() { 8037 if ($(this).hasClass('show')) { 8038 // Scroll html or body, whichever is the scroll root 8039 var target = $('html').scrollTop() ? $('html') : $('body'); 8040 8041 target.animate({ 8042 scrollTop: 0 8043 }, 500); 8044 8045 return false; 8046 } 8047 }); 8048 8049 btn.addTooltip({ 8050 pos: [ 2, 1, 0, 1 ] 8051 }); 8052 8053 // Allow external teardown 8054 self.on('removeScrollToTopBtn.' + ns, function() { 8055 $window.off('.' + ns); 8056 self.off('.' + ns); 8057 btn.remove(); 8058 }); 8059 8060 return this; 8061 }; 8062 8063}(jQuery, jQuery(window))); 8064/* 8065 * alignTo() :: align a layer to another 8066 * 8067 * Copyright by Lazaworx 8068 * http://www.lazaworx.com 8069 * Author: Laszlo Molnar 8070 * 8071 * Dual licensed under the MIT and GPL licenses. 8072 * - http://www.opensource.org/licenses/mit-license.php 8073 * - http://www.gnu.org/copyleft/gpl.html 8074 * 8075 * Usage: $(element).alignTo(target, options); 8076 * options: gap, posX, posY, toX, toY 8077 */ 8078 8079;(function($, $window) { 8080 'use strict'; 8081 8082 var ALIGN_LEFT = 0, 8083 ALIGN_TOP = 0, 8084 ALIGN_CENTER = 1, 8085 ALIGN_MIDDLE = 1, 8086 ALIGN_RIGHT = 2, 8087 ALIGN_BOTTOM = 2, 8088 8089 getFixedParent = function(el) { 8090 8091 while (el.length && el[0].nodeName !== 'BODY') { 8092 if (el.css('position') === 'fixed') { 8093 return el; 8094 } 8095 el = el.parent(); 8096 } 8097 8098 return $(); 8099 }; 8100 8101 $.fn.alignTo = function(target, settings) { 8102 8103 settings = $.extend({}, $.fn.alignTo.defaults, settings); 8104 8105 if (typeof target === 'string') { 8106 target = $(target); 8107 } 8108 8109 if (!(target instanceof $ && target.length)) { 8110 return; 8111 } 8112 8113 var to, 8114 tw, 8115 th, 8116 ww = $(window).width(), 8117 wh = $(window).height(), 8118 fixedParent = getFixedParent(target); 8119 8120 if (target[0].nodeName === 'AREA') { 8121 var shape = target[0].shape.toUpperCase(); 8122 if (shape === 'RECT' || shape === 'CIRCLE' || shape === 'POLY') { 8123 var r = target[0].coords.split(','); 8124 to = target.parent().offset(); 8125 // Area left, top 8126 to.left += parseInt(r[0],10); 8127 to.top += parseInt(r[1],10); 8128 // Area width, height 8129 tw = parseInt(r[2]||0,10) - parseInt(r[0],10); 8130 th = parseInt(r[3]||0,10) - parseInt(r[1],10); 8131 } 8132 } else { 8133 // Normal element 8134 to = target.offset(); 8135 tw = target.outerWidth(); 8136 th = target.outerHeight(); 8137 } 8138 8139 to.left -= $window.scrollLeft(); 8140 to.top -= $window.scrollTop(); 8141 8142 return this.each(function() { 8143 var e = $(this), 8144 nub = e.find('.' + settings.nubClass), 8145 nubPos, 8146 secPos, 8147 hal = settings.pos[2], 8148 val = settings.pos[3]; 8149 8150 e.css('maxHeight', 'none'); 8151 8152 var w = e.outerWidth(), 8153 h = e.outerHeight(), 8154 rx = Math.round(to.left + settings.pos[2] * tw / 2 +
8155 (settings.pos[1] === ALIGN_MIDDLE? ((settings.pos[2] - 1) * settings.gap) : ((settings.pos[0] - settings.pos[2]) * settings.nubSize))), 8156 ry = Math.round(to.top + settings.pos[3] * th / 2 + 8157 (settings.pos[1] !== ALIGN_MIDDLE? ((settings.pos[3] - 1) * settings.gap) : 0)), 8158 l = Math.round(rx - settings.pos[0] * w / 2), 8159 t = Math.round(ry - settings.pos[1] * h / 2); 8160 8161 if (t < 0 || (t + h) > wh) { 8162 // Overflow - vertical 8163 if (settings.pos[2] !== ALIGN_CENTER) { 8164 // Aligned to sides, just make sure it won't hang above 8165 t = ((2 * t + h) > wh)? (wh - h) : 0; 8166 } else if (settings.pos[3] === ALIGN_TOP) { 8167 if (wh > (to.top * 2 + th)) { 8168 // More space below :: moving below 8169 t = to.top + th + settings.gap; 8170 val = ALIGN_BOTTOM; 8171 } 8172 } else if (settings.pos[3] === ALIGN_BOTTOM) { 8173 if (wh < (to.top * 2 + th)) { 8174 // More space above :: move above 8175 t = Math.max(0, to.top - h - settings.gap); 8176 val = ALIGN_TOP; 8177 } 8178 } 8179 if (t < 0) { 8180 t = 0; 8181 } 8182 } 8183 8184 if (l < 0 || (l + w) > ww) { 8185 // Overflow - horizontal 8186 if (settings.pos[3] !== ALIGN_MIDDLE) { 8187 // Not aligned to vertical center 8188 l = ((2 * l + w) > ww)? (ww - w) : 0; 8189 } else if (settings.pos[2] === ALIGN_LEFT) { 8190 if (ww > (to.left * 2 + tw)) { 8191 // More space right :: move right 8192 l = to.left + tw + settings.gap; 8193 hal = ALIGN_RIGHT; 8194 } 8195 } else if (settings.pos[2] === ALIGN_RIGHT) { 8196 if (ww < (to.left * 2 + tw)) { 8197 // More space left :: move left 8198 l = Math.max(0, to.left - w - settings.gap); 8199 hal = ALIGN_LEFT; 8200 } 8201 } 8202 if (l < 0) { 8203 l = 0; 8204 } 8205 if ((l + w) > ww) { 8206 // Still oversized 8207 e.css({ 8208 //overflow: 'auto', 8209 maxWidth: ww - l - (parseInt(e.css('paddingLeft'), 10) + parseInt(e.css('paddingRight'), 10) + parseInt(e.css('borderLeftWidth'), 10) + parseInt(e.css('borderRightWidth'), 10)) 8210 }); 8211 } 8212 } 8213 8214 if (settings.fixNub && nub.length && (hal !== ALIGN_CENTER || val !== ALIGN_MIDDLE)) { 8215 var css = { 8216 left: 'auto', 8217 right: 'auto', 8218 top: 'auto', 8219 bottom: 'auto' 8220 }; 8221 8222 if (settings.pos[1] !== ALIGN_MIDDLE) { 8223 // Align nub horizontally 8224 css.left = to.left - l + hal * tw / 2 - hal * settings.nubSize; 8225 if (val === ALIGN_TOP) { 8226 css.bottom = - 2 * settings.nubSize; 8227 nubPos = 'top'; 8228 } else { 8229 css.top = - 2 * settings.nubSize; 8230 nubPos = 'bottom'; 8231 } 8232 secPos = (settings.pos[0] === ALIGN_LEFT)? 'left' : ((settings.pos[0] === ALIGN_RIGHT)? 'right' : 'center'); 8233 } else { 8234 // Align nub vertically 8235 css.top = to.top - t + val * th / 2 - val * settings.nubSize; 8236 if (hal === ALIGN_LEFT) { 8237 css.right = - 2 * settings.nubSize; 8238 nubPos = 'left'; 8239 } else { 8240 css.left = - 2 * settings.nubSize; 8241 nubPos = 'right'; 8242 } 8243 secPos = (settings.pos[1] === ALIGN_TOP)? 'top' : ((settings.pos[1] === ALIGN_BOTTOM)? 'bottom' : 'middle'); 8244 } 8245 8246 nub.css(css); 8247 } 8248 8249 if (fixedParent.length) { 8250 e.css({ 8251 position: 'fixed', 8252 left: l, 8253 top: t 8254 }); 8255 } else { 8256 e.css({ 8257 position: 'absolute', 8258 left: l + $(window).scrollLeft(), 8259 top: t + $(window).scrollTop() 8260 }); 8261 } 8262 8263 e.removeClass('align-left align-right align-top align-bottom').removeClass('sec-left sec-right sec-top sec-bottom sec-center sec-middle'); 8264 8265 if (nubPos) { 8266 e.addClass('align-' + nubPos); 8267 } 8268 if (secPos) { 8269 e.addClass('sec-' + secPos); 8270 } 8271 8272 }); 8273 }; 8274 8275 $.fn.alignTo.defaults = { 8276 gap: 0, 8277 pos: [ ALIGN_CENTER, ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP ], 8278 fixNub: true, 8279 nubClass: 'nub', 8280 nubSize: 5 8281 }; 8282 8283})(jQuery, jQuery(window)); 8284/* 8285 * addTooltip() :: little Popup displaying 'title' text, or passed text (can be HTML) 8286 * 8287 * Copyright by Lazaworx 8288 * http://www.lazaworx.com 8289 * Author: Laszlo Molnar 8290 * 8291 * Dual licensed under the MIT and GPL licenses. 8292 * - http://www.opensource.org/licenses/mit-license.php 8293 * - http://www.gnu.org/copyleft/gpl.html 8294 * 8295 * Usage: $(element).addTooltip( [txt,] options ); 8296 * options: 8297 id: 'tooltip', 8298 stay: 3000, 8299 posX: ALIGN_CENTER, 8300 posY: ALIGN_BOTTOM, 8301 toX: ALIGN_CENTER, 8302 toY: ALIGN_TOP 8303 */ 8304 8305;(function($) { 8306 'use strict'; 8307 8308 $.fn.hideAllTooltips = function(except) { 8309 8310 return this.each(function() { 8311 8312 //log('Hiding all tooltips'); 8313 $(this).data('suppressTooltips', true).find('[data-tooltip-id]').each(function() { 8314 if (!$(this).data('tooltip-keep')) { 8315 var id = $(this).attr('data-tooltip-id'); 8316 8317 if (id && id.length && id !== except) { 8318 $('#' + id).hide(); 8319 } 8320 } 8321 });
8322 8323 $(this).data('suppressTooltips', false); // restore 8324 }); 8325 }; 8326 8327 $.fn.destroyAllTooltips = function(except) { 8328 8329 return this.each(function() { 8330 8331 //log('Removing ' + $(this).find('[data-tooltip-id]').length + ' tooltip(s).'); 8332 $(this).data('suppressTooltips', true).find('[data-tooltip-id]').each(function() { 8333 var id = $(this).attr('data-tooltip-id'); 8334 //log(id); 8335 if (id && id.length && id !== except) { 8336 $('#' + id).remove(); 8337 } 8338 }); 8339 }); 8340 }; 8341 8342 // Hiding all tooltips on mouse leaving the document 8343 $(document).on('mouseleave._ltt_', function() { 8344 $(document.body).hideAllTooltips(); 8345 }); 8346 8347 $.fn.addTooltip = function(content, settings) { 8348 8349 if (typeof content !== UNDEF && typeof content !== STRING && !(content && content.jquery)) { 8350 settings = content; 8351 content = null; 8352 } 8353 8354 settings = $.extend({}, $.fn.addTooltip.defaults, settings); 8355 8356 //console.log(content || $(this).data('tooltip') || $(this).attr('title')); 8357 8358 var isVisible = function(el) { 8359 8360 if (typeof el === UNDEF || !el || !el.length) { 8361 return true; 8362 } else if (el.is(':hidden') || el.css('opacity') < .25) { 8363 //console.log(el[0].id + ' is hidden or transparent!'); 8364 return false; 8365 } else { 8366 var visible = true; 8367 el.parents().each(function() { 8368 if ($(this).is(':hidden') || $(this).css('opacity') < 0.25) { 8369 visible = false; 8370 return false; 8371 } 8372 });
8373 8374 return visible; 8375 } 8376 }, 8377 8378 // Creating new tooltip 8379 createNew = function(el, cont) { 8380 var tt; 8381 8382 if (!cont) { 8383 8384 if (cont = el.data('tooltip')) { 8385 8386 if (cont.jquery) { 8387 // jQuery element 8388 cont.removeClass('hidden'); 8389 } else { 8390 8391 if (typeof cont === STRING) { 8392 // Selector 8393 if (cont.charAt(0) === '.') { 8394 // read from related layer 8395 cont = el.find(cont).eq(0); 8396 } else if (cont.charAt(0) === '#') { 8397 // ID 8398 cont = $(cont); 8399 } 8400 8401 } else { 8402 // Read settings from data 8403 if (typeof cont === OBJECT) { 8404 settings = $.extend({}, settings, cont); 8405 } 8406 8407 if (settings.hasOwnProperty('html')) { 8408 // read from settings.html 8409 cont = settings['html'].replaceAll('\uff02', '"'); 8410 } else { 8411 // read from title attr 8412 cont = el.attr('title'); 8413 el.removeAttr('title'); 8414 } 8415 } 8416 } 8417 8418 } else { 8419 // read from data or title attr 8420 cont = el.attr('title'); 8421 el.removeAttr('title'); 8422 } 8423 8424 if (!cont || !cont.length) { 8425 return $(); 8426 } 8427 8428 tt = $('<div>', { 8429 html: cont 8430 }).appendTo('body'); 8431 8432 } else if (typeof cont === STRING) { 8433 8434 // passed directly :: html structure as string 8435 tt = $('<div>', { 8436 html: cont 8437 }).appendTo('body'); 8438 8439 } else if (cont.jquery) { 8440 8441 // jQuery element 8442 if (!$.contains(document.body, cont[0])) { 8443 tt = cont.appendTo('body'); 8444 } else { 8445 tt = cont; 8446 } 8447 8448 } else { 8449 return $(); 8450 } 8451 8452 if (tt.length === 0) { 8453 return $(); 8454 } 8455 8456 tt .attr('id', el.attr('data-tooltip-id')) 8457 .addClass(settings.className) 8458 .attr('role', 'tooltip') 8459 .append($('<span>', { 8460 'class': settings.nub 8461 })) 8462 .data('display', (tt.css('display') === 'none')? 'block' : tt.css('display')) 8463 .attr('aria-hidden', 'true') 8464 .hide(); 8465 8466 return tt; 8467 8468 }; 8469 8470 return this.each(function() { 8471 8472 if ($(this).data('tooltip-id')) { 8473 // already has tooltip 8474 return true; 8475 } 8476 8477 var self = $(this), // trigger element 8478 options = settings, // initializing with outer scope settings 8479 tt, // tooltip layer 8480 to, // show timeout 8481 hto, // hide timeout 8482 overtt = false, // over the tooltip 8483 focus = false, // tooltip input got focus 8484 offs, // last offset to detect if moving 8485 start, // event start 8486 wasOn = false, // was on at the beginning of the event? 8487 ns = '_ltt_' + ($.fn.addTooltip._counter = ($.fn.addTooltip._counter || 0) + 1), 8488 8489 // Create 8490 create = function() { 8491 //log('Creating tooltip'); 8492 if (self.data('suppressTooltips') || !(tt = createNew(self, content))) { 8493 return false; 8494 } 8495 8496 // Keep the popup live while the mouse is over 8497 tt .on('mouseenter.' + ns, getFocus) 8498 .on('mouseleave.' + ns, lostFocus); 8499 8500 // ... or an input box has focus 8501 tt .find('input, textarea') 8502 .on('focus.' + ns, function() { 8503 focus = true; 8504 getFocus(this); 8505 }) 8506 .on('blur.' + ns, function() { 8507 focus = false; 8508 }); 8509 8510 // A button that closes the tooltip on click 8511 tt .find('[data-closer]') 8512 .on('click.' + ns, function() { 8513 setTimeout(hideLater, 1000); 8514 return true; 8515 }); 8516 8517 return true; 8518 }, 8519 8520 // getFocus 8521 getFocus = function(e) { 8522 8523 //log('Getting focus (' + e.type + ')'); 8524 overtt = true; 8525 if (isVisible($(this))) { 8526 clearTimeout(hto); 8527 } 8528 }, 8529 8530 // lostFocus 8531 lostFocus = function(e) { 8532 8533 //log('Losing focus (' + e.type + ')'); 8534 if (focus) { 8535 return; 8536 } 8537 8538 overtt = false; 8539 clearTimeout(hto); 8540 hto = setTimeout(hide, Math.max(options.stay, 500)); 8541 }, 8542 8543 // Hiding the popup 8544 hide = function() { 8545 8546 //log('Hiding tooltip (over = ' + overtt + ')'); 8547 clearTimeout(hto); 8548 8549 if (overtt === false) { 8550 wasOn = false; 8551 clearTimeout(to); 8552 //events = ''; 8553 if (tt) { 8554 tt .css({ 8555 opacity: 0 8556 }) 8557 .one('transitionend', function() { 8558 $(this).css({ 8559 display: 'none' 8560 });
8561 }); 8562 } 8563 } 8564 }, 8565 8566 // Hide later :: automatically on touch devices 8567 hideLater = function() { 8568 //log('Hiding tooltip (stay = ' + options.stay + 'ms)'); 8569 clearTimeout(hto); 8570 hto = setTimeout(hide, options.stay); 8571 }, 8572 8573 8574 // Showing, aligning and fading in 8575 show = function() { 8576 var o = self.offset(); 8577 //log('Showing tooltip'); 8578 if (self.data('suppressTooltips')) { 8579 return; 8580 } 8581 8582 clearTimeout(hto); 8583 //log('pos: ' + o.left.toFixed(3) + ',' + o.top.toFixed(3)); 8584 8585 if (!offs || (Math.abs(o.top - offs.top) < 1 && Math.abs(o.left - offs.left) < 1)) { 8586 // only if target layer not in move 8587 if (options.exclusive) { 8588 self.data('tooltip-keep', true); 8589 $(document.body).hideAllTooltips(self.data('tooltip-id')); 8590 self.data('tooltip-keep', null); 8591 } 8592 8593 tt .css({ 8594 opacity: 0, 8595 display: 'block' 8596 }) 8597 .alignTo(self, { 8598 gap: options.gap, 8599 pos: options.pos 8600 }) 8601 .css({ 8602 opacity: 1 8603 }) 8604 .one('transitionend', function() { 8605 $(this).css({ 8606 display: 'block' 8607 }); 8608 hideLater(); 8609 }); 8610 8611 // Fallback if no transition is defined 8612 setTimeout(hideLater, 50); 8613 8614 wasOn = true; 8615 8616 } 8617 8618 offs = o; 8619 }, 8620 8621 // Entering the hotspot :: hover or focus 8622 enter = function(e) { 8623 //log('Entering spot (' + e.type + ')'); 8624 8625 if (!self.data('suppressTooltips') && isVisible($(e.target).closest('[data-tooltip-id]'))) { 8626 wasOn = ttVisible(); 8627 start = new Date(); 8628 offs = self.offset(); 8629 tt = $('#' + self.data('tooltip-id')); 8630 //log('pos: ' + offs.left.toFixed(3) + ',' + offs.top.toFixed(3)); 8631 8632 if (!tt.length) { 8633 if (!create()) { 8634 destroy(); 8635 return true; 8636 } 8637 } else { 8638 clearTimeout(hto); 8639 } 8640 8641 clearTimeout(to); 8642 to = null; 8643 8644 if (whatInput.ask('intent') === 'mouse') { 8645 to = setTimeout(show, options.delay); 8646 } 8647 } 8648 8649 return true; 8650 }, 8651 8652 // Leaving the trigger element 8653 leave = function(e) { 8654 8655 clearTimeout(to); 8656 to = null; 8657 8658 if (isVisible($(e.target).closest('[data-tooltip-id]'))) { 8659 clearTimeout(hto); 8660 //log('Leaving spot (' + e.target.nodeName + ')'); 8661 if (whatInput.ask('intent') === 'mouse') { 8662 hto = setTimeout(hide, 500); 8663 } else { 8664 hto = setTimeout(hide, 3000); 8665 } 8666 } 8667 8668 return false; 8669 }, 8670 8671 // Avoid Click 8672 avoidClick = function(e) { 8673 e.preventDefault(); 8674 clearTimeout(to); 8675 clearTimeout(hto); 8676 }, 8677 8678 ttVisible = function() { 8679 return !!tt && tt.is(':visible') && (tt.css('opacity') > 0.99); 8680 }, 8681 8682 // Test for link 8683 hasLink = function(el) { 8684 var a = el.closest('a'); 8685 return a.length && a.attr('href') && !a.attr('href').startsWith('javascript') && a.attr('href') !== '#'; 8686 }, 8687 8688 // The hotspot clicked 8689 clicked = function(e) { 8690 //log('Clicking spot + (target: ' + e.target.nodeName + ')'); 8691 //log(whatInput.ask()); 8692 //log(events); 8693 8694 if (isVisible($(e.target).closest('[data-tooltip-id]'))) { 8695 clearTimeout(to); 8696 8697 if (options.touchToggle || whatInput.ask('intent') !== 'mouse') { 8698 //if (events.indexOf(TOUCH.START) !== -1 || isFireFox) { 8699 // touched 8700 // Firefox by default emulates touch events with mouse events, 8701 // no way you can tell the difference, so it's safer to treat like touch 8702 8703 var now = new Date(); 8704 8705 if (options.touchToggle || (now - start) > 1000) { 8706 // touch toggle or long touch 8707 //log('Touched for ' + (now-start) + 'ms'); 8708 //log('wasOn='+wasOn+' hasLink()='+hasLink($(e.target))+' visible='+ttVisible()); 8709 if (hasLink($(e.target)) && ttVisible()) { 8710 // Link and tt is visible :: let click it 8711 return true; 8712 } else { 8713 // No link or need to toggle on first 8714 avoidClick(e); 8715 8716 if (wasOn) { 8717 hide(); 8718 } else { 8719 show(); 8720 } 8721 8722 return false; 8723 } 8724 } 8725 8726 } else if (hasLink($(e.target))) { 8727 return true; 8728 } 8729 8730 if (wasOn) { 8731 clearTimeout(hto); 8732 hide(); 8733 } 8734 8735 //events = ''; 8736 overtt = false; 8737 } 8738 8739 return true; 8740 }, 8741 8742 // Force removing the tooltip 8743 destroy = function(e) { 8744 self.off('.' + ns); 8745 clearTimeout(to); 8746 clearTimeout(hto); 8747 self.data('suppressTooltips', true); 8748 $('#' + self.attr('data-tooltip-id')).remove(); 8749 self.attr('data-tooltip-id', null); 8750 }; 8751 8752 if (self.attr('data-tooltip') && typeof self.data('tooltip') === OBJECT) { 8753 options = $.extend({}, options, self.data('tooltip')); 8754 self.removeAttr('data-tooltip'); 8755 } 8756 8757 self.attr('data-tooltip-id', ns) 8758 .data('suppressTooltips', false) 8759 .on('destroyTooltip', destroy) 8760 .on('removeTooltip', hide) 8761 .on('focus.' + ns + ' mouseenter.' + ns /*+ ' ' + TOUCH.START + '.' + ns*/, enter) 8762 .on('blur.' + ns + ' mouseleave.' + ns, leave) 8763 .on('click.' + ns, clicked); 8764 8765 }); 8766 }; 8767 8768 /* pos: 8769 ALIGN_LEFT = ALIGN_TOP = 0 8770 ALIGN_CENTER = ALIGN_MIDDLE = 1 8771 ALIGN_RIGHT = ALIGN_BOTTOM = 2 8772 */ 8773 $.fn.addTooltip.defaults = { 8774 delay: 150, 8775 className: 'tooltip', 8776 nub: 'nub', 8777 stay: 2000, 8778 exclusive: true, 8779 touchToggle: false, 8780 pos: [1,2,1,0], 8781 gap: 6 8782 }; 8783 8784})(jQuery); 8785/* 8786 * laza.modal() :: adding modal window to any layer (typically 'body') 8787 * 8788 * Copyright by Lazaworx 8789 * http://www.lazaworx.com 8790 * Author: Laszlo Molnar 8791 * 8792 * Dual licensed under the MIT and GPL licenses. 8793 * - http://www.opensource.org/licenses/mit-license.php 8794 * - http://www.gnu.org/copyleft/gpl.html 8795 * 8796 * Usage: $(element).modal( content, buttons, options ); 8797 */ 8798 8799;(function($) { 8800 'use strict'; 8801 8802 $.fn.modal = function(content, buttons, settings) { 8803 8804 var $body = $(document.body); 8805 8806 if (typeof content === 'string') { 8807 // Content is a String? 8808 content = $(document.createTextNode(content)); 8809 } 8810 8811 if (!(content instanceof $ && content.length)) { 8812 // Is jQuery element? 8813 return; 8814 } 8815 8816 if (typeof buttons !== UNDEF && !Array.isArray(buttons)) { 8817 // No buttons? Swap with settings 8818 settings = buttons; 8819 buttons = null; 8820 } 8821 8822 if (content.data('modal')) { 8823 // Settings passed as data attribute of the container 8824 settings = $.extend({}, settings, content.data('modal')); 8825 } 8826 8827 // Merge settings with defaults 8828 settings = $.extend({}, $.fn.modal.defaults, settings); 8829 8830 var container = $(this), 8831 modals = container.find('.' + settings.modalClass), 8832 lastZ = modals.length? parseInt(modals.last().css('zIndex')) : 300000, 8833 text = getTranslations($.fn.modal.text), 8834 8835 // Aligning the window to an existing element 8836 alignPopup = function(modal, to) { 8837 8838 if (!to.jquery) { 8839 to = $(to); 8840 } 8841 8842 var toPos = to.position(), 8843 toDim = [ to.outerWidth(), to.outerHeight() ], 8844 wDim = [ modal.outerWidth(), modal.outerHeight() ], 8845 midPos = [ (toPos.left + toDim[0] / 2) / wDim[0], (toPos.top + toDim[1] / 2) / wDim[1] ], 8846 left = null, 8847 right = null, 8848 top = null, 8849 bottom = null, 8850 ctop, 8851 cbottom, 8852 css = {}, 8853 ccss = {}; 8854 8855 // Horizontal 8856 if (midPos[0] > 0.333 && midPos[0] < 0.667) { 8857 // Center 8858 left = '50%'; 8859 } else if (midPos[0] <= 0.333) { 8860 // Left 8861 left = toPos.left + 'px'; 8862 } else { 8863 // Right 8864 right = (wDim[0] - (toPos.left + toDim[0])) + 'px'; 8865 left = 'auto'; 8866 } 8867 8868 // Vertical 8869 if (midPos[1] <= 0.5) { 8870 // Top 8871 top = (toPos.top + toDim[1] + 4) + 'px'; 8872 ctop = (toPos.top + toDim[1] - settings.closeSize) + 'px'; 8873 } else { 8874 // Bottom 8875 bottom = (wDim[1] - toPos.top + 4) + 'px'; 8876 top = 'auto'; 8877 cbottom = (wDim[1] - toPos.top - settings.closeSize) + 'px'; 8878 ctop = 'auto'; 8879 } 8880 8881 css.left = ccss.left = left; 8882 8883 if (right !== null) { 8884 css.right = ccss.right = right; 8885 } 8886 8887 css.top = top; 8888 ccss.top = ctop; 8889 8890 if (bottom !== null) { 8891 css.bottom = bottom; 8892 ccss.bottom = cbottom; 8893 } 8894 8895 if (left === '50%') { 8896 css.transform = ccss.transform = 'translateX(-50%)'; 8897 } else { 8898 css.transform = ccss.transform = 'none'; 8899 } 8900 8901 // Applying the position 8902 modal.children('.' + settings.windowClass).css(css); 8903 modal.children('.' + settings.closeClass).css(ccss); 8904 }, 8905 8906 // Setting up an existing modal 8907 setupExistingModal = function(content) { 8908 var modal = content.hasClass(settings.modalClass)? content : content.parents('.' + settings.modalClass); 8909 8910 if (!modal.length || modal.parent()[0] !== container[0]) { 8911 // Moving under container 8912 container.append(modal); 8913 } 8914 8915 modal.data('keep', true); 8916 8917 return modal; 8918 }, 8919 8920 createNewModal = function(content, buttons) { 8921 // Creating new modal 8922 var modal, 8923 popup, 8924 closeBtn, 8925 cont; 8926 8927 $.fn.modal._zIndex = Math.max($.fn.modal._zIndex || 300000, lastZ) + 1; 8928
8929 modal = $('<div>', { 8930 'class': settings.modalClass, 8931 zIndex: $.fn.modal._zIndex 8932 }).appendTo(container); 8933 8934 modal.data('modal-ns', 'lmo_' + ($.fn.modal._counter = ($.fn.modal._counter || 0) + 1)); 8935 8936 popup = $('<div>', { 8937 'class': settings.windowClass + (settings.hasOwnProperty('class')? (' ' + settings['class']) : ''), 8938 role: 'dialog' 8939 }).appendTo(modal); 8940 8941 closeBtn = $('<a>', { 8942 'class': settings.closeClass, 8943 'title': text.closeWindow 8944 }).appendTo((settings.alignTo)? modal : popup); 8945 8946 closeBtn.addTooltip(); 8947 8948 cont = $('<div>', { 8949 'class': settings.contentClass + ' scrollable' 8950 }).appendTo(popup); 8951 8952 if (settings.title) { 8953 popup.addClass(settings.hasHeaderClass).prepend($('<header>', { 8954 'class': settings.headerClass + (settings.icon? (' icon-' + settings.icon) : ''), 8955 html: settings.title 8956 })); 8957 } 8958 8959 cont.append(content.show()); 8960 8961 if (typeof buttons !== UNDEF && buttons) { 8962 var i, 8963 a, 8964 8965 btnCont = $('<div>', { 8966 'class': settings.buttonsClass 8967 }).appendTo(cont), 8968 8969 clickhandler = function(e) { 8970 e.preventDefault(); 8971 var a = e.target; 8972 8973 if (typeof a.handler === FUNCTION) { 8974 if (a.handler.call(container[0]) !== false) { 8975 close($(a).closest('.' + settings.modalClass)); 8976 } 8977 } 8978 return false; 8979 }; 8980 8981 for (i = 0; i < buttons.length; i++) { 8982 a = $('<button>', { 8983 'class': [ settings.buttonClass, buttons[i].c ].filter(Boolean).join(' '), 8984 html: ' ' + buttons[i].t 8985 }).on('click', clickhandler).appendTo(btnCont); 8986 8987 if (typeof buttons[i].h === FUNCTION) { 8988 a[0].handler = buttons[i].h; 8989 } 8990 } 8991 } 8992 //setMaxHeight(); 8993 8994 return modal; 8995 }, 8996 8997 init = function(content, buttons) { 8998 var modal = document.body.contains(content[0])? 8999 setupExistingModal(content) 9000 : 9001 createNewModal(content, buttons); 9002 9003 if (settings.hasOwnProperty('closeOnClick')) { 9004 $(settings.closeOnClick).on('click', function() { 9005 close($(this).closest('.' + settings.modalClass)); 9006 });
9007 } 9008 9009 var at = settings.hasOwnProperty('alignTo')? ((settings.alignTo instanceof jQuery)? settings.alignTo : $(settings.alignTo)) : $(); 9010 9011 if (at.length && at.is(':visible')) { 9012 alignPopup(modal, at); 9013 } 9014 9015 // Prepare 9016 modal.css({ 9017 display: 'block', 9018 opacity: 0, 9019 transition: 'opacity ' + settings.speed + 'ms ease-out' 9020 }).removeClass('hidden'); 9021 modal[0].offsetHeight; 9022 modal.css({ 9023 opacity: 1 9024 }) 9025 9026 $body.addClass(settings.hasModalClass); 9027 9028 // Externally triggered close 9029 modal.on('destroy', function() { 9030 close($(this)); 9031 }); 9032 9033 modal.on('close', function() { 9034 close($(this)); 9035 }); 9036 9037 // Close button click handler 9038 modal.find('.' + settings.closeClass).add(modal).on('click.' + modal.data('modal-ns'), function(e) { 9039 if (e.target === this) { 9040 close($(this).closest('.' + settings.modalClass)); 9041 if (typeof settings['onClose'] === FUNCTION) { 9042 settings.onClose.call(container[0]); 9043 } 9044 return false; 9045 } 9046 }); 9047 9048 if (settings.autoFade) { 9049 modal.data('to', setTimeout(function() { 9050 close(modal); 9051 }, settings.autoFade)); 9052 } 9053 9054 return modal; 9055 }, 9056 9057 close = function(modal) { 9058 9059 if (!modal.length) { 9060 return; 9061 } 9062 9063 clearTimeout(modal.data('to')); 9064 9065 modal.destroyAllTooltips(); 9066 9067 modal.one('transitionend', function() { 9068 if (modal.data('keep')) { 9069 modal.css({ 9070 display: 'none' 9071 }); 9072 } else { 9073 modal.remove(); 9074 } 9075 $body.toggleClass(settings.hasModalClass, container.find('.' + settings.modalClass).length > 0); 9076 }).css({ 9077 transition: 'opacity ' + settings.speed + 'ms ease-out' 9078 }).css({ 9079 opacity: 0 9080 }); 9081 }; 9082 9083 // Starts here 9084 return init(content, buttons); 9085 }; 9086 9087 $.fn.modal.defaults = { 9088 modalClass: 'modal', 9089 windowClass: 'window', 9090 headerClass: 'header', 9091 closeClass: 'close', 9092 contentClass: 'content', 9093 buttonsClass: 'buttons', 9094 buttonClass: 'button', 9095 hasHeaderClass: 'has-header', 9096 hasModalClass: 'has-modal', 9097 speed: 300, 9098 autoFade: 0, 9099 width: 400, 9100 type: 'normal', 9101 closeSize: 40 9102 }; 9103 9104 $.fn.modal.text = { 9105 closeWindow: "Close window" 9106 }; 9107 9108})(jQuery); 9109/* 9110 * Rendering share buttons 9111 */ 9112 9113;(function($) { 9114 'use strict'; 9115 9116 $.fn.renderShares = function(settings) { 9117 9118 settings = $.extend({}, $.fn.renderShares.defaults, settings); 9119 9120 var self = $(this), 9121 cont, 9122 previewImage, 9123 shareCaption, 9124 socialLinks, 9125 text = getTranslations($.fn.renderShares.text), 9126 emailSubject = settings.emailSubject || text['checkThisOut'], 9127 emailBody = settings.emailBody || '{title}\n\r{description}\n\r{link}', 9128 defaultImage, 9129 defaultCaption, 9130 9131 // Copying URL to the clipboard 9132 9133 copyUrl = function(e) { 9134 var url = this.querySelector('input[name=url]'), 9135 save, 9136 succ; 9137 9138 if (url) { 9139 save = url.value; 9140 9141 url.focus(); 9142 url.setSelectionRange(0, save.length); 9143 9144 if (navigator.clipboard && navigator.clipboard.writeText) { 9145 navigator.clipboard.writeText(save).then(function() { 9146 url.value = text['copied'] + '...'; 9147 url.classList.add('copied'); 9148 setTimeout(function() { 9149 url.value = save; 9150 url.classList.remove('copied'); 9151 }, 500); 9152 });
9153 } else { 9154 // Fallback: select for manual copy 9155 url.focus(); 9156 url.setSelectionRange(0, save.length); 9157 } 9158 } 9159 9160 return false; 9161 }, 9162 9163 // Updating URL in the input box 9164 9165 changeUrl = function(e) { 9166 var opt = this.checked, 9167 option = this.name, 9168 url = this.parentNode.querySelector('input[name=url]'); 9169 9170 if (opt) { 9171 if (url.value.indexOf('#') >= 0) { 9172 if (url.value.search(new RegExp('[#&]' + option)) === -1) { 9173 url.value += '&' + option; 9174 } 9175 } else { 9176 url.value += '#' + option; 9177 } 9178 } else { 9179 url.value = url.value.replace(new RegExp('[\#&]' + option, 'g'), ''); 9180 } 9181 }, 9182 9183 // Updating links on trigger (e, opt), or initial 9184 9185 updateLinks = function(opt) { 9186 9187 if (typeof opt === UNDEF) { 9188 // Initial 9189 opt = settings; 9190 9191 } else { 9192 // Triggered 9193 9194 opt = $.extend({}, settings, opt); 9195 9196 // Reading URL 9197 if (!opt.hasOwnProperty('href')) { 9198 opt.href = window.location.href; 9199 } 9200 9201 // Fixing HTML title 9202 opt.title = (opt.title || '').stripHTML(); 9203 9204 // Fixing HTML description 9205 opt.description = (opt.description || '').stripHTML(); 9206 } 9207 9208 // Fixing relative URL 9209 if (opt.image && !opt.image.startsWith('http')) { 9210 opt.image = window.location.href.getDir() + opt.image; 9211 } 9212 9213 var cc = $(opt.currCardHook); // Current lightbox image 9214 9215 // Reading data from current card in the lightbox 9216 if (cc.length) { 9217 9218 if (!opt.hasOwnProperty('title')) { 9219 // Finding title 9220 opt.title = cc.find('.caption .title').text(); 9221 9222 if (!opt['title']) { 9223 // No title: it's mandatory, so trying to read something from the image / video 9224 var img = cc.find('img, video'); 9225 opt.title = img.length? 9226 img.attr('title') || img.attr('alt') || (img.attr('src')? img.attr('src').getFile().replace(/\.(jpg|gif|png|mp4)$/i, '').replace(/_/g, ' ') : '') 9227 : 9228 ''; 9229 } 9230 } 9231 9232 if (!opt.hasOwnProperty('description')) { 9233 // Reading lightbox caption 9234 opt.description = cc.find('.caption .comment').text(); 9235 } 9236 9237 // Image 9238 if (!opt.hasOwnProperty('image')) { 9239 var c = $(opt.currCardHook + ' img'); 9240 if (c.length) { 9241 // Image 9242 opt.image = c.attr('src'); 9243 } else { 9244 c = $(opt.currCardHook + ' video'); 9245 if (c.length) { 9246 // Video 9247 opt.image = c.attr('src').replaceExt('jpg'); 9248 } else { 9249 // Nothing found: fall back to meta 9250 c = $('link[rel=image_src]'); 9251 opt.image = (c.length)? c.attr('href') : (window.location.href.getDir() + 'folderthumb.jpg'); 9252 } 9253 } 9254 } 9255 9256 // Updating preview card 9257 if (opt.usePreview) { 9258 9259 if (opt.hasOwnProperty('image')) { 9260 previewImage.attr('src', opt.image); 9261 } 9262 9263 shareCaption.empty(); 9264 9265 if (opt.hasOwnProperty('title')) { 9266 shareCaption.append($('<h3>', { 9267 'class': 'title', 9268 text: opt.title 9269 })); 9270 } 9271 9272 if (opt.hasOwnProperty('description')) { 9273 shareCaption.append($('<div>', { 9274 'class': 'description', 9275 text: opt.description 9276 })); 9277 } 9278 } 9279 9280 } else { 9281 9282 // Restore defaults 9283 if (previewImage) previewImage.attr('src', defaultImage); 9284 if (shareCaption) shareCaption.html(defaultCaption); 9285 } 9286 9287 // Encoded versions 9288 var _href = encodeURIComponent(opt.href), 9289 _slideHref = (opt.href.indexOf('#img=') > 0)? opt.href.replace(settings.indexName, '').replace('#img=', 'slides/').replaceExt(opt.pageExt) : opt.href, 9290 _title = encodeURIComponent(opt.title), 9291 _description = encodeURIComponent(opt.description), 9292 _image = encodeURIComponent(opt.image), 9293 _link; 9294 9295 self.find('.' + settings.sharesClass + ' a').each(function() { 9296 var a = $(this); 9297 9298 switch (a.data('share')) { 9299 9300 case 'facebook': 9301 9302 if (typeof FB === UNDEF) { 9303 _link = 'https://www.facebook.com/sharer.php?s=100&p%5Burl%5D='
9303+ opt.href + '&p%5Bimages%5D%5B0%5D=' + opt.image + '&p%5Btitle%5D=' + _title; 9304 } else { 9305 a.off('.share').on('click.share', function() { 9306 FB.ui({ 9307 method: 'share', 9308 href: _slideHref, 9309 caption: opt.title, 9310 description: opt.description, 9311 picture: opt.image 9312 }); 9313 return false; 9314 }); 9315 return; 9316 } 9317 break; 9318 9319 case 'threads': 9320 _link = 'https://threads.net/intent/post?text=' + _title + ':%20' + _slideHref; 9321 break; 9322 9323 case 'bluesky': 9324 _link = 'https://bsky.app/intent/compose?text=' + _title + ':%20' + _slideHref; 9325 break; 9326 9327 case 'twitter': 9328 _link = 'https://twitter.com/intent/tweet?text=' + _title + ':%20' + _href; 9329 break; 9330 9331 case 'tumblr': 9332 _link = 'https://www.tumblr.com/share/link?url=' + _href + '&name=' + _title; 9333 break; 9334 9335 case 'pinterest': 9336 _link = 'https://pinterest.com/pin/create/button/?url=' + _href + '&media=' + _image + '&description=' + _title; 9337 break; 9338 9339 case 'linkedin': 9340 _link = 'https://www.linkedin.com/shareArticle?mini=true&url=' + _href + '&title=' + _title + '&summary=' + _description; 9341 break; 9342 9343 case 'digg': 9344 _link = 'https://digg.com/submit?url=' + opt.href; 9345 break; 9346 9347 case 'reddit': 9348 _link = 'https://www.reddit.com/submit?url=' + opt.href; 9349 break; 9350 9351 case 'email': 9352 _link = 'mailto:?subject=' + encodeURIComponent(emailSubject.replace('{title}', opt.title).replace('{description}', opt.description).replace('{album}', settings.albumTitle || '')) + 9353 '&body=' + encodeURIComponent(emailBody.replace('{title}', opt.title).replace('{description}', opt.description).replace('{link}', opt.href).replace('{album}', settings.albumTitle || '')); 9354 break; 9355 9356 case 'link': 9357 var tt = $('<form class="copy-url">' + 9358 '<input name="url" type="text" readonly value="' + opt.href + '">' + 9359 '<input name="slideshow" id="slideshowcb" type="checkbox">' + 9360 '<label for="slideshowcb">' + text['slideshow'] + '</label>' + 9361 ' <button type="submit" class="button icon-copy" data-closer> ' + text['copy'] + '</button>' + 9362 '</form>'); 9363 9364 tt.on('submit', copyUrl); 9365 tt.find('[name=slideshow]').on('change', changeUrl); 9366 9367 a.addClass('copy-url-btn') 9368 .trigger('destroyTooltip').addTooltip(tt, { 9369 stay: 4000, 9370 pos: settings.pos, 9371 touchToggle: true 9372 }); 9373 9374 return; 9375 9376 default: 9377 return; 9378 9379 } 9380 9381 a.attr({ 9382 href: _link, 9383 target: '_blank' 9384 }); 9385 9386 }); 9387 }, 9388 9389 // Creating like buttons initially 9390 9391 createButtons = function(target) { 9392 9393 var buttons = settings.buttons.split(','), 9394 _href = encodeURIComponent(window.location.href.split('#')[0]), 9395 _title = encodeURIComponent(settings.title || $('meta[name=title]').attr('content') || $('title').text()), 9396 _image = settings.image? 9397 ((settings.image.startsWith('http')? '' : window.location.href.getDir()) + 9398 encodeURIComponent(settings.image)) 9399 : 9400 $('link[rel=image_src]').attr('href'), 9401 btns = $('<div>', { 9402 'class': settings.buttonsClass 9403 }).appendTo(target || self); 9404 9405 for (var i = 0; i < buttons.length; i++) { 9406 9407 switch (buttons[i]) { 9408 9409 case 'facebook': 9410 btns.append('<div class="likebtn fb-like" data-href="' + _href + '" data-layout="button_count" data-action="like" data-size="large" data-show-faces="false" data-share="false"></div>'); 9411 break; 9412 9413 case 'twitter': 9414 btns.append('<a class="likebtn twitter-share-button icon-x" href="https://twitter.com/intent/tweet?text=' + _title + '&url=' + _href + '" data-size="large" target="_blank"> Tweet</a>'); 9415 break; 9416 9417 case 'pinterest': 9418 btns.append('<div class="likebtn"><a data-pin-count="beside" data-pin-tall="true" data-pin-save="true" href="https://www.pinterest.com/pin/create/button/?url=' + _href + '&media=' + _image + '&description=' + _title + '" data-pin-do="buttonPin"></a></div>'); 9419 break; 9420 } 9421 } 9422 }, 9423 9424 // Creating share links the first time 9425 9426 createLinks = function(target) { 9427 var n, 9428 links = $('<div>', { 9429 'class': settings.sharesClass 9430 }).appendTo(target || self), 9431 sites = settings.sites.split(','), 9432 validSites = ['facebook','threads','bluesky','twitter','tumblr','pinterest','linkedin','digg','reddit','email','link']; 9433 9434 for (var i = 0, a; i < sites.length; i++) { 9435 9436 n = sites[i]; 9437 9438 if (validSites.indexOf(n.trim()) >= 0) { 9439 a = $('<a>', { 9440 'class': settings.btnClass + ' icon-' + ((n === 'twitter')? 'x' : n), 9441 rel: 'noopener', 9442 text: ' ' + (settings.buttonLabels? n.capitalize() : '') 9443 }) 9444 .data('share', n)
9445 .appendTo(links); 9446 9447 if (n === 'email') { 9448 a.data('tooltip', text['email']); 9449 } else if (n !== 'link') { 9450 a.data('tooltip', text['shareOn'] + ' ' + ((n === 'twitter')? 'X' : n.capitalize())); 9451 } 9452 } 9453 } 9454 9455 updateLinks(); 9456 9457 links.children('a').not('.copy-url-btn').addTooltip({ 9458 pos: settings.pos 9459 }); 9460 }, 9461 9462 // Trying to find a title 9463 getDefaultTitle = function() { 9464 var t = self.find('h3,.title'); 9465 // self-contained 9466 if (t.length && (t = t.text())) { 9467 return t; 9468 } 9469 // Opengraph title 9470 t = $('meta[property="og:title"]'); 9471 if (t.length && (t = t.attr('content'))) { 9472 return t; 9473 } 9474 // <title> tag 9475 return $('title').text() || ''; 9476 }, 9477 9478 // Trying to find a description 9479 getDefaultDescription = function() { 9480 var t = self.find('.description,.comment'); 9481 // self-contained 9482 if (t.length && (t = t.text())) { 9483 return t; 9484 } 9485 // Opengraph description 9486 t = $('meta[property="og:description"]'); 9487 if (t.length && (t = t.attr('content'))) { 9488 return t; 9489 } 9490 // <meta> description tag 9491 t = $('meta[name=description]'); 9492 if (t.length && (t = t.attr('content'))) { 9493 return t; 9494 } 9495 return ''; 9496 }; 9497 9498 // Reading smart defaults if missing 9499 9500 // URL 9501 if (!settings.hasOwnProperty('href')) { 9502 settings.href = window.location.href; 9503 } 9504 9505 // Image 9506 if (!settings.hasOwnProperty('image')) { 9507 var i = $('link[rel=image_src]'); 9508 settings.image = (i.length)? i.attr('href') : (window.location.href.getDir() + 'folderthumb.jpg'); 9509 } 9510 9511 // Title 9512 if (!settings.hasOwnProperty('title')) { 9513 settings.title = getDefaultTitle(); 9514 } 9515 9516 // Description 9517 if (!settings.hasOwnProperty('description')) { 9518 settings.description = getDefaultDescription(); 9519 } 9520 9521 // Page extension 9522 if (!settings.hasOwnProperty('pageExt')) { 9523 settings.pageExt = settings.indexName.getExt(); 9524 } 9525 9526 if (settings.usePreview) { 9527 // Complete share card with preview 9528 9529 // Wrapping element 9530 cont = self.find('.' + settings.contClass); 9531 9532 if (!cont.length) { 9533 cont = $('<div>', { 9534 'class': settings.contClass 9535 }).appendTo(self); 9536 } 9537 9538 // Image 9539 previewImage = cont.find('img'); 9540 9541 if (!previewImage.length) { 9542 if (settings['image']) { 9543 previewImage = $('<img>', { 9544 src: settings.image // Relative URL is fine 9545 }).appendTo(cont); 9546 } else { 9547 cont.addClass('no-image'); 9548 } 9549 } 9550 9551 // Caption 9552 shareCaption = cont.find('.' + settings.captionClass); 9553 9554 if (!shareCaption.length) { 9555 shareCaption = $('<div>', { 9556 'class': settings.captionClass, 9557 html: '<h3 class="title">' + settings.title + '</h3>' + 9558 (settings.description? ('<div class="description">' + settings.description + '</div>') : '') 9559 }).appendTo(cont); 9560 } 9561 9562 defaultCaption = shareCaption.html(); 9563 9564 // Social links wrap 9565 socialLinks = cont.find('.' + settings.socialLinksClass); 9566 9567 if (!socialLinks.length) { 9568 socialLinks = $('<div>', { 9569 'class': settings.socialLinksClass 9570 }).appendTo(cont); 9571 } 9572 9573 } else { 9574 // Simple share buttons, no card 9575 socialLinks = self; 9576 } 9577 9578 // Creating simple links 9579 createLinks(socialLinks); 9580 9581 // Creating button 9582 if (settings.hasOwnProperty('buttons') && !LOCAL) { 9583 createButtons(socialLinks); 9584 } 9585 9586 if (self.closest('.modal').length) { 9587 // On a modal window 9588 self.find('.' + settings.sharesClass + ' a').not('.copy-url-btn').on('click', function() { 9589 // Close modal on clicking a button 9590 $(this).closest('.modal').trigger('close'); 9591 return true; 9592 }); 9593 } 9594 9595 // External triggering 9596 self.on('updateLinks', function(e, opt) { 9597 updateLinks(opt); 9598 }); 9599 9600 return this; 9601 }; 9602 9603 $.fn.renderShares.defaults = { 9604 buttonLabels: false, 9605 usePreview: true,
9606 contClass: 'preview', 9607 captionClass: 'caption', 9608 socialLinksClass: 'social-links', 9609 sharesClass: 'shares', 9610 buttonsClass: 'buttons', 9611 btnClass: 'btn', 9612 sites: 'facebook,twitter,tumblr', 9613 indexName: 'index.html', 9614 currCardHook: '.lightbox .curr', 9615 emailSubject: '', 9616 emailBody: '', 9617 pos: [2,1,0,1] 9618 }; 9619 9620 $.fn.renderShares.text = { 9621 checkThisOut: "Found this page", 9622 copied: "Copied", 9623 slideshow: "Slideshow", 9624 copy: "Copy", 9625 email: "Email", 9626 shareOn: "Share on" 9627 }; 9628 9629})(jQuery); 9630 9631/* Generated structure 9632<div class="share-cont social-links text-center"> 9633 <div class="thumb"> 9634 <img src="http://path.to/image"> 9635 <div class="caption"> 9636 <h3>Title</h3> 9637 <div class="description">Description</div> 9638 </div> 9639 <div class="social-links"> 9640 <div class="shares"> 9641 <a class="btn icon-facebook" rel="noopener" data-tooltip-id="_ltt_1742"> </a> 9642 <a class="btn icon-twitter" rel="noopener" href="https://twitter.com/home?status=Patrik: http%3A%2F%2F127.0.0.1%3A8080%2FTiger%2FPeople%2Findex.html%23img%3DPatrik.jpg" target="_blank" data-tooltip-id="_ltt_4248"> </a> 9643 <a class="btn icon-email" rel="noopener" href="mailto:?subject=Check%20this%20out&body=Patrik%0D%0APeople%0D%0Ahttp://127.0.0.1:8080/Tiger/People/index.html#img=Patrik.jpg" target="_blank" data-tooltip-id="_ltt_6930"> </a> 9644 </div> 9645 </div> 9646 </div> 9647</div> 9648*/ 9649;(function($, $window) { 9650 'use strict'; 9651 9652 /* 9653 sticky.js - renders 'sticky' class to off-screen layers 9654 */ 9655 9656 $.fn.sticky = function(options) { 9657 9658 options = $.extend({ 9659 stickyClass: 'sticky', // Sticky class 9660 stickToEdge: 'st-stick', // The first/last element that need to be stick to edge class 9661 topmostClass: 'st-topmost', // First element but not stick to window top 9662 bottommostClass: 'st-bottommost', // First element but not stick to window top 9663 topClass: 'st-top', // Top class 9664 bottomClass: 'st-bottom', // Bottom class 9665 placeholderClass: 'st-placeholder', // Placeholder class 9666 controlsClass: 'st-controls', // Minimize/Maximize controls 9667 minimizedClass: 'minimized', 9668 maximizedClass: 'maximized', 9669 watch: '.thumbnails', // Only if this section is on screen 9670 watchThreshold: 100, // The height to be on screen to be considered as visible 9671 minimizableThreshold: 80, // Minimized controls added only above this height 9672 placeBelow: '.top-bar.sticky' // Extra top position to add above 9673 }, options); 9674 9675 var ns = 'lst_' + ($.fn.sticky._counter = ($.fn.sticky._counter || 0) + 1), 9676 st = [], 9677 last = -999, 9678 9679 // Make panel static 9680 9681 makeStatic = function(i) { 9682 var el = $('[data-sticky-rel=' + st[i].id + ']'), 9683 p = $('#' + st[i].id); 9684 9685 if (el.length && el.hasClass(options.stickyClass)) { 9686 // Hiding placeholder 9687 p.css('height', ''); 9688 // On screen 9689 el.removeClass(options.stickyClass + ' ' + options.topClass + ' ' + options.bottomClass); 9690 } 9691 }, 9692 9693 // Make all panels static 9694 9695 makeAllStatic = function() { 9696 9697 for (var i = 0; i < st.length; i++) { 9698 makeStatic(i); 9699 } 9700 }, 9701 9702 // Recalculate panel positions after minimize / maximize 9703 9704 recalcPos = function(bottom) { 9705 var el = bottom? 9706 $('.' + options.bottomClass + '.' + options.stickyClass + ':visible').backwards() 9707 : 9708 $('.' + options.topClass + '.' + options.stickyClass + ':visible'), 9709 offs = bottom? 0 : $(options.placeBelow + ':visible').outerHeight() || 0; 9710 9711 el.each(function() { 9712 $(this).css(bottom? 'bottom':'top', offs); 9713 offs += $(this).outerHeight(); 9714 }); 9715 }, 9716 9717 // Checking all elements 9718 9719 checkPos = function() { 9720 var wt = $window.scrollTop(), 9721 wh = $window.outerHeight(), 9722 watch = $(options.watch + ':visible'), 9723 offsTop, 9724 offsBtm, 9725 topmost, 9726 bottommost, 9727 wpt; 9728 9729 if (Math.abs(wt - last) <= 2) { 9730 // Already checked 9731 return; 9732 } 9733 9734 last = wt; 9735 9736 if (watch.length) { 9737 wpt = watch.position().top; 9738 } 9739 9740 if (!watch.length || (wpt > wt + wh - options.watchThreshold) || (wpt + watch.outerHeight() <= wt + options.watchThreshold)) { 9741 // No watch element or not on screen 9742 9743 makeAllStatic(); 9744 9745 } else { 9746 // Watch element is on screen 9747 9748 offsTop = $(options.placeBelow + ':visible').outerHeight() || 0; 9749 topmost = true; 9750 9751 // Calculating top position 9752 $('.' + options.topClass + '.' + options.stickyClass + ':visible').each(function() { 9753 offsTop += $(this).outerHeight(); 9754 topmost = false; 9755 });
9756 9757 offsBtm = 0; 9758 bottommost = true; 9759 9760 // Calculating bottom position 9761 $('.' + options.bottomClass + '.' + options.stickyClass + ':visible').each(function() { 9762 offsBtm += $(this).outerHeight(); 9763 bottommost = false; 9764 }); 9765 9766 for (var i = 0, el, p, pt; i < st.length; i++) { 9767 el = $('[data-sticky-rel=' + st[i].id + ']'); 9768 p = $('#' + st[i].id); 9769 9770 if (el.length && p.length) { 9771 // Panel's top position 9772 pt = p.position().top; 9773 9774 if (pt < (wt + offsTop) || (pt + st[i].height) > (wt + wh - offsBtm)) { 9775 // Place as sticky 9776 if (!el.hasClass(options.stickyClass)) { 9777 // Make the placeholder same height 9778 p.height(st[i].height); 9779 el.addClass(options.stickyClass); 9780 9781 if (pt < (wt + offsTop)) { 9782 // Above 9783 //pt = getPos(options.topClass); 9784 9785 if (offsTop) { 9786 // Offsetting already existing stickies 9787 el.removeClass(options.stickToEdge).css('top', offsTop); 9788 } else { 9789 el.addClass(options.stickToEdge); 9790 } 9791 9792 offsTop += st[i].height; 9793 9794 if (topmost) { 9795 el.addClass(options.topmostClass); 9796 topmost = false; 9797 } 9798 9799 el.addClass(options.topClass).removeClass(options.bottomClass); 9800 9801 } else { 9802 // Below 9803 el.addClass(options.bottomClass).removeClass(options.topClass); 9804 } 9805 } 9806 9807 } else { 9808 // Place as static 9809 makeStatic(i); 9810 } 9811 } 9812 } 9813 9814 offsBtm = 0; 9815 9816 $('.' + options.bottomClass + '.' + options.stickyClass).backwards().each(function() { 9817 $(this).css('bottom', offsBtm); 9818 9819 if (offsBtm) { 9820 $(this).removeClass(options.stickToEdge); 9821 } else { 9822 $(this).addClass(options.stickToEdge); 9823 } 9824 9825 offsBtm += $(this).outerHeight(); 9826 9827 if (bottommost) { 9828 $(this).addClass(options.bottommostClass); 9829 bottommost = false; 9830 } 9831 });
9832 } 9833 9834 return true; 9835 }; 9836 9837 // Preprocessing elements 9838 this.each(function(i) { 9839 var el = $(this), 9840 cont = el.children().first(), 9841 id = ns + '_' + i, 9842 h = el.outerHeight(); 9843 9844 el.before($('<div>', { 9845 id: id, 9846 'class': options.placeholderClass 9847 })); 9848 9849 if (h > options.minimizableThreshold) { 9850 cont.prepend($('<div>', { 9851 'class': options.controlsClass 9852 }).append($('<a>', { 9853 'class': 'minimize' 9854 }).on('click', (function(i) { 9855 return function() { 9856 el.removeClass(options.maximizedClass).addClass(options.minimizedClass); 9857 st[i].height = el.outerHeight(); 9858 recalcPos(el.hasClass(options.bottomClass)); 9859 }; 9860 }(i)))).append($('<a>', { 9861 'class': 'maximize' 9862 }).on('click', function() { 9863 el.removeClass(options.minimizedClass).addClass(options.maximizedClass); 9864 recalcPos(el.hasClass(options.bottomClass)); 9865 })) 9866 ); 9867 } 9868 9869 el.attr('data-sticky-rel', id).addClass(options.maximizedClass); 9870 9871 st.push({ 9872 id: id, 9873 height: h 9874 }); 9875 }); 9876 9877 var els = $(this); 9878 9879 els.on('stickyRefresh', function() { 9880 // Re-measure heights 9881 els.each(function(i) { 9882 st[i].height = $(this).outerHeight(); 9883 }); 9884 last = -999; 9885 checkPos(); 9886 }); 9887 9888 // Scroll event listener 9889 $window.on('scroll.' + ns + ' resize.' + ns, function() { 9890 checkPos(); 9891 }); 9892 9893 // Initial check 9894 checkPos(); 9895 9896 return this; 9897 }; 9898 9899}(jQuery, jQuery(window))); 9900/* 9901 * swipe() :: Swipe action using CSS transforms with jQuery position fallback 9902 * 9903 * Copyright by Lazaworx 9904 * http://www.lazaworx.com 9905 * Author: Laszlo Molnar 9906 * 9907 * Dual licensed under the MIT and GPL licenses. 9908 * - http://www.opensource.org/licenses/mit-license.php 9909 * - http://www.gnu.org/copyleft/gpl.html 9910 * 9911 * Usage: $(element).swipe( options ); 9912 */ 9913 9914;(function($) { 9915 'use strict'; 9916 9917 $.fn.swipe = function(options) { 9918 9919 options = $.extend({ 9920 onSwipedLeft: false, // Swiped left event 9921 onSwipedRight: false, // Swiped right event 9922 onSwipedUp: false, // Swiped up event 9923 onSwipedDown: false, // Swiped down event 9924 onSmallSwipe: false, // Small swipe (< threshold px) 9925 onSwipeStart: false, // Start swiping event 9926 onPinchZoom: false, // Pinch zoomed event 9927 onClick: false, // Clicked 9928 onDoubleClick: false, // Doble-clicked 9929 onFinished: false, // Swiped out - can remove 9930 threshold: 20, // Absolute floor (px) for the swipe distance 9931 tapTolerance: 10, // Max finger travel (px) still counted as a tap (click / double-tap) 9932 minSwipeFraction: 0.06, // Min swipe travel to navigate, as fraction of stage width (avoids accidental navigation); 0 disables 9933 clickThreshold: 150, // Timespan to tell apart single and double click 9934 doubleClickThreshold: 350, // Two clicks come in shorter time treated as double-click 9935 noSwipeInside: '.caption', // Avoid swipe inside this container 9936 stopAutoPano: true, // Stops autopano move, and doesn't trigger click if done so 9937 swipeoutDuration: 300, // Swipe out speed in ms 9938 avoidNavigationOnZoomed: true, // Allow navigation on large images 9939 overThreshold: 0.2, // Oversized pictures must moved beyond (this * screen width) to trigger swipe 9940 minZoomFraction: 0.4, // Min zoom: image may shrink to this fraction of the stage (fit dimension) 9941 maxZoomNatural: 2 // Max zoom: this * the image's natural pixel size (falls back to * fit size if natural size is unknown) 9942 }, options); 9943 9944 // Getting coordinates of a touch or mouse event 9945 var getDim = function(el) { 9946 return [ el.outerWidth(), el.outerHeight() ]; 9947 }, 9948 9949 getPos = function(e) { 9950 if (e.touches) { 9951 if (e.touches.length > 1) { 9952 // Two finger event 9953 return [ e.touches[0].clientX, e.touches[0].clientY, e.touches[1].clientX, e.touches[1].clientY ]; 9954 } 9955 // Single finger event 9956 return [ e.touches[0].clientX, e.touches[0].clientY ]; 9957 } 9958 // mouse event 9959 return (e.clientX === null)? null : [ e.clientX, e.clientY ]; 9960 }; 9961 9962 return this.each(function() { 9963 9964 var self = $(this), // target element 9965 cont = self.parent(), // container or clipping window 9966 ns, // namespace 9967 rto, // window resize update timeout 9968 // Dimensions 9969 selfDim, // item dimensions 9970 clipDim, // clip dimensions 9971 scale, // scaling 9972 minScale, // min total scale allowed this gesture 9973 maxScale, // max total scale allowed this gesture 9974 panRect, // image edges relative to container at gesture start 9975 // Event timestamps 9976 startTime, // start time 9977 lastTime, // previous event time (to calculate speed) 9978 endTime, // end time 9979 // Event coordinates (screen-relative) 9980 startPos, // initial position Array[2..4] 9981 currPos, // Current position 9982 imgStartCenter, // Visual center of image at swipe start 9983 // Etc 9984 speed, // move speed [ vx, vy ] in px/ms 9985 startD, // initial distance of two finger events 9986 currD, 9987 mx, // original matrix 9988 // Flags 9989 hasStarted = false, // flag to avoid double start on touch devices 9990 hasEnded = true, // ended swiping 9991 swipeHandled = true, // swipe event already fired 9992 allowNavigation = false, // Allow navigation (true if small, pano or settings.avoidNavigationOnZoomed) 9993 dontSwipeUp = true, // limit vertical swipe 9994 dontSwipeDown = true, 9995 canConstrainHorz = false, // Can constrain to horizontal 9996 nonSwipeable = true, // target element swipeable? 9997 clickedOnce = false, // Check for double click 9998 hadAutoPano = false, // Remembering if autopano was on 9999 chkDoubleClick = null, // Checking double-click (timeout) 10000 10001 // Handling swipe start event 10002 handleSwipeStart = function(e) { 10003 if (typeof options.onSwipeStart === FUNCTION) { 10004 options.onSwipeStart.call(self[0], e); 10005 } 10006 }, 10007 10008 // Handling swipeout movement finished event 10009 handleFinished = function(e) { 10010 //log('handleFinished'); 10011 self.data('scrolling', false); 10012 if (typeof options.onFinished === FUNCTION) { 10013 options.onFinished.call(self[0], e); 10014 } 10015 }, 10016 10017 // Handling small swipe 10018 handleSmallSwipe = function(e) { 10019 //log('handleSmallSwipe'); 10020 unregisterEvents(e); 10021 self.data('scrolling', false); 10022 if (typeof options.onSmallSwipe === FUNCTION) { 10023 options.onSmallSwipe.call(self[0], e); 10024 } 10025 }, 10026 10027 // Handling click event 10028 handleClick = function(e) { 10029 //log('handleClick'); 10030 unregisterEvents(e); 10031 self.data('scrolling', false); 10032 if (!hadAutoPano && typeof options.onClick === FUNCTION) { 10033 // Avoid click when stopping panomove consumed the event 10034 options.onClick.call(self[0], e); 10035 } 10036 }, 10037 10038 // Handling doubleclick event 10039 handleDoubleClick = function(e) { 10040 //log('handleDoubleClick'); 10041 unregisterEvents(e); 10042 self.data('scrolling', false); 10043 if (typeof options.onDoubleClick === FUNCTION) { 10044 options.onDoubleClick.call(self[0], e); 10045 } 10046 }, 10047 10048 // Handling swipe 10049 handleSwipedDown = function(e) { 10050 //log('handleSwipe:'+(right?'right':'left')); 10051 if (!swipeHandled && typeof options.onSwipedDown === FUNCTION) { 10052 unregisterEvents(e); 10053 swipeHandled = true; 10054 // Swiped down 10055 options.onSwipedDown.call(self[0], e); 10056 } 10057 }, 10058 10059 // Handling swipe 10060 handleSwipedUp = function(e) { 10061 //log('handleSwipe:'+(right?'right':'left')); 10062 if (!swipeHandled && typeof options.onSwipedUp === FUNCTION) { 10063 unregisterEvents(e); 10064 swipeHandled = true; 10065 // Swiped up
10066 options.onSwipedUp.call(self[0], e); 10067 } 10068 }, 10069 10070 // Handling swipe 10071 handleSwipedRight = function(e) { 10072 //log('handleSwipe:'+(right?'right':'left')); 10073 if (!swipeHandled && typeof options.onSwipedRight === FUNCTION) { 10074 unregisterEvents(e); 10075 swipeHandled = true; 10076 // Swiped right 10077 options.onSwipedRight.call(self[0], e); 10078 } 10079 }, 10080 10081 // Handling swipe 10082 handleSwipedLeft = function(e) { 10083 //log('handleSwipe:'+(right?'right':'left')); 10084 if (!swipeHandled && typeof options.onSwipedLeft === FUNCTION) { 10085 unregisterEvents(e); 10086 swipeHandled = true; 10087 // Swiped left 10088 options.onSwipedLeft.call(self[0], e); 10089 } 10090 }, 10091 10092 // Handling pinch zoom 10093 handlePinchZoom = function(e) { 10094 //log('handlePinchZoom:'+(right?'right':'left')); 10095 if (!swipeHandled && typeof options.onPinchZoom === FUNCTION) { 10096 unregisterEvents(e); 10097 swipeHandled = true; 10098 // Swiped left 10099 options.onPinchZoom.call(self[0], e); 10100 } 10101 }, 10102 10103 // Handling right click event 10104 handleRightClick = function(e) { 10105 //log('handleRightClick'); 10106 unregisterEvents(e); 10107 if (typeof options.onRightClick === FUNCTION) { 10108 options.onRightClick.call(self[0], e); 10109 } 10110 }, 10111 10112 // Registering events 10113 registerEvents = function(e) { 10114 //log('registerEvents'); 10115 if (e.type === 'mousedown') { 10116 self.on('mouseup.' + ns + ' mouseout.' + ns, swipeEnd); 10117 $(document).on('mousemove.' + ns, swipeMove); 10118 } else if (e.type === TOUCH.START) { 10119 self.on(TOUCH.END + '.' + ns, swipeEnd); 10120 } 10121 }, 10122 10123 unregisterEvents = function(e) { 10124 //log('unregisterEvents'); 10125 if (e.type === 'mouseup' || e.type === 'mouseout') { 10126 self.off('mouseup.' + ns + ' mouseout.' + ns); 10127 $(document).off('mousemove.' + ns); 10128 } else if (e.type === TOUCH.END) { 10129 self.off(TOUCH.END + '.' + ns); 10130 } 10131 }, 10132 10133 // Getting distance of two finger events 10134 getDistance = function(p) { 10135 return Math.hypot(p[2] - p[0], p[3] - p[1]); 10136 }, 10137 10138 // Move to 10139 // p = event coordinates (2 = one finger, 4 = 2 finger gesture) 10140 // op = opacity (null = don't care 10141 // duration = transition speed (ms) 10142 // doneFn = callback 10143 transformTo = function(p, op, duration, doneFn) { 10144 var css = {}, 10145 dx, 10146 dy; 10147 10148 if (p.length === 4) { 10149 // --- 2 Finger Move (Pinch Zoom) --- 10150 10151 // 1. Scale relative to the start of this gesture 10152 scale = getDistance(p) / startD; 10153 10154 // 1b. Clamp the *total* zoom to the configured min / max 10155 var totalScale = mx[0] * scale; 10156 if (totalScale < minScale) { 10157 totalScale = minScale; 10158 } else if (maxScale && totalScale > maxScale) { 10159 totalScale = maxScale; 10160 } 10161 scale = totalScale / mx[0]; 10162 10163 // 2. Get Finger Center 10164 var currCX = (p[0] + p[2]) / 2, 10165 currCY = (p[1] + p[3]) / 2, 10166 startCX = (startPos[0] + startPos[2]) / 2, 10167 startCY = (startPos[1] + startPos[3]) / 2; 10168 10169 // 3. Calculate Pan (Finger movement) 10170 dx = currCX - startCX; 10171 dy = currCY - startCY; 10172 10173 // 4. Focal Point Fix 10174 // We use the cached imgStartCenter calculated in swipeStart. 10175 // This ensures we pull the image based on where it ACTUALLY was when you started pinching. 10176 dx += (startCX - imgStartCenter[0]) * (1 - scale); 10177 dy += (startCY - imgStartCenter[1]) * (1 - scale); 10178 10179 } else { 10180 // --- 1 Finger Move --- 10181 dx = p[0] - startPos[0]; 10182 dy = p[1] - startPos[1]; 10183 } 10184 10185 css.transform = 'matrix(' + (mx[0] * scale) + ',' + mx[1] + ',' + mx[2] + ',' + (mx[3] * scale) + ',' + (mx[4] + dx) + ',' + (mx[5] + dy) + ')'; 10186 10187 if (typeof duration !== UNDEF && duration) { 10188 css.transition = 'transform ' + duration + 'ms ease-in-out'; 10189 if (op !== null) { 10190 css.transition += ',opacity ' + duration + 'ms ease-in-out'; 10191 css.opacity = op; 10192 } 10193 } else {
10194 css.transition = 'none'; 10195 } 10196 10197 if (typeof doneFn === FUNCTION) { 10198 self.one('transitionend', doneFn); 10199 } 10200 10201 self.css(css); 10202 }, 10203 10204 // Starting swipe 10205 swipeStart = function(e) { 10206 //log('swipeStart:' + e.type); 10207 10208 //log('Touch start, fingers: ' + (e.originalEvent.touches? e.originalEvent.touches.length : 0)); 10209 if (pageZoomed() || 10210 e.originalEvent.touches && e.originalEvent.touches.length > 2 || 10211 (options.noSwipeInside && $(e.target).closest(options.noSwipeInside).length) || 10212 e.target.nodeName === 'A' || 10213 e.target.nodeName === 'INPUT' || 10214 e.target.nodeName === 'BUTTON' || 10215 e.target.nodeName === 'AUDIO' || 10216 (e.target.nodeName === 'VIDEO' && 10217 (!e.target.paused || 10218 e.target.controls && 10219 (ISIOSDEVICE || e.offsetY > ($(e.target).height() - 60)) 10220 ) 10221 ) 10222 ) { 10223 // Don't swipe buttons and links and other non-swipeable elements 10224 // No swiping on iOS video player 10225 nonSwipeable = true; 10226 return true; 10227 } 10228 10229 if (e.type === 'mousedown' && e.which !== 1) { 10230 // Right click 10231 if (options.stopAutoPano && cont.data('panomove')) { 10232 self.trigger('autopanoStop'); 10233 } 10234 handleRightClick(e); 10235 return true; 10236 } 10237 10238 if (hasStarted) { 10239 // No mouseup / touchend registered yet 10240 return false; 10241 } 10242 10243 if (e.type !== 'touchstart') { 10244 // Cancel bubbling, but not break long tap 10245 e.preventDefault(); 10246 } 10247 10248 hasStarted = true; 10249 nonSwipeable = false; 10250 hasEnded = false; 10251 // Cancel any pending single-click timer from a previous tap so it 10252 // can't fire mid-gesture, but KEEP clickedOnce so a quick second 10253 // tap is still recognised as a double-tap. 10254 if (chkDoubleClick) { 10255 clearTimeout(chkDoubleClick); 10256 chkDoubleClick = null; 10257 } 10258 10259 if (options.stopAutoPano && cont.data('panomove')) { 10260 self.trigger('autopanoStop'); 10261 hadAutoPano = true; 10262 } else { 10263 hadAutoPano = false; 10264 } 10265 10266 self.data({ 10267 scrolling: true, 10268 swipeEnded: false, 10269 taplength: 0 10270 });
10271 10272 startTime = lastTime = new Date().getTime(); 10273 startPos = getPos(e.originalEvent); 10274 currPos = startPos.slice(); 10275 speed = [ 0, 0 ]; 10276 if (startPos.length === 4) { 10277 startD = getDistance(startPos); 10278 } 10279 clipDim = getDim(cont); 10280 selfDim = getDim(self); 10281 mx = self.getMatrix(); 10282 scale = 1; 10283 10284 // Zoom limits for this gesture (absolute total scale, relative to layout size). 10285 // minScale: image may shrink to minZoomFraction of the stage in its fit dimension. 10286 // maxScale: maxZoomNatural * (natural px / layout px) so it caps at e.g. 300% of 10287 // the real pixels; if natural size is unknown, cap at maxZoomNatural * fit. 10288 var fitRatio = Math.min(clipDim[0] / selfDim[0], clipDim[1] / selfDim[1]), 10289 media = (self[0].naturalWidth || self[0].videoWidth) ? self[0] 10290 : (self.find('img,video')[0] || self[0]), 10291 natW = media.naturalWidth || media.videoWidth || 0; 10292 minScale = Math.min(1, options.minZoomFraction * fitRatio); 10293 maxScale = Math.max(1, natW ? options.maxZoomNatural * natW / selfDim[0] 10294 : options.maxZoomNatural); 10295 10296 // Image's actual visual center, straight from its rendered box. 10297 // Includes the current transform, so it's correct at any zoom level 10298 // and independent of transform-origin and the left/top/translate trick. 10299 var srect = self[0].getBoundingClientRect(); 10300 imgStartCenter = [ 10301 srect.left + srect.width / 2, 10302 srect.top + srect.height / 2 10303 ]; 10304 10305 // Image edges relative to the container, for the boundary / over-swipe 10306 // math. Measured from the live rendered boxes, so it's correct for any 10307 // transform-origin and any CSS centering trick (no scale/translate model 10308 // assumptions). Translation maps 1:1 to px, so a pan of d shifts every 10309 // edge by d. 10310 var crect = cont[0].getBoundingClientRect(); 10311 panRect = { 10312 l: srect.left - crect.left, 10313 t: srect.top - crect.top, 10314 r: srect.right - crect.left, 10315 b: srect.bottom - crect.top, 10316 w: srect.width, 10317 h: srect.height 10318 }; 10319 10320 canConstrainHorz = hadAutoPano || clipDim[1] >= selfDim[1] * mx[3] && clipDim[0] >= selfDim[0] * mx[0]; 10321 //log('wh: ' + clipDim[1] + ' ih: ' + selfDim[1] + ' sc: ' + mx[3].toFixed(2)); 10322 allowNavigation = !(options.avoidNavigationOnZoomed && (clipDim[1] < selfDim[1] * mx[3] || clipDim[0] < selfDim[0] * mx[0])) 10323 || 10324 canConstrainHorz; 10325 10326 if (canConstrainHorz) { 10327 dontSwipeUp = typeof options.onSwipedUp !== FUNCTION; 10328 dontSwipeDown = typeof options.onSwipedDown !== FUNCTION; 10329 } else { 10330 dontSwipeUp = dontSwipeDown = false; 10331 } 10332 10333 registerEvents(e); 10334 handleSwipeStart(e); 10335 swipeHandled = false; 10336 // Do not cancel long tap 10337 return e.type === 'touchstart'; 10338 }, 10339 10340 // Moving in progress 10341 swipeMove = function(e) { 10342 //log('swipeMove:' + e.type + ' [' + e.originalEvent.touches.length + ']'); 10343 10344 if (hasEnded || nonSwipeable || 10345 e.originalEvent.touches && e.originalEvent.touches.length > 2) { 10346 // not active, prohibited area or three finger touch 10347 return true; 10348 } 10349 10350 e.preventDefault(); 10351 10352 var lastX = currPos? currPos[0] : 0, // Saving previous position as values 10353 lastY = currPos? currPos[1] : 0, 10354 t = new Date().getTime(), 10355 dt = (t - lastTime) || 1; 10356 10357 currPos = getPos(e.originalEvent); 10358 10359 if (startPos === null || startPos.length < currPos.length) { 10360 // No swipestart happened: init startPos / startD now 10361 for (var i = startPos.length || 0; i < currPos.length; i++) { 10362 startPos[i] = currPos[i]; 10363 } 10364 startD = getDistance(currPos); 10365 } 10366 10367 if (dontSwipeUp && dontSwipeDown) { 10368 // Horizontal-only 10369 currPos[1] = startPos[1]; 10370 // Speed: Smoothing with 2/3 : 1/3 10371 speed[0] = (speed[0] + 2 * (currPos[0] - lastX) / dt) / 3; 10372 10373 } else { 10374 var dx = currPos[0] - startPos[0], 10375 dy = currPos[1] - startPos[1], 10376 d = Math.abs(dx), 10377 a = dy !== 0 ? Math.abs(dx / dy) : Infinity; 10378 10379 // Can move both axes 10380 if (canConstrainHorz && (d > 30 || d > 8 && a > 0.25)) { 10381 // Change to horizontal-only swipe 10382 dontSwipeUp = dontSwipeDown = true; 10383 } 10384 10385 if (dontSwipeUp && currPos[1] < startPos[1] || 10386 dontSwipeDown && currPos[1] > startPos[1]) { 10387 currPos[1] = startPos[1]; 10388 } 10389 10390 speed = [ 10391 (speed[0] + 2 * (currPos[0] - lastX) / dt) / 3, 10392 (speed[1] + 2 * (currPos[1] - lastY) / dt) / 3 10393 ]; 10394 } 10395 10396 lastTime = t; 10397 transformTo(currPos); 10398 return false; 10399 }, 10400 10401 // Cancel swipe (e.g. moved out of the screen) 10402 swipeCancel = function(e) { 10403 //log('swipeCancel:' + e.type); 10404 10405 hasEnded = true; 10406 hasStarted = false; 10407 10408 unregisterEvents(e); 10409 10410 setTimeout(function() { 10411 self.data({ 10412 scrolling: false, 10413 swipeEnded: true 10414 });
10415 }, 100); 10416 10417 if (startPos) { 10418 transformTo(startPos, null, 100); 10419 } 10420 10421 return false; 10422 }, 10423 10424 // Stopped moving or clicked : mouse/touch up 10425 swipeEnd = function(e) { 10426 //log('swipeEnd:' + e.type); 10427 if (nonSwipeable) { 10428 // Prohibited area 10429 return true; 10430 } 10431 10432 e.preventDefault(); 10433 10434 hasStarted = false; 10435 hasEnded = true; 10436 10437 //currPos = getPos(e.originalEvent); 10438 10439 if (currPos.length === 4) { 10440 handlePinchZoom(e); 10441 } else { 10442 var dx = currPos[0] - startPos[0], 10443 dy = currPos[1] - startPos[1], 10444 dt = new Date().getTime() - startTime, 10445 dist = Math.hypot(dx, dy), 10446 moveThreshold = Math.max(options.threshold, options.minSwipeFraction * clipDim[0]), 10447 w = selfDim[0] * scale * mx[0], 10448 h = selfDim[1] * scale * mx[3]; 10449 10450 if ((dt > 50) && (dist > moveThreshold)) { 10451 // Not a click, because it took longer or the distance is larger than the threshold 10452 10453 // Setting data flags 10454 self.data({ 10455 scrolling: true, 10456 swipeEnded: true, 10457 taplength: dt 10458 }); 10459 10460 if (dontSwipeUp && dontSwipeDown) { 10461 // Constrain horizontally 10462 var extra = speed[0] * options.swipeoutDuration / 2, // extra travel after end event 10463 slackX = options.overThreshold * clipDim[0], 10464 pL = panRect.l + dx + extra, // prospective edges (container coords) 10465 pR = panRect.r + dx + extra; 10466 // Linear slowing: dx = v * t / 2; 10467 10468 if ( (clipDim[0] >= panRect.w) || 10469 (dx && 10470 ( 10471 (pL > slackX) || // gap opened on the left 10472 (pR < clipDim[0] - slackX) // gap opened on the right 10473 ) 10474 ) 10475 ) { 10476 // Swiped => move and fade out 10477 transformTo([ currPos[0] + extra, startPos[1] ], 0, options.swipeoutDuration, handleFinished); 10478 10479 if (allowNavigation) { 10480 if (dx > 0) { 10481 handleSwipedRight(e); 10482 } else { 10483 handleSwipedLeft(e); 10484 } 10485 } 10486 10487 } else { 10488 // Panoramic :: Hasn't reached the edge yet 10489 transformTo([ currPos[0] + extra, startPos[1] ], null, options.swipeoutDuration); 10490 unregisterEvents(e); 10491 } 10492 10493 } else { 10494 // No constrain 10495 var vertical = Math.abs(dy) > 1.5 * Math.abs(dx), // max 30 degrees 10496 extra = [ // extra = extra travel after end event 10497 speed[0] * options.swipeoutDuration / 2, 10498 speed[1] * options.swipeoutDuration / 2 10499 ], 10500 off = [ 0, 0 ], // Amount of moving over the boundaries 10501 // Prospective image edges in container coords after this gesture 10502 slackX = options.overThreshold * clipDim[0], 10503 slackY = options.overThreshold * clipDim[1], 10504 pL = panRect.l + dx + extra[0], 10505 pR = panRect.r + dx + extra[0], 10506 pT = panRect.t + dy + extra[1], 10507 pB = panRect.b + dy + extra[1]; 10508 10509 // off = how far an edge has crossed its allowed boundary (incl. slack). 10510 // Only meaningful when the image is larger than the container on that 10511 // axis; smaller images are treated as swipes further down. 10512 if (panRect.w > clipDim[0]) { 10513 if (pL > slackX) { 10514 off[0] = pL - slackX; // gap on the left 10515 } else if (pR < clipDim[0] - slackX) { 10516 off[0] = pR - (clipDim[0] - slackX); // gap on the right 10517 } 10518 } 10519 if (panRect.h > clipDim[1]) { 10520 if (pT > slackY) { 10521 off[1] = pT - slackY; // gap on the top 10522 } else if (pB < clipDim[1] - slackY) { 10523 off[1] = pB - (clipDim[1] - slackY); // gap on the bottom 10524 } 10525 } 10526 10527 if (w < clipDim[0] * 1.1 || h < clipDim[1] * 1.1) { 10528 // Smaller than screen at least in one direction and swiped over the boundaries 10529 if (allowNavigation) { 10530 if (typeof options.onSwipedDown === FUNCTION && vertical && dy > 0) { 10531 // Swiped down 10532 transformTo([ startPos[0], currPos[1] + extra[1] ], 0, options.swipeoutDuration, handleFinished); 10533 handleSwipedDown(e); 10534 } else if (typeof options.onSwipedUp === FUNCTION && vertical && dy < 0) { 10535 // Swiped up 10536 transformTo([ startPos[0], currPos[1] + extra[1] ], 0, options.swipeoutDuration, handleFinished); 10537 handleSwipedUp(e); 10538 } else { 10539 // Horizontal swipe or vertical but no up or down swipe handler 10540 transformTo([ currPos[0] + extra[0], startPos[1] ], 0, options.swipeoutDuration, handleFinished); 10541 if (dx > 0) { 10542 handleSwipedRight(e); 10543 } else { 10544 handleSwipedLeft(e); 10545 } 10546 } 10547 } else { 10548 transformTo([ currPos[0] + extra[0] - off[0], currPos[1] + extra[1] - off[1] ], 1, options.swipeoutDuration); 10549 unregisterEvents(e); 10550 } 10551 10552 } else { 10553 if (off[0] || off[1]) { 10554 if (allowNavigation) { 10555 if (typeof options.onSwipedDown === FUNCTION && vertical && off[1] > clipDim[1] / 2) { 10556 // Swiped down 10557 transformTo([ startPos[0], currPos[1] + extra[1] ], 0, options.swipeoutDuration, handleFinished); 10558 handleSwipedDown(e); 10559 } else if (typeof options.onSwipedUp === FUNCTION && vertical && (-off[1]) > clipDim[1] / 2) { 10560 // Swiped up 10561 transformTo([ startPos[0], currPos[1] + extra[1] ], 0, options.swipeoutDuration, handleFinished); 10562 handleSwipedUp(e); 10563 } else if (Math.abs(off[0]) > clipDim[0] / 2) { 10564 // Horizontal swipe or vertical but no up or down swipe handler 10565 transformTo([ currPos[0] + extra[0], startPos[1] ], 0, options.swipeoutDuration, handleFinished); 10566 if (dx > 0) { 10567 handleSwipedRight(e); 10568 } else { 10569 handleSwipedLeft(e); 10570 } 10571 } else {
10572 // Rebounce to boundary 10573 transformTo([ currPos[0] + extra[0] - off[0], currPos[1] + extra[1] - off[1] ], null, options.swipeoutDuration); 10574 unregisterEvents(e); 10575 } 10576 } else { 10577 // Rebounce to boundary 10578 transformTo([ currPos[0] + extra[0] - off[0], currPos[1] + extra[1] - off[1] ], null, options.swipeoutDuration); 10579 unregisterEvents(e); 10580 } 10581 10582 } else { 10583 // No need to limit; continue swiping 10584 transformTo([ currPos[0] + extra[0], currPos[1] + extra[1] ], null, options.swipeoutDuration); 10585 unregisterEvents(e); 10586 } 10587 } 10588 } 10589 10590 clickedOnce = false; 10591 10592 } else if (dist > options.tapTolerance) { 10593 // Moved more than a tap but not far enough to navigate: 10594 // rebound, and don't trigger a click or navigation 10595 if (chkDoubleClick) { 10596 clearTimeout(chkDoubleClick); 10597 chkDoubleClick = null; 10598 } 10599 clickedOnce = false; 10600 handleSmallSwipe(e); 10601 transformTo(startPos, null, 100); 10602 10603 } else if (options.onDoubleClick && clickedOnce) { 10604 // Second clean tap inside the window: double-tap. 10605 // (clickedOnce is only still true if the single-click 10606 // timer hasn't fired, i.e. we're within doubleClickThreshold.) 10607 clickedOnce = false; 10608 handleDoubleClick(e); 10609 10610 } else if (options.onDoubleClick) { 10611 // First clean tap: tear the listeners down and wait to see 10612 // whether a second tap follows before firing a single click 10613 clickedOnce = true; 10614 unregisterEvents(e); 10615 chkDoubleClick = setTimeout(function() { 10616 chkDoubleClick = null; 10617 clickedOnce = false; 10618 handleClick(e); 10619 }, options.doubleClickThreshold); 10620 10621 } else { 10622 // No double-click handler: fire the single click immediately 10623 handleClick(e); 10624 } 10625 } 10626 10627 return false; 10628 }, 10629 10630 noAction = function(e) { 10631 e.preventDefault(); 10632 return false; 10633 }, 10634 10635 // Tearing off the swipe handler 10636 removeSwipe = function() { 10637 setTimeout(function() { 10638 self.data('scrolling', false); 10639 }, 20); 10640 self.removeAttr('draggable'); 10641 self.add($(document)).off('.' + ns); 10642 }; 10643 10644 // Starts here 10645 ns = self.data('lsw_ns'); 10646 10647 if (ns) { 10648 // Clean up events that may hang around (in theory this should never happen) 10649 self.add($(document)).off('.' + ns); 10650 } 10651 10652 // Storing new namespace 10653 self.data('lsw_ns', ns = getUniqueId('lsw')); 10654 10655 // Need to be updated about the window size 10656 $(window).on('resize.' + ns, function() { 10657 clearTimeout(rto); 10658 rto = setTimeout(function() { 10659 clipDim = getDim(cont); 10660 }, 50); 10661 }); 10662 10663 self.attr('draggable', 'false') 10664 .on(TOUCH.START + '.' + ns + ' dragstart.' + ns + ' mousedown.' + ns, swipeStart) 10665 //.on(TOUCH.CANCEL + '.' + ns + ' dragcancel.' + ns, swipeCancel) 10666 .on(TOUCH.MOVE + '.' + ns + ' drag.' + ns, swipeMove) 10667 // External remove request 10668 .on('removeSwipe.' + ns, removeSwipe) 10669 // Do not select 10670 .on('selectstart.' + ns, noAction); 10671 }); 10672 }; 10673 10674})(jQuery); 10675/* 10676 * laza.autopano.js - auto panorama viewer 10677 * jslint browser: true 10678 */ 10679 10680;(function($, $document) { 10681 'use strict'; 10682 // Requires laza.util 10683 10684 $.fn.autopano = function(settings) { 10685 10686 settings = $.extend({}, $.fn.autopano.defaults, settings); 10687 10688 var self = $(this), // Element 10689 cont = self.parent(), // Container 10690 exposedStart = false, // Left end reached 10691 exposedEnd = false, // Right end reached 10692 direction = 0, // Current direction 10693 maxDur = 1600 / settings.speed, // Max. duration (40s - 20s - 10s) 10694 minDur = 120 / settings.speed, // Min. duration (3s - 1.5s - 0.75s) 10695 size, // Element size 10696 screenSize, // Container size 10697 vertical = false, // Move axis 10698 rto, // Resize timeout 10699 ns = self.data('lap_ns'), // Name space 10700 10701 // Initializing 10702 10703 init = function() { 10704 10705 // Updating window size 10706 10707 $(window).on('resize.' + ns, function() { 10708 clearTimeout(rto);
10709 rto = setTimeout(updateDims, 50, false); 10710 return true; 10711 }); 10712 10713 // External events 10714 10715 self.data('hasAutoPano', true) 10716 .on('autopanoRemove.' + ns, function() { 10717 direction = 0; 10718 remove(); 10719 return false; 10720 }) 10721 .on('autopanoStart.' + ns, function() { 10722 startAuto(); 10723 return false; 10724 }) 10725 .on('autopanoStop.' + ns, function() { 10726 direction = 0; 10727 stopMove(); 10728 return false; 10729 }) 10730 .on('autopanoAdjust', function() { 10731 updateDims(false); 10732 }); 10733 10734 updateDims(true); 10735 }, 10736 10737 // Update dimensions 10738 10739 updateDims = function(first) { 10740 var mx = self.getMatrix(), 10741 ew = self[0].scrollWidth, 10742 eh = self[0].scrollHeight, 10743 cw = cont[0].clientWidth, 10744 ch = cont[0].clientHeight; 10745 10746 if (ew / cw < eh / ch) { 10747 // Vertical 10748 size = eh; 10749 screenSize = ch; 10750 if (!vertical) { 10751 // Changed direction 10752 vertical = true; 10753 if (!first) { 10754 mx[4] = - ew / 2; 10755 self.css({ 10756 transition: 'none', 10757 transform: 'matrix(' + mx.join(',') + ')' 10758 }); 10759 } 10760 } 10761 } else { 10762 // Horizontal 10763 size = ew; 10764 screenSize = cw; 10765 if (vertical) { 10766 // Changed direction 10767 vertical = false; 10768 if (!first) { 10769 mx[5] = - eh / 2; 10770 self.css({ 10771 transition: 'none', 10772 transform: 'matrix(' + mx.join(',') + ')' 10773 }); 10774 } 10775 } 10776 } 10777 10778 if (size > screenSize) { 10779 //console.log('Dimensions updated'); 10780 if (first || !direction) { 10781 // Start if wider/taller than screen 10782 setTimeout(startAuto, 100); 10783 } else if (direction > 0) { 10784 moveToEnd(); 10785 } else { 10786 moveToStart(); 10787 } 10788 } else { 10789 // Stop if smaller 10790 stopMove(); 10791 } 10792 }, 10793 10794 // Remove autopano 10795 10796 remove = function() { 10797 10798 stopMove(); 10799 10800 setTimeout(function() { 10801 cont.data('panomove', false); 10802 }, 20); 10803 10804 self.removeAttr('draggable'); 10805 self.add($document).off('.' + ns); 10806 }, 10807 10808 // Reached the start 10809 10810 reachedStart = function(e) { 10811 //eventLog(e); 10812 exposedStart = true; 10813 10814 if (direction) { 10815 if (!exposedEnd || settings.loop) { 10816 moveToEnd(); 10817 } else { 10818 stopMove(); 10819 } 10820 } 10821 }, 10822 10823 // Reached the end 10824 10825 reachedEnd = function(e) { 10826 //eventLog(e); 10827 exposedEnd = true; 10828 10829 if (direction) { 10830 if (!exposedStart || settings.loop) { 10831 moveToStart(); 10832 } else { 10833 stopMove(); 10834 } 10835 } 10836 }, 10837 10838 // Move to left end 10839 10840 moveToStart = function() { 10841 var dis = - screenSize / 2, 10842 mx = self.getMatrix(), 10843 dur; 10844 10845 if (vertical) { 10846 dur = 1000 * Math.minMax(minDur, Math.abs(mx[5] - dis) / settings.speed, maxDur), 10847 mx[5] = dis; 10848 } else { 10849 dur = 1000 * Math.minMax(minDur, Math.abs(mx[4] - dis) / settings.speed, maxDur), 10850 mx[4] = dis; 10851 } 10852 10853 exposedEnd = false; 10854 direction = -1; 10855 self[0].style.transition = 'none'; 10856 self.one('transitionend', reachedStart) 10857 .css({ 10858 transition: 'transform ' + dur + 'ms ' + settings.easing, 10859 transform: 'matrix(' + mx.join(',') + ')' 10860 }); 10861 }, 10862 10863 // Move to right end 10864 10865 moveToEnd = function() { 10866 var dis = screenSize / 2 - size, 10867 mx = self.getMatrix(), 10868 dur; 10869 10870 if (vertical) { 10871 dur = 1000 * Math.minMax(minDur, Math.abs(mx[5] - dis) / settings.speed, maxDur), 10872 mx[5] = dis; 10873 } else { 10874 dur = 1000 * Math.minMax(minDur, Math.abs(mx[4] - dis) / settings.speed, maxDur), 10875 mx[4] = dis; 10876 } 10877 10878 exposedStart = false; 10879 direction = 1;
10880 self[0].style.transition = 'none'; 10881 self.one('transitionend', reachedEnd) 10882 .css({ 10883 transition: 'transform ' + dur + 'ms ' + settings.easing, 10884 transform: 'matrix(' + mx.join(',') + ')' 10885 }); 10886 }, 10887 10888 // Start auto pano 10889 10890 startAuto = function() { 10891 var mx = self.getMatrix(), 10892 c = vertical? (self[0].scrollHeight / 2 + mx[5]) : (self[0].scrollWidth / 2 + mx[4]); 10893 10894 if (settings.direction < 0 || (settings.direction === 0 && c > 0)) { 10895 moveToStart(); 10896 } else { 10897 moveToEnd(); 10898 } 10899 10900 cont.addClass(settings.panoMoveClass); 10901 cont.data('panomove', true); 10902 }, 10903 10904 // Stop moving 10905 10906 stopMove = function() { 10907 var mx = self.getMatrix(); 10908 10909 direction = 0; 10910 self.off('transitionend'); 10911 10912 // Move immediately 10913 self.css({ 10914 transition: 'none', 10915 transform: 'matrix(' + mx.join(',') + ')' 10916 }); 10917 10918 cont.data('panomove', false); 10919 cont.removeClass(settings.panoMoveClass); 10920 }; 10921 10922 10923 // Initializing 10924 10925 if (ns) { 10926 $(window).add($(document)).off('.' + ns); 10927 } 10928 10929 // Creating new namespace 10930 10931 self.data('lap_ns', ns = 'lap_' + ($.fn.autopano._counter = ($.fn.autopano._counter || 0) + 1)); 10932 10933 // Initializing 10934 10935 init(); 10936 10937 // Ready event 10938 10939 if (settings.hasOwnProperty('onReady') && typeof settings.onReady === FUNCTION) { 10940 settings.onReady(self[0]); 10941 } 10942 10943 return this; 10944 }; 10945 10946 $.fn.autopano.defaults = { 10947 direction: -1, // 1: to right, -1: to left 10948 speed: 80, // pixel / s 10949 easing: 'cubic-bezier(0.2, 0, 0.8, 1)', // Easing function 10950 autoStart: true, // Auto start 10951 loop: true, // continue after exposed both ends 10952 panoMoveClass: 'pano-move' // Class for the container during move 10953 }; 10954 10955})(jQuery, jQuery(document)); 10956/* 10957 * audioPlayer() :: adds an audio player to a button 10958 * 10959 * Copyright by Lazaworx 10960 * http://www.lazaworx.com 10961 * Author: Laszlo Molnar 10962 * 10963 * Dual licensed under the MIT and GPL licenses. 10964 * - http://www.opensource.org/licenses/mit-license.php 10965 * - http://www.gnu.org/copyleft/gpl.html 10966 * 10967 * Usage: $(element).addAudioPlayer(options); 10968 * options: 10969 autoPlay: false, 10970 loop: true, 10971 volume: 0.2, 10972 target: 'id' // or as tooltip if no target 10973 src: '' // or pulled from data-src '["song1.mp3","song2.mp3"]' 10974 10975 */ 10976 10977;(function($) { 10978 'use strict'; 10979 10980 $.fn.audioPlayer = function(settings) { 10981 10982 if ($(this).data('audioplayer')) { 10983 settings = $.extend({}, settings, $(this).data('audioplayer')); 10984 } 10985 10986 settings = $.extend({}, $.fn.audioPlayer.defaults, settings); 10987 10988 if (!settings.hasOwnProperty('rootPath') || settings.rootPath === '.') { 10989 settings.rootPath = ''; 10990 } 10991 10992 const ns = 'lap_ns', 10993 musicFolder = (settings.rootPath? (settings.rootPath + '/') : '') + settings.folder + '/', 10994 ACTIVE = 'active', 10995 PAUSED = 'paused'; 10996 10997 return this.each(function() { 10998 let self = $(this), 10999 target = $(), 11000 player = $(), 11001 playList = $(), 11002 audio = $(), 11003 source = $(), 11004 curr = 0, 11005 songs, 11006 promise, 11007 savedVolume = settings.volume, 11008 fadeTimeout, 11009 fadeInOn = false, 11010 fadeOutOn = false, 11011 11012 // Creating player 11013 11014 createPlayer = function() { 11015 11016 if (settings.hasOwnProperty('target')) { 11017 player = $('#' + settings.target); 11018 } else { 11019 player = $('<div>', { 11020 'class': settings.playerHook 11021 }); 11022 } 11023 11024 audio = $('<audio>', { 11025 preload: 'auto', 11026 controls: true, 11027 controlsList: 'nofullscreen nodownload noremote' 11028 }).appendTo(player); 11029 11030 audio[0].pause(); 11031 self.data(PAUSED, true); 11032 11033 source = $('<source>', { 11034 src: musicFolder + songs[curr] 11035 }).appendTo(audio); 11036 11037 setTimeout(function() { 11038 11039 // Wiring events with a delay 11040 audio.on('ended.' + ns, function() { 11041 playNext(); 11042 return true; 11043 });
11044 11045 audio.on('pause.' + ns, function() { 11046 self.data(PAUSED, true); 11047 self.removeClass(ACTIVE); 11048 if (typeof settings['onPaused'] === FUNCTION) { 11049 settings.onPaused.call(self); 11050 } 11051 }); 11052 11053 audio.on('playing.' + ns, function() { 11054 self.data(PAUSED, false); 11055 if (audio[0].volume === 0) { 11056 audio[0].volume = savedVolume || settings.volume; 11057 } 11058 self.addClass(ACTIVE); 11059 if (typeof settings['onPlaying'] === FUNCTION) { 11060 settings.onPlaying.call(self); 11061 } 11062 }); 11063 11064 audio.on('volumechange.' + ns, function() { 11065 if (!fadeInOn && !fadeOutOn) { 11066 //clearTimeout(fadeTimeout); 11067 savedVolume = audio[0]['volume']; 11068 } 11069 }); 11070 }, 100); 11071 11072 if (songs.length) { 11073 playList = $('<ul>', { 11074 'class': settings.playListHook 11075 }).appendTo(player); 11076 11077 for (var i = 0; i < songs.length; i++) { 11078 playList.append($('<li>').append($('<a>', { 11079 text: decodeURIComponent(songs[i].replace(/\.[^\.]*$/, '')) 11080 }).on('click.' + ns, function(e) { 11081 var i = playList.children().index($(e.target).parent()); 11082 play(i); 11083 return false; 11084 }))); 11085 } 11086 11087 playList.children().eq(curr).addClass(ACTIVE); 11088 } 11089 }, 11090 11091 // Setting the current song's source 11092 11093 setSource = function(n) { 11094 11095 audio[0].pause(); 11096 11097 if (n >= songs.length || n < 0) { 11098 n = ((n % songs.length) + songs.length) % songs.length; 11099 } 11100 11101 source.attr({ 11102 src: musicFolder + songs[n] 11103 }) 11104 11105 audio[0].load(); 11106 11107 curr = n; 11108 11109 playList.children().removeClass(ACTIVE); 11110 playList.children().eq(n).addClass(ACTIVE); 11111 }, 11112 11113 // Start playing 11114 11115 play = function(n) { 11116 if (typeof n === UNDEF || n instanceof $.Event) { 11117 n = curr; 11118 } 11119 11120 if (!source.attr('src') || n !== curr) { 11121 // New/missing song 11122 setSource(n); 11123 } else if (!audio[0].paused) { 11124 // Playing already 11125 return; 11126 } 11127 11128 if (promise = audio[0].play()) { 11129 promise.then(function() { 11130 // Autoplay started! 11131 self.addClass(ACTIVE); 11132 self.data(PAUSED, false); 11133 if (typeof settings['onPlaying'] === FUNCTION) { 11134 settings.onPlaying.call(self); 11135 } 11136 }, function(err) { 11137 // Autoplay was prevented. 11138 // Show a "Play" button so that user can start playback. 11139 self.removeClass(ACTIVE); 11140 self.data(PAUSED, true); 11141 if (typeof settings['onPaused'] === FUNCTION) { 11142 settings.onPaused.call(self); 11143 } 11144 console.log('Autoplay has been prevented by the browser. Interact with the page first!'); 11145 }); 11146 } 11147 }, 11148 11149 // Pausing play 11150 11151 pause = function() { 11152 audio[0].pause(); 11153 promise = null; 11154 self.removeClass(ACTIVE); 11155 self.data(PAUSED, true); 11156 }, 11157 11158 // Toggling play 11159 11160 toggle = function() { 11161 if (audio[0].paused) { 11162 play(); 11163 } else { 11164 pause(); 11165 } 11166 }, 11167 11168 // Fade in one step 11169 11170 fadeInStep = function() { 11171 11172 clearTimeout(fadeTimeout); 11173 11174 let v = audio[0]['volume']; 11175 11176 if (typeof v !== 'number') { 11177 v = parseFloat(v); 11178 if (typeof v !== 'number') { 11179 return; 11180 } 11181 } 11182 11183 v = Math.min(v + settings.step, 1); 11184 11185 if (v >= savedVolume) { 11186 audio[0]['volume'] = savedVolume; 11187 setTimeout(function() { 11188 fadeInOn = false; 11189 }, 50); 11190 return; 11191 } 11192 11193 audio[0]['volume'] = v; 11194 11195 fadeTimeout = setTimeout(fadeInStep, settings.stepFreq); 11196 }, 11197 11198 // Fade in playing 11199 11200 fadeIn = function() { 11201 11202 if (audio[0].paused) { 11203 audio[0]['volume'] = 0.01; 11204 play(); 11205 } 11206 11207 fadeInOn = true; 11208 fadeOutOn = false; 11209 fadeTimeout = setTimeout(fadeInStep, settings.stepFreq); 11210 11211 self.data(PAUSED, false); 11212 }, 11213 11214 // Fade out one step 11215 11216 fadeOutStep = function() { 11217 11218 clearTimeout(fadeTimeout); 11219 11220 let v = audio[0]['volume']; 11221 11222 if (typeof v !== 'number') { 11223 v = parseFloat(v); 11224 if (typeof v !== 'number') { 11225 return; 11226 } 11227 } 11228 11229 v = Math.max(v - settings.step, 0); 11230 11231 if (v < 0.01) { 11232 audio[0]['volume'] = 0; 11233 pause(); 11234 setTimeout(function() { 11235 fadeOutOn = false; 11236 }, 50); 11237 return; 11238 } 11239 11240 audio[0]['volume'] = v; 11241 11242 fadeTimeout = setTimeout(fadeOutStep, settings.stepFreq); 11243 }, 11244 11245 // Fade out playing 11246 11247 fadeOut = function() { 11248 11249 if (!audio[0].paused) { 11250 fadeOutOn = true; 11251 fadeInOn = false; 11252 fadeTimeout = setTimeout(fadeOutStep, settings.stepFreq); 11253 } 11254 11255 self.data(PAUSED, true); 11256 }, 11257 11258 // Fade out playing 11259 11260 fadeToggle = function() { 11261 11262 if (fadeInOn || !audio[0].paused) { 11263 fadeOut(); 11264 } else { 11265 fadeIn(); 11266 } 11267 }, 11268 11269 // Next song 11270 11271 playNext = function() { 11272 if (curr >= songs.length - 1) { 11273 if (settings.loop) { 11274 play(0); 11275 } 11276 } else { 11277 play(curr + 1); 11278 } 11279 }, 11280 11281 // Destroying player 11282 11283 destroy = function() { 11284 player.trigger('removeTooltip'); 11285 player.add(self).off('.' + ns); 11286 player.remove(); 11287 }, 11288 11289 // Saving play status 11290 11291 saveStatus = function() { 11292 if ($.cookie) { 11293 $.cookie('ap-status', 11294 (audio[0].paused? '0':'1') + 11295 '::' + (audio[0].currentTime || 0) + 11296 '::' + ((fadeInOn || fadeOutOn || audio[0].paused)? savedVolume : audio[0].volume) + 11297 (curr? ('::' + curr) : '') 11298 ); 11299 } 11300 }, 11301 11302 // Loading play status 11303 11304 loadStatus = function() { 11305 if ($.cookie) {
11306 let c = $.cookie('ap-status'); 11307 11308 if (c) { 11309 c = c.split('::'); 11310 11311 settings.autoPlay = (c[0] === '1'); 11312 settings.startTime = parseFloat(c[1] || 0); 11313 savedVolume = settings.volume = parseFloat(c[2]) || .2; 11314 curr = parseInt(c[3] || 0, 10); 11315 11316 if (curr < 0 || curr > songs.length) { 11317 curr = 0; 11318 } 11319 } 11320 } 11321 }; 11322 11323 11324 // Loading playlist 11325 if (self.data('src')) { 11326 songs = self.data('src').split('::'); 11327 } else if (settings.hasOwnProperty('src')) { 11328 songs = (typeof settings.src === 'string')? settings.src.split('::') : settings.src; 11329 } else { 11330 return; 11331 } 11332 11333 // Load status to initialize, save status on unload 11334 if (settings.saveStatus) { 11335 loadStatus(); 11336 } 11337 11338 // Creating the AUDIO element 11339 createPlayer(); 11340 11341 // If no target, add as a tooltip, playlist appears on hover 11342 if (settings.hasOwnProperty('target')) { 11343 target = $('#' + self.data('rel')); 11344 } 11345 11346 if (target.length) { 11347 target.append(player); 11348 } else { 11349 self.addTooltip(player, { 11350 pos: settings.pos, 11351 delay: 500 11352 }); 11353 } 11354 11355 // Play/Pause button functionality 11356 self.on('click.' + ns, function() { 11357 if (!target.length || target.is(':hidden')) { 11358 if (self.data(PAUSED)) { 11359 fadeIn(); 11360 } else { 11361 fadeOut(); 11362 } 11363 return false; 11364 } else { 11365 return true; 11366 } 11367 }); 11368 11369 // Events for outside control 11370 11371 self.on('removePlayer.' + ns, destroy); 11372 11373 self.on('startPlayer.' + ns, play); 11374 11375 self.on('pausePlayer.' + ns, pause); 11376 11377 self.on('togglePlayer.' + ns, toggle); 11378 11379 self.on('fadeInPlayer.' + ns, fadeIn); 11380 11381 self.on('fadeOutPlayer.' + ns, fadeOut); 11382 11383 self.on('fadeTogglePlayer.' + ns, fadeToggle); 11384 11385 self.data('slideshowControl', settings.slideshowControl); 11386 11387 // Delaying the player initialization for the sake of Firefox 11388 11389 setTimeout(function() { 11390 11391 audio[0]['volume'] = settings.volume; 11392 11393 if (settings.hasOwnProperty('startTime')) { 11394 audio[0]['currentTime'] = settings.startTime; 11395 } 11396 11397 // Start playing if Auto on 11398 if (settings.autoPlay) { 11399 fadeIn(); 11400 } 11401 11402 $(window).on('pagehide.' + ns, saveStatus); 11403 11404 }, 50); 11405 11406 }); 11407 11408 }; 11409 11410 $.fn.audioPlayer.defaults = { 11411 autoPlay: true, 11412 loop: true, 11413 volume: 0.25, 11414 stepFreq: 30, 11415 step: .02, 11416 saveStatus: true, 11417 playerHook: 'player', 11418 playListHook: 'play-list', 11419 folder: 'res', 11420 rootPath: '', 11421 pos: [1,2,1,0], 11422 onPlaying: null, 11423 onPaused: null 11424 }; 11425 11426})(jQuery); 11427/* 11428 * lightbox.js - lightbox for Tiger skin 11429 * jslint browser: true 11430 */ 11431 11432;(function($, $window, $document) { 11433 'use strict'; 11434 // Requires laza.util, laza.swipe and laza.transform 11435 var $body = jQuery('body'); 11436 11437 $.fn.lightbox = function(album, settings) { 11438 11439 if (typeof album === UNDEF) { 11440 return this; 11441 } 11442 11443 settings = $.extend({}, $.fn.lightbox.defaults, settings); 11444 11445 var self = $(this), 11446 text = getTranslations($.fn.lightbox.text), 11447 thumbs = $([]), 11448 thumb = $(), 11449 main = $(), 11450 oldMain = $(), 11451 image = $(), 11452 media = $(), 11453 caption = $(), 11454 oldCaption = $(), 11455 controls = $(), 11456 audioClip = $(), 11457 el, 11458 isImage = true, 11459 isVideo = false, 11460 isAudio = false, 11461 isOther = false, 11462 isWebPage = false, 11463 isExternal = false, 11464 isVr = false, 11465 isPdf = false, 11466 isPanorama = false, 11467 dontRemove = false, 11468 slideshow = null, 11469 slideshowSuspended = false, 11470 slideshowDelay = settings.slideshowDelay, 11471 transitionSpeed = settings.transitionSpeed, 11472 audioClipTimeout = null, 11473 fitTimeout = null, 11474 controlsTimeout = null, 11475 resizeTimeout = null, 11476 progressTo = null, 11477 listening = false, 11478 11479 maxZoom = settings.maxZoom, 11480 fitImages = settings.fitImages, 11481 zoomLevel, 11482 11483 thumbsVisibleDefault = settings.thumbsVisible, 11484 captionVisibleDefault = settings.captionVisible, 11485 11486 curr = -1, 11487 loadCounter = 0, 11488 loadTimer, 11489 connectionSpeed = 0, 11490 direction = 0, 11491 imgPadding, 11492 lbPadding = settings['fitPadding'], 11493 11494 // Elements 11495 lightbox, 11496 lb_overlay, 11497 lb_activity, 11498 11499 // Thumbstrip 11500 lb_thumbstrip, 11501 lb_thumb_cont, 11502 lb_thumbs, 11503 11504 // Controls 11505 lb_btn_left, 11506 lb_up, 11507 lb_zoom, 11508 lb_zoomin, 11509 lb_zoomout, 11510 lb_zoom_slider, 11511 lb_fullscr, 11512 lb_exitfullscr, 11513 lb_showthumbs, 11514 lb_hidethumbs, 11515 lb_showcaption, 11516 lb_hidecaption, 11517 lb_play_pause, 11518 lb_progress, 11519 lb_timing_value, 11520 lb_btn_right, 11521 11522 ns = self.data('llb_ns'), 11523 extraSizes = settings.hasOwnProperty('extraSizes')? settings.extraSizes.split(/,\s*/) : false, 11524 11525 backgroundAudioRoot = $('[data-audioplayer]'), 11526 11527 cleanup = function(ns) { 11528 // cleaning up the old event handlers 11529 $window.add(document).off('.' + ns); 11530 // Removing the lightbox :: you can only have one lightbox per thumb set 11531 $('#' + ns).remove(); 11532 }, 11533 11534 closeModal = function() { 11535 11536 lightbox.find('.modal').trigger('destroy'); 11537 }, 11538 11539 getThumbsHeight = function() { 11540 return (settings.fitBetween !== 'screen' && 11541 settings.thumbsVisible && 11542 thumbs.length > 1 && 11543 lb_thumb_cont && 11544 lb_thumb_cont.length)? 11545 lb_thumb_cont[0].clientHeight : 0; 11546 }, 11547 11548 getInfoHeight = function() { 11549 11550 return (settings.fitBetween !== 'screen' && 11551 lightbox.hasClass(settings.captionVisibleClass) && 11552 caption && 11553 caption.length)? 11554 caption[0].offsetHeight : 0; 11555 }, 11556 11557 getControlsHeight = function() { 11558 11559 return controls[0].clientHeight; 11560 }, 11561 /*
11562 isTapOnMediaControls = function(e, m) { 11563 11564 var rect = m.getBoundingClientRect(), 11565 c = getCoords(e), 11566 top = rect.top + $window.scrollTop(), 11567 left = rect.left + $window.scrollLeft(), 11568 bottomBand = Math.max(40, rect.height * 0.18); 11569 11570 // Bottom control bar 11571 if (c.y >= top + rect.height - bottomBand) { 11572 return true; 11573 } 11574 11575 // Center play button, pre-first-play only (Safari/WebKit) 11576 if (m.paused && m.currentTime === 0) { 11577 var cw = Math.min(96, rect.width * 0.35), 11578 ch = Math.min(96, rect.height * 0.35), 11579 cx = left + rect.width / 2, 11580 cy = top + rect.height / 2; 11581 11582 if (Math.abs(c.x - cx) <= cw / 2 && Math.abs(c.y - cy) <= ch / 2) { 11583 return true; 11584 } 11585 } 11586 11587 return false; 11588 }, 11589 */ 11590 // Fixing the zoom button after pinch 11591 fixZoom = function(zoom, coords) { 11592 11593 if (settings.useZoom && settings.zoomSlider) { 11594 if (zoom === 1) { 11595 return; 11596 } 11597 11598 var mnr = main.data('minRatio') || 1, 11599 mxr = main.data('maxRatio') || 1.4, 11600 z = mnr + zoomLevel * (mxr - mnr) / 100; 11601 11602 z = Math.minMax(mnr, z * zoom, mxr); 11603 zoomLevel = (z - mnr) / (mxr - mnr) * 100; 11604 11605 setZoomSlider(zoom); 11606 setZoomBtn(zoomLevel < 50, zoomLevel >= 50); 11607 } 11608 }, 11609 11610 // Toggling zoom in simple zomm toggling 11611 toggleZoom = function(coords) { 11612 11613 if (settings.useZoom && settings.zoomSlider) { 11614 fitImages = true; 11615 zoomLevel = (zoomLevel > 50)? 0 : 100; 11616 setZoomSlider(zoomLevel); 11617 } else { 11618 // Change fit strategy 11619 fitImages = !fitImages; 11620 } 11621 //savePrefs(); 11622 if (typeof coords === UNDEF) { 11623 fitImage(true); 11624 } else { 11625 fitImage(true, coords);
11626 } 11627 }, 11628 11629 // Sets zoom (0 .. 100), where 100 means 1:1 size 11630 setZoom = function(zoom, coords) { 11631 //console.log('Set zoom:' + zoom); 11632 zoomLevel = zoom; 11633 setZoomSlider(zoom); 11634 11635 if (typeof coords === UNDEF) { 11636 fitImage(true); 11637 } else { 11638 fitImage(true, coords); 11639 } 11640 }, 11641 11642 increaseZoom = function(incr, coords) { 11643 var v = settings.zoomSlider? getZoomSlider() : zoomLevel; 11644 11645 if (v < 100) { 11646 v = Math.min(100, v + ((typeof incr !== UNDEF)? incr : 25)); 11647 } 11648 11649 if (typeof coords === UNDEF) { 11650 setZoom(v); 11651 } else { 11652 setZoom(v, coords); 11653 } 11654 }, 11655 11656 decreaseZoom = function(incr, coords) { 11657 var v = settings.zoomSlider? getZoomSlider() : zoomLevel; 11658 11659 if (v > 0) { 11660 v = Math.max(0, v - ((typeof incr !== UNDEF)? incr : 25)); 11661 } 11662 11663 if (typeof coords === UNDEF) { 11664 setZoom(v); 11665 } else { 11666 setZoom(v, coords); 11667 } 11668 }, 11669 11670 getZoomSlider = function() { 11671 return parseInt(lb_zoom_slider.val()); 11672 }, 11673 11674 setZoomSlider = function(zoom) { 11675 lb_zoom_slider.val(zoom); 11676 }, 11677 11678 setZoomBtn = function(canZoomIn, canZoomOut) { 11679 11680 if (settings.zoomSlider) { 11681 if (canZoomIn || canZoomOut) { 11682 lightbox.addClass('show-zoom-level'); 11683 if (zoomLevel === 0) { 11684 lb_zoom.removeClass('icon-zoom-out icon-zoom-level').addClass('icon-zoom-in'); 11685 } else if (zoomLevel === 100) { 11686 lb_zoom.removeClass('icon-zoom-in icon-zoom-level').addClass('icon-zoom-out'); 11687 } else { 11688 lb_zoom.removeClass('icon-zoom-in icon-zoom-out').addClass('icon-zoom-level'); 11689 } 11690 } else { 11691 lightbox.removeClass('show-zoom-level'); 11692 } 11693 } else { 11694 lightbox.toggleClass('show-zoom-out', canZoomOut); 11695 // Zoom out takes precedence if both possible 11696 lightbox.toggleClass('show-zoom-in', canZoomIn && !canZoomOut); 11697 } 11698 11699 }, 11700 11701 // Slideshow timing controls 11702 11703 setTiming = function(t) { 11704 lb_timing_value.html((t >= 1000)? (Math.round(t / 1000) + 's') : (t + 'ms')); 11705 }, 11706 11707 increaseTiming = function() { 11708 11709 if (settings.slideshowDelay > 1000) { 11710 settings.slideshowDelay += 1000; 11711 } else { 11712 settings.slideshowDelay += 100; 11713 } 11714 11715 setTiming(settings.slideshowDelay); 11716 }, 11717 11718 decreaseTiming = function() { 11719 11720 if (settings.slideshowDelay > 500) { 11721 if (settings.slideshowDelay > 1000) { 11722 settings.slideshowDelay -= 1000; 11723 } else { 11724 settings.slideshowDelay -= 100; 11725 } 11726 } 11727 11728 setTiming(settings.slideshowDelay); 11729 }, 11730 11731 // Animating progress bar 11732 11733 animateProgress = function(duration) { 11734 var d = 20 / duration, 11735 11736 setProgress = function(to) { 11737 11738 //progress.css('transform', 'rotate(' + to * 360 + 'deg)'); 11739 lb_progress.css('width', to * 100 + '%'); 11740 11741 if (to < 1) { 11742 to = Math.min(1, to + d); 11743 progressTo = setTimeout(setProgress, 20, to); 11744 } 11745 }; 11746 11747 clearTimeout(progressTo); 11748 progressTo = setTimeout(setProgress, 20, 0); 11749 }, 11750 11751 showProgress = function() { 11752 if (lb_progress) { 11753 lb_progress.show(); 11754 animateProgress(slideshowDelay); 11755 } 11756 }, 11757 11758 hideProgress = function() { 11759 if (lb_progress) { 11760 clearTimeout(progressTo); 11761 lb_progress.hide(); 11762 } 11763 }, 11764 11765 getLightboxDims = function() { 11766 var lbDims = [ lightbox.width(), lightbox.height() ]; 11767 11768 return [ lbDims[0] - 2 * lbPadding, lbDims[1] - Math.max(getThumbsHeight(), lbPadding) - Math.max(getInfoHeight(), lbPadding) ]; 11769 }, 11770 11771 // Reacalculating image position and size upon window resize 11772 11773 fitImage = function(animate, center, doneFn) { 11774 11775 if (!main || !main.length) { 11776 return; 11777 } 11778 11779 var o = thumbs.eq(curr).data(J.OBJ), 11780 lbDims = [ lightbox.width(), lightbox.height() ], 11781 controlsHeight = (isVr || isOther || isExternal || (sett
11781ings.fitBetween === 'all' && !settings.autohideControls))? getControlsHeight() : 0, 11782 thumbsHeight = getThumbsHeight(), 11783 infoHeight = getInfoHeight(), 11784 11785 checkPano = function(dims) { 11786 return settings.autoPano && (dims[0] / dims[1] > settings.horzPanoThreshold || dims[0] / dims[1] < settings.vertPanoThreshold); 11787 }, 11788 11789 checkPanoDims = function(dims) { 11790 return dims[0] / lbDims[0] >= settings.panoramaAutoDetect // hangs over the edges least 1.3333 times the width or height 11791 || 11792 dims[1] / lbDims[1] >= settings.panoramaAutoDetect; 11793 }, 11794 11795 getTargetDims = function(dims, f) { 11796 return [ dims[0] * f, dims[1] * f ]; 11797 }, 11798 11799 fitPano = function(dims) { 11800 return Math.max( 11801 (lbDims[0] - 2 * lbPadding - 2 * imgPadding) / dims[0], 11802 (lbDims[1] - Math.max(thumbsHeight + controlsHeight, lbPadding) - Math.max(infoHeight, lbPadding) - 2 * imgPadding) / dims[1] 11803 ); 11804 }, 11805 11806 fitToLightbox = function(dims) { 11807 return Math.min( 11808 (lbDims[0] - 2 * lbPadding - 2 * imgPadding) / dims[0], 11809 (lbDims[1] - Math.max(thumbsHeight + controlsHeight, lbPadding) - Math.max(infoHeight, lbPadding) - 2 * imgPadding) / dims[1] 11810 ); 11811 }; 11812 11813 // Image padding = border 11814 if (typeof imgPadding === UNDEF) { 11815 imgPadding = parseInt(main.css('paddingTop')); 11816 } 11817 11818 // Lightbox padding 11819 if (typeof lbPadding === UNDEF) { 11820 lbPadding = 0; 11821 } 11822 11823 // Set zoom level if undef 11824 if (typeof zoomLevel === UNDEF || zoomLevel === null) { 11825 zoomLevel = fitImages? 0 : 100; 11826 if (settings.useZoom && settings.zoomSlider) { 11827 setZoomSlider(zoomLevel); 11828 } 11829 } 11830 11831 11832 if (isVr || isOther || isWebPage || isExternal || isAudio && image.hasClass('default')) { 11833 // Non-zoomable types 11834 var th = Math.max(thumbsHeight + ((isVr || isOther || isExternal)? controlsHeight : 0), lbPadding), 11835 bh = Math.max(infoHeight, lbPadding), 11836 w = lbDims[0] - 2 * lbPadding, 11837 h = lbDims[1] - (th + bh); 11838 11839 if (isExternal || isAudio) { 11840 var imgDims = album.getDimensions(o); 11841 if (isExternal) { 11842 // Might be smaller than viewport 11843 if (imgDims[0] < w && imgDims[1] < h) { 11844 // Smaller than available space? 11845 w = Math.min(imgDims[0], w) + 2 * imgPadding; 11846 h = Math.min(imgDims[1], h) + 2 * imgPadding; 11847 } 11848 } else if (isAudio) { 11849 // Audio with no poster 11850 if (imgDims[0] > w || imgDims[1] > h) { 11851 // Default audio poster is larger than viewport 11852 var sc = Math.min(w / imgDims[0], h / imgDims[1]); 11853 w = imgDims[0] * sc; 11854 h = imgDims[1] * sc; 11855 } else { 11856 w = imgDims[0]; 11857 h = imgDims[1]; 11858 } 11859 } 11860 } else if (isWebPage) { 11861 w = Math.min(w, settings.maxPageWidth); 11862 h = 'auto'; 11863 } 11864 11865 main.data('fitted', true) 11866 .css({ 11867 display: 'block', 11868 width: w, 11869 height: h, 11870 maxHeight: 'calc(100% - ' + (bh + th) + 'px)', 11871 transition: 'none', 11872 transform: 'matrix(1,0,0,1,' + Math.round((0 - w) / 2) + ',' + Math.round((th + bh - h) / 2) + ')', 11873 opacity: 1 11874 });
11875 11876 if (isExternal || isWebPage) { 11877 main.addClass('scrollable'); 11878 } 11879 11880 if (settings.useZoom) { 11881 setZoomBtn(false, false); 11882 } 11883 11884 // Done Function 11885 if (typeof doneFn === FUNCTION) { 11886 doneFn.call(null); 11887 } 11888 11889 } else { 11890 // Zoomable types 11891 var imgDims = album.getDimensions(o), 11892 maxDims = album.getMaxDimensions(o) || imgDims, 11893 minRatio, 11894 maxRatio, 11895 targetRatio, 11896 targetDims, 11897 matrix = main.getMatrix(), 11898 optSrc, 11899 isPano, 11900 fitted = false; 11901 11902 // Stop pano move 11903 if (lightbox.data('panomove')) { 11904 main.trigger('autopanoStop'); 11905 } 11906 11907 // Minimal size 11908 minRatio = Math.min(1, fitToLightbox(imgDims)); 11909 11910 // Maximum size 11911 if (isVideo || isAudio) { 11912 // Audio or Video: constrain to lightbox 11913 maxRatio = Math.min(maxZoom, fitToLightbox(imgDims)); 11914 } else if (maxDims && maxDims[0] > imgDims[0]) { 11915 // Has larger variants 11916 maxRatio = Math.max(1, maxDims[0] / imgDims[0]); 11917 } else { 11918 // Single image 11919 maxRatio = maxZoom; 11920 } 11921 11922 if (minRatio > maxRatio) { 11923 // Swap if reversed 11924 var r = minRatio; 11925 minRatio = maxRatio; 11926 maxRatio = r; 11927 } 11928 11929 // Target size (fit image taken into account) 11930 if (settings.useZoom && settings.zoomSlider) { 11931 // Determining initial size for zoom slider 11932 if (main.data('inited')) { 11933 // Already painted 11934 targetRatio = minRatio + zoomLevel * (maxRatio - minRatio) / 100; 11935 targetDims = getTargetDims(imgDims, targetRatio); 11936 fitted = targetRatio == minRatio; 11937 isPano = !!lightbox.data('hasAutoPano'); 11938 } else { 11939 // First paint 11940 if (isPanorama || checkPano(imgDims)) { 11941 // Panorama: fit to height 11942 targetRatio = Math.min(maxRatio, fitPano(imgDims)); 11943 targetDims = getTargetDims(imgDims, targetRatio); 11944 isPano = checkPanoDims(targetDims); 11945 fitted = true; 11946 } else if (settings.fitImages) { 11947 // Fit to screen 11948 targetRatio = Math.min(maxRatio, fitToLightbox(imgDims)); 11949 targetDims = getTargetDims(imgDims, targetRatio); 11950 isPano = false; 11951 fitted = true; 11952 } else { 11953 // Show in 1:1 size 11954 targetRatio = 1; 11955 targetDims = imgDims; 11956 isPano = false; 11957 fitted = targetRatio == minRatio; 11958 }
11959 zoomLevel = 100 * Math.minMax(0, (targetRatio - minRatio) / (maxRatio - minRatio), 1); 11960 setZoomSlider(zoomLevel); 11961 } 11962 } else { 11963 // Fit toggle 11964 if (isPanorama || checkPano(imgDims)) { 11965 targetRatio = Math.min(maxZoom, fitImages? fitPano(imgDims) : fitToLightbox(imgDims)); 11966 targetDims = getTargetDims(imgDims, targetRatio); 11967 isPano = checkPanoDims(targetDims); 11968 fitted = fitImages; 11969 } else { 11970 if (!main.data('inited')) { 11971 fitImages = settings.fitImages; 11972 // Initial: fit if larger, 1:1 if smaller 11973 targetRatio = (fitImages || isVideo || isAudio)? Math.min(maxZoom, fitToLightbox(imgDims)) : 1; 11974 } else { 11975 if (fitImages) { 11976 // Zoom out 11977 targetRatio = Math.min(1, fitToLightbox(imgDims)); 11978 } else { 11979 // Zoom in 11980 targetRatio = maxRatio; 11981 } 11982 } 11983 fitted = targetRatio == minRatio; 11984 targetDims = getTargetDims(imgDims, targetRatio); 11985 } 11986 } 11987 11988 // Checking if high res image needed 11989 if ( isImage && !fitted && // It's an image 11990 ((maxDims[0] > imgDims[0]) || (maxDims[1] > imgDims[1])) && // Has higher resolution version 11991 ((targetDims[0] > imgDims[0]) || (targetDims[1] > imgDims[1])) && // The target size is larger than the base size 11992 (!main.data('lastDims') || main.data('lastDims')[0] < targetDims[0]) // The higher resolution version hasn't yet been loaded 11993 ) { 11994 // Saving optimized image source 11995 main.data('optSource', album.getOptimalImagePath(o, targetDims, settings.displayOriginals)); 11996 main.data('lastDims', targetDims); 11997 } 11998 11999 // Adding padding (border) 12000 targetDims = [ targetDims[0] + 2 * imgPadding, targetDims[1] + 2 * imgPadding ]; 12001 12002 // Reset scale 12003 matrix[0] = matrix[3] = 1; 12004 // Setting position 12005 if (!main.data('inited') || (!isPano && fitted)) { 12006 // First paint or fit 12007 main.data('inited', true); 12008 matrix[4] = 0 - targetDims[0] / 2; 12009 matrix[5] = 0 - targetDims[1] / 2 + 12010 (fitted? 12011 (Math.max(thumbsHeight + controlsHeight, lbPadding) - Math.max(infoHeight, lbPadding)) / 2 12012 : 12013 0 12014 ); 12015 } else { 12016 // Already painted. 12017 // Measure the CURRENT on-screen geometry directly, so any pinch-zoom 12018 // scale baked into the transform is taken into account. Using the stale 12019 // matrix[4]/[5] + main.width() (which ignore the pinch scale) is what made 12020 // the focal point drift and the image fly off screen after a pinch. 12021 var lbRect = lightbox[0].getBoundingClientRect(), 12022 imgRect = main[0].getBoundingClientRect(), 12023 ccx = lbRect.left + lbRect.width / 2, // container centre (viewport) 12024 ccy = lbRect.top + lbRect.height / 2, 12025 curLeft = imgRect.left - ccx, // current border-box edges, 12026 curTop = imgRect.top - ccy, // relative to the container centre 12027 z = targetDims[0] / imgRect.width; // visual zoom factor (incl. scale) 12028 12029 if (typeof center !== UNDEF && center) { 12030 // Zoom toward the event point, keeping it fixed 12031 var cx = center.x - lbDims[0] / 2, 12032 cy = center.y - lbDims[1] / 2; 12033 matrix[4] = cx - (cx - curLeft) * z; 12034 matrix[5] = cy - (cy - curTop) * z; 12035 } else { 12036 // Keep the current centre (zoom about the container centre) 12037 matrix[4] = curLeft * z; 12038 matrix[5] = curTop * z; 12039 } 12040 } 12041 12042 // Prepare panoramas 12043 if (isImage) { 12044 if (isPano) { 12045 // Pamorama 12046 if (!main.data('hasAutoPano')) { 12047 if (fitted && (targetDims[0] > (lbDims[0] + 10) || targetDims[1] > (lbDims[1] + 10))) { 12048 // Applying panorama player 12049 if (targetDims[0] / targetDims[1] > lbDims[0] / lbDims[1]) { 12050 // Horizontal 12051 matrix[4] -= settings.autoPanoStart * (targetDims[0] - lbDims[0]) / 2; 12052 } else { 12053 // Vertical 12054 matrix[5] -= settings.autoPanoStart * (targetDims[1] - lbDims[1]) / 2; 12055 } 12056 } 12057 } 12058 } 12059 } 12060 12061 // Functions to execute on transition end or immediately if no animation 12062 var fto = null, 12063 finish = function() { 12064 // Replacing image source if not optimal 12065 if (main.data('optSource')) { 12066 var img = main.children('img').eq(0); 12067 if (img.length) { 12068 img.attr('src', main.data('optSource')); 12069 main.removeData('optSource'); 12070 } 12071 } 12072 // Adjust or attach pano player 12073 if (isImage && isPano && (typeof center === UNDEF || !center)) { 12074 12075 if (main.data('hasAutoPano')) { 12076 main.trigger('autopanoAdjust'); 12077 } else { 12078 //console.log('Applying autopano'); 12079 main.autopano({ 12080 direction: -settings.autoPanoStart || direction, 12081 speed: settings.autoPanoSpeed 12082 });
12083 } 12084 } 12085 12086 if (isVideo && settings.videoAuto && media[0].paused) { 12087 // Play video only after the transition 12088 playVideo(media[0]); 12089 } 12090 }, 12091 12092 endFitImage = function() { 12093 clearTimeout(fto); 12094 // Done Function 12095 if (typeof doneFn === FUNCTION) { 12096 doneFn(finish); 12097 } else { 12098 finish(); 12099 } 12100 }; 12101 12102 main.css({ 12103 display: 'block', 12104 transition: animate? ('transform ' + settings.transitionProps + ', width ' + settings.transitionProps + ', height ' + settings.transitionProps) : 'none' 12105 }); 12106 12107 // Apply transforms 12108 12109 if (animate) { 12110 //console.log('Scheduling finish()'); 12111 main.one('transitionend', endFitImage); 12112 fto = setTimeout(endFitImage, 320); 12113 } 12114 12115 main.data({ 12116 fitted: fitted, 12117 minRatio: minRatio, 12118 maxRatio: maxRatio 12119 }) 12120 .css({ 12121 transform: 'matrix(' + matrix.join(',') + ')', 12122 width: targetDims[0], 12123 height: targetDims[1] 12124 });
12125 12126 // Set zoom button 12127 if (settings.useZoom) { 12128 if (maxRatio > targetRatio) { 12129 setZoomBtn(true, false); 12130 } else if (minRatio < targetRatio) { 12131 setZoomBtn(false, true); 12132 } else { 12133 setZoomBtn(false, false); 12134 } 12135 } 12136 12137 if (!animate) { 12138 //console.log('Calling finish()'); 12139 fto = setTimeout(endFitImage, 20); 12140 } 12141 } 12142 12143 }, 12144 12145 // Reading image type 12146 12147 getImageType = function(item) { 12148 12149 isVideo = isAudio = isOther = isWebPage = isImage = isVr = isPdf = isPanorama = false; 12150 12151 isExternal = item.hasOwnProperty(J.EXTERNAL); 12152 12153 isVr = !isExternal && settings.use360Player && ( 12154 (item.hasOwnProperty(J.PROJECTIONTYPE) && item[J.PROJECTIONTYPE] === 'equirectangular') 12155 || 12156 (item[J.CATEGORY] === 'video' && item.hasOwnProperty(J.PANORAMA) && item[J.PANORAMA]) 12157 ); 12158 12159 if (!isExternal && !isVr) { 12160 12161 switch (item[J.CATEGORY]) { 12162 12163 case 'video': 12164 isVideo = true; 12165 break; 12166 12167 case 'audio': 12168 isAudio = true; 12169 break; 12170 12171 case 'other': 12172 isOther = true; 12173 isPdf = album.getExtension(item).toLowerCase() === 'pdf'; 12174 break; 12175 12176 case 'webPage': 12177 isWebPage = true; 12178 break; 12179 12180 default: 12181 isImage = true; 12182 isPanorama = 12183 (item.hasOwnProperty(J.PANORAMA) && item[J.PANORAMA]) 12184 || 12185 ( settings.autoPano 12186 && 12187 ( item[J.IMAGE][J.WIDTH] > lightbox.width() 12188 || 12189 item[J.IMAGE][J.HEIGHT] > lightbox.height() 12190 ) 12191 && 12192 ( (item[J.IMAGE][J.WIDTH] / item[J.IMAGE][J.HEIGHT] > settings.horzPanoThreshold) 12193 || 12194 (item[J.IMAGE][J.WIDTH] / item[J.IMAGE][J.HEIGHT] < settings.vertPanoThreshold) 12195 ) 12196 ); 12197 } 12198 } 12199 }, 12200 12201 // Gets the custom slideshow interval (if exist) 12202 12203 getSlideshowTiming = function(item) { 12204 12205 if (item.hasOwnProperty(J.SLIDESHOWINTERVAL)) { 12206 slideshowDelay = item[J.SLIDESHOWINTERVAL]; 12207 } else { 12208 slideshowDelay = settings.slideshowDelay; 12209 } 12210 if (item.hasOwnProperty(J.TRANSITIONSPEED)) { 12211 transitionSpeed = item[J.TRANSITIONSPEED]; 12212 } else { 12213 transitionSpeed = settings.transitionSpeed; 12214 } 12215 }, 12216 12217 // Updates rating (if changed externally) 12218 12219 updateRating = function(r) { 12220 12221 if (curr >= 0) { 12222 if (typeof r === UNDEF) { 12223 r = thumbs.eq(curr).data(J.OBJ)[J.VISITORRATING] || 0; 12224 } 12225 12226 lightbox.find('.' + settings.lightboxCaptionClass + ' .ratingbox span') 12227 .removeClass('r0 r1 r2 r3 r4 r5') 12228 .addClass('r' + r); 12229 } 12230 }, 12231 12232 // Paint regions 12233 12234 paintRegions = function(regions, visible) { 12235 var el = $('<div>', { 12236 'class': settings.regionsClass 12237 }).appendTo(main); 12238 12239 for (var i = 0, r; i < regions.length; i++) { 12240 r = regions[i].split(';'); 12241 el.append($('<div>').css({ 12242 left: (100 * parseFloat(r[1])) + '%', 12243 top: (100 * parseFloat(r[2])) + '%', 12244 width: (100 * parseFloat(r[3])) + '%', 12245 height: (100 * parseFloat(r[4])) + '%' 12246 }).append('<span>' + r[0] + '</span>')); 12247 } 12248 12249 el.toggle(typeof visible !== UNDEF && visible); 12250 }, 12251 12252 // Show/hide regions 12253 12254 toggleRegions = function() { 12255 var el = lightbox.find('.' + settings.regionsClass); 12256 12257 if (el.length) { 12258 settings.regionsVisible = !settings.regionsVisible; 12259 if (el.is(':visible')) { 12260 el.fadeOut(); 12261 } else { 12262 el.fadeIn(); 12263 } 12264 } 12265 12266 return false; 12267 }, 12268 12269 // Creating caption and buttons 12270 12271 prepareCaption = function(item) { 12272 12273 caption = $('<div>', { 12274 'class': [ 12275 settings.lightboxCaptionClass, 12276 'scrollable', 12277 (slideshow || slideshowSuspended)? 'slideshow' : '', 12278 settings.infoPanelAdapt? 'adapt-width' : 'fixed-width'
12279 ].filter(Boolean).join(' ') 12280 }); 12281 12282 var txt = $('<div>', { 12283 'class': 'caption' 12284 }), 12285 buttons = $('<div>', { 12286 'class': 'buttons' 12287 }), 12288 hasShop, 12289 useLabels = settings.buttonLabels; 12290 12291 if (item[J.IMAGECAPTION]) { 12292 txt.append(item[J.IMAGECAPTION]); 12293 } 12294 12295 if (!isExternal && item[J.PHOTODATA] && !settings.metaAsPopup) { 12296 txt.append($('<div>', { 12297 'class': 'photodata', 12298 html: item[J.PHOTODATA] 12299 })); 12300 } 12301 12302 if (!txt.is(':empty')) { 12303 txt.find('[data-tooltip]').addTooltip(); 12304 caption.append(txt); 12305 } else { 12306 caption.addClass('no-caption'); 12307 } 12308 12309 if (settings.useRating) { 12310 12311 var r = item[J.RATING] || 0; 12312 12313 if (settings.visitorRating || r) { 12314 var s; 12315 12316 el = $('<div>', { 12317 'class': 'ratingbox' 12318 }).appendTo(buttons); 12319 12320 el.append($('<label>', { 12321 html: text.rating 12322 })); 12323 12324 s = $('<span>', { 12325 'class': 'rating', 12326 html: STARS 12327 }) 12328 .appendTo(el); 12329 12330 if (settings.visitorRating) { 12331 12332 if (item.hasOwnProperty(J.VISITORRATING)) { 12333 // Already exists 12334 r = item[J.VISITORRATING]; 12335 } else { 12336 if (!settings.useJalbumRating) { 12337 // Ignore jAlbum rating 12338 r = 0; 12339 } 12340 // Save the default 12341 item[J.VISITORRATING] = r; 12342 } 12343 12344 s.addClass('r' + r + (settings.useJalbumRating? '' : ' icon-close')) 12345 .on('click.' + ns, function(e) { 12346 var el = $(this), 12347 r = Math.minMax(0, Math.floor((getCoords(e).x - el.offset().left - parseFloat(el.css('paddingLeft'))) / 16), 5); 12348 //Math.minMax(0, Math.floor((getCoords(e).x - el.offset().left + 4) / el.width()) * 5, 5); 12349 12350 el.removeClass('r0 r1 r2 r3 r4 r5').addClass('r' + r); 12351 item[J.VISITORRATING] = r; 12352 12353 // Propagate to thumbnails 12354 self.trigger('refreshRating', thumbs.eq(curr)); 12355 12356 return false; 12357 }); 12358 } else { 12359 // Unalterable rating from jAlbum 12360 s.addClass('r' + r); 12361 } 12362 } 12363 } 12364 12365 // Extra sizes 12366 12367 if (!isExternal && extraSizes && item[J.CATEGORY] === 'image') { 12368 var p = album.getImagePath(item); 12369 12370 el = ($('<div>', { 12371 'class': 'download icon-download' 12372 })).appendTo(buttons); 12373 12374 for (var i = 0; i < extraSizes.length; i++) { 12375 el.append($('<a>', { 12376 text: extraSizes[i], 12377 href: p.replace('slides/', 'dl/' + extraSizes[i] + '/'), 12378 download: '' 12379 })); 12380 } 12381 } 12382 12383 // Print button 12384 12385 if (settings.printImage && item[J.CATEGORY] === 'image' && !settings.rightClickProtect) { 12386 el = $('<a>', { 12387 'class': settings.buttonClass + ' icon-printer' 12388 }).appendTo(buttons); 12389 12390 if (useLabels) { 12391 el.html('<span>' + text.print + '</span>'); 12392 } 12393 12394 if (!ISSEPARATEHOVER) { 12395 el.data('tooltip', useLabels? text.printLabel : text.print); 12396 } 12397 12398 el.on('click', function(e) { 12399 printImage((item[J.ORIGINAL] && settings.showDownload)? album.getOriginalPath(item) : album.getImagePath(item), 12400 item[J.TITLE] || '', 12401 item[J.THUMBCAPTION] || '' 12402 ); 12403 });
12404 } 12405 12406 // Download button 12407 12408 if (!isExternal && settings.showDownload) { 12409 var link; 12410 12411 if (!!item[J.ORIGINAL] && (isImage || isPdf || settings.allowDownloadOthers)) { 12412 // Original of images and PDF's are allowed to download, others with extra permission 12413 link = album.getOriginalPath(item); 12414 } else if (isImage && settings.allowDownloadScaled) { 12415 link = album.getImagePath(item); 12416 } 12417 12418 if (link) { 12419 12420 el = $('<a>', { 12421 'class': settings.buttonClass + ' icon-download download-btn', 12422 href: link, 12423 html: useLabels? ('<span>' + text.download + '</span>') : '', 12424 download: '' 12425 }).appendTo(buttons); 12426 12427 if (!ISSEPARATEHOVER) { 12428 // Adding size as tooltip 12429 getFileSize(link, function(size, el) { 12430 if (size && el) { 12431 el.addTooltip(useLabels? niceByte(size) : (text.download + ' [' + niceByte(size) + ']')); 12432 } 12433 }, el); 12434 } 12435 } 12436 } 12437 12438 // PayPal button 12439 12440 hasShop = settings.hasOwnProperty('shop') && album.hasShop(item) && settings.showShop; 12441 12442 if (hasShop) { 12443 var txt = album.getPriceRange(item); 12444 12445 txt = text.addCart + (txt? (' <b>' + txt + '</b>') : ''); 12446 12447 el = $('<a>', { 12448 'class': settings.buttonClass + ' icon-shopping-cart-add add-cart-btn' 12449 }).on('click', function(e) { 12450 12451 if (slideshow) { 12452 suspendAuto(); 12453 settings.shop.root.trigger('onClose', function() { 12454 if (slideshowSuspended) { 12455 resumeAuto(); 12456 } 12457 settings.shop.root.trigger('onClose', null); 12458 }); 12459 } 12460 12461 settings.shop.root.trigger('addItems', item); 12462 return false; 12463 12464 }).appendTo(buttons); 12465 12466 el.html('<span>' + txt + '</span>') 12467 .data('tooltip', text.shopLabel); 12468 12469 el = $('<a>', { 12470 'class': settings.buttonClass + ' secondary icon-shopping-cart view-cart-btn' 12471 }).on('click', function(e) { 12472 settings.shop.root.trigger('showCart'); 12473 return false; 12474 }).appendTo(buttons); 12475 12476 el.html('<span>' + text.viewCart + '</span>') 12477 .data('tooltip', text.viewCartLabel); 12478 } 12479 12480 // Feedback button 12481 12482 if (settings.hasOwnProperty('feedback') && settings.feedback['root'] && settings.showFeedback) { 12483 el = $('<a>', { 12484 'class': settings.buttonClass + ' icon-email-send feedback-btn' 12485 }).on('click', function(e) { 12486 12487 if (slideshow) { 12488 suspendAuto(); 12489 settings.feedback.root.trigger('onClose', function() { 12490 if (slideshowSuspended) { 12491 resumeAuto(); 12492 } 12493 settings.feedback.root.trigger('onClose', null); 12494 }); 12495 } 12496 12497 settings.feedback.root.trigger('addItems', item); 12498 return false; 12499 12500 }).appendTo(buttons); 12501 12502 if (useLabels) { 12503 el.html('<span>' + settings.feedbackBtnLabel + '</span>') 12504 .data('tooltip', settings.feedbackBtnTooltip); 12505 } else { 12506 el.data('tooltip', settings.feedbackBtnLabel); 12507 } 12508 12509 } 12510 12511 // Fotomoto button 12512 12513 if ((!LOCAL || DEBUG) && settings.hasOwnProperty('fotomoto') && !album.hideFotomoto(item)) { 12514 el = $('<a>', { 12515 'class': settings.buttonClass + ' ' + (hasShop? 'icon-fotomoto':'icon-shopping-cart') + ' fotomoto-btn' 12516 }).on('click', function(e) { 12517 e.preventDefault(); 12518 12519 if (slideshow) { 12520 stopAuto(true); 12521 } 12522 12523 //console.log('Fotomoto button clicked!'); 12524 if (typeof FOTOMOTO !== UNDEF) { 12525 FOTOMOTO.API.showWindow(10, 12526 album.getOriginalPath(item) || album.getItemPath(item) 12527 ); 12528 } 12529 return false; 12530 }).appendTo(buttons); 12531 12532 el.html('<span>' + text.fotomotoBtn + '</span>') 12533 .data('tooltip', text.fotomotoTooltip); 12534 } 12535 12536 // Photo data 12537 12538 if (!isExternal && item[J.PHOTODATA] && settings.metaAsPopup) { 12539 el = $('<a>', { 12540 'class': settings.buttonClass + ' icon-camera photodata-btn' 12541 }).appendTo(buttons); 12542 12543 if (useLabels) { 12544 el.html('<span>' + text.metaBtn + '</span>') 12545 .data('tooltip', text.metaLabel); 12546 } else { 12547 el.data('tooltip', text.metaBtn); 12548 } 12549 12550 el.on('click', function() { 12551 12552 if (slideshow) { 12553 suspendAuto(); 12554 } 12555 12556 lightbox.modal($('<div>', { 12557 'class': 'photodata', 12558 html: item[J.PHOTODATA] 12559 }), { 12560 title: text.metaBtn, 12561 icon: 'camera', 12562 onClose: function() { 12563 if (slideshowSuspended) { 12564 resumeAuto(); 12565 } 12566 } 12567 });
12568 12569 return false; 12570 }); 12571 } 12572 12573 // Regions 12574 12575 if (!isExternal && settings.showRegions && item[J.REGIONS]) { 12576 el = $('<a>', { 12577 'class': settings.buttonClass + ' icon-facetag regions-btn' 12578 }).appendTo(buttons); 12579 12580 if (useLabels) { 12581 el.html('<span>' + (settings.regionsBtn || text.regionsBtn) + '</span>') 12582 .data('tooltip', text.regionsLabel); 12583 } else { 12584 el.data('tooltip', (settings.regionsBtn || text.regionsBtn)); 12585 } 12586 12587 paintRegions(JSON.parse(item[J.REGIONS]), settings.regionsVisible); 12588 12589 el.on('click', toggleRegions); 12590 } 12591 12592 // Map 12593 12594 if (settings.showMap && !!item[J.LOCATION]) { 12595 12596 el = $('<a>', { 12597 'class': settings.buttonClass + ' icon-location map-btn' 12598 }).appendTo(buttons); 12599 12600 if (useLabels) { 12601 el.html('<span>' + text.mapBtn + '</span>') 12602 .data('tooltip', text.mapLabel); 12603 } else { 12604 el.data('tooltip', text.mapBtn); 12605 } 12606 12607 el.on('click', function() { 12608 var target = $('<div>', { 12609 'class': 'map-cont' 12610 }); 12611 12612 if (slideshow) { 12613 suspendAuto(); 12614 } 12615 12616 target.height($window.height() * settings.mapHeight); 12617 12618 lightbox.modal(target, { 12619 'class': 'no-padding large', 12620 onClose: function() { 12621 if (slideshowSuspended) { 12622 resumeAuto(); 12623 } 12624 } 12625 }); 12626 12627 setTimeout(function() { 12628 if (settings.hasOwnProperty('mapApiKey') && settings.mapApiKey) { 12629 // Google 12630 target.addMap({ 12631 apiKey: settings['mapApiKey'], 12632 type: settings['mapType'] || 'hybrid', 12633 zoom: settings['mapZoom'] || 16, 12634 location: item[J.LOCATION], 12635 fitBounds: false, 12636 fullscreenControl: false, 12637 onTypeChanged: function(type) { 12638 settings.mapType = type; 12639 }, 12640 onZoomChanged: function(zoom) { 12641 settings.mapZoom = zoom; 12642 } 12643 }); 12644 } else { 12645 // OSM 12646 target.addMap({ 12647 resPath: settings.resPath, 12648 zoom: settings['mapZoom'] || 16, 12649 location: item[J.LOCATION], 12650 fitBounds: false 12651 }); 12652 } 12653 }, 100); 12654 12655 return false; 12656 });
12657 } 12658 12659 // Mostphotos 12660 12661 if (!LOCAL && !isExternal && item[J.MOSTPHOTOS]) { 12662 el = $('<a>', { 12663 'class': settings.buttonClass + ' icon-shopping-cart mostphotos-btn', 12664 target: '_blank', 12665 href: 'https://mostphotos.com/' + item[J.MOSTPHOTOS] 12666 }).appendTo(buttons); 12667 12668 if (useLabels) { 12669 el.html('<span>' + text.mostphotosBtn + '</span>') 12670 .data('tooltip', text.mostphotosLabel); 12671 } else { 12672 el.data('tooltip', text.mostphotosBtn); 12673 } 12674 } 12675 12676 // Share buttons 12677 12678 if ((DEBUG || !LOCAL) && settings.share && settings.showShare) { 12679 12680 el = $('<a>', { 12681 'class': settings.buttonClass + ' icon-connect share-btn', 12682 }).appendTo(buttons); 12683 12684 if (useLabels) { 12685 el.html('<span>' + text.share + '</span>') 12686 .data('tooltip', text.shareLabel); 12687 } else { 12688 el.data('tooltip', text.share); 12689 } 12690 12691 el.on('click', function() { 12692 var target = $('<div>', { 12693 'class': 'social' 12694 }); 12695 12696 if (slideshow) { 12697 suspendAuto(); 12698 } 12699 12700 lightbox.modal(target, { 12701 title: text.shareOn, 12702 icon: 'connect', 12703 onClose: function() { 12704 if (slideshowSuspended) { 12705 resumeAuto(); 12706 } 12707 } 12708 }); 12709 12710 setTimeout(function() { 12711 target.renderShares({ 12712 sites: settings.share, 12713 title: item[J.TITLE] || item[J.NAME].stripExt().replace(/[-_]/g, ' ').capitalize(), 12714 description: item[J.COMMENT], 12715 image: album.getOptimalThumbPath(item, [ 960, 640 ], false), 12716 href: album.getAbsolutePath(item) 12717 }); 12718 }, 100); 12719 12720 return false; 12721 });
12722 } 12723 12724 if (settings.showNumbers) { 12725 buttons.prepend($('<div>', { 12726 'class': 'numbers', 12727 'html': '<span>' + (curr + 1) + ' </span>/ ' + thumbs.length 12728 })); 12729 } 12730 12731 // Audio clip 12732 12733 if (!isAudio && item[J.AUDIOCLIP]) { 12734 // Wrapper 12735 el = $('<div>', { 12736 'class': 'audio-clip paused' 12737 }).appendTo(buttons); 12738 12739 // Button 12740 var btn = $('<a>', { 12741 'class': settings.buttonClass + ' icon-volume-up audioclip-btn', 12742 }).appendTo(el); 12743 12744 btn.on('click', function() { 12745 12746 if (btn.hasClass('icon-pause')) { 12747 pauseAudioClip(); 12748 } else { 12749 startAudioClip() 12750 } 12751 12752 return false; 12753 }); 12754 12755 // Audio element 12756 audioClip = $('<audio>', { 12757 'class': 'audioclip', 12758 src: album.getAudioClipPath(item), 12759 controlsList: 'nofullscreen nodownload noremote noplaybackrate' 12760 }).appendTo(el); 12761 12762 // Type attribute saving and duration 12763 if (item[J.AUDIOCLIP].hasOwnProperty(J.TYPE)) { 12764 audioClip[0]['type'] = item[J.AUDIOCLIP][J.TYPE]; 12765 if (item[J.AUDIOCLIP].hasOwnProperty(J.DURATION)) { 12766 audioClip.data('duration', item[J.AUDIOCLIP][J.DURATION]); 12767 } 12768 } 12769 12770 var volume = $('<input class="volume" type="range" name="volume" min="0" max="100" value="' + (settings.audioClipVolume * 100) + '">'); 12771 12772 if (useLabels) { 12773 12774 btn.append('<span>' + text.play + '</span>') 12775 .data('tooltip', text.audioClipLabel); 12776 12777 el.append(volume); 12778 12779 } else { 12780 12781 btn.data('tooltip', volume); 12782 } 12783 12784 volume.on('change', function(e) { 12785 var v = $(this).val(); 12786 12787 if (typeof v !== UNDEF) { 12788 settings.audioClipVolume = v / 100; 12789 } 12790 12791 audioClip[0]['volume'] = settings.audioClipVolume; 12792 savePrefs(settings, settings.prefsKept, 'lb_pref'); 12793 12794 if (typeof settings.onVolumeChange === FUNCTION) { 12795 settings.onVolumeChange(settings.audioClipVolume); 12796 } 12797 });
12798 12799 // Setting loudness 12800 audioClip[0]['volume'] = settings.audioClipVolume; 12801 12802 if (settings.autoplayAudioClips) { 12803 // Trying to autoplay 12804 startAudioClip(); 12805 } 12806 12807 } else { 12808 audioClip = $(); 12809 } 12810 12811 if (!buttons.is(':empty')) { 12812 caption.addClass(settings.hasbuttonsClass); 12813 caption.append(buttons); 12814 buttons.find('.button').not('[download]').addTooltip(); 12815 } 12816 12817 if (caption.is(':empty')) { 12818 lightbox.removeClass(settings.captionVisibleClass); 12819 lightbox.addClass('no-caption'); 12820 } else { 12821 lightbox.toggleClass(settings.captionVisibleClass, settings.captionVisible); 12822 lightbox.removeClass('no-caption'); 12823 lightbox.append(caption); 12824 } 12825 12826 }, 12827 12828 // Preloading the next image (direction sensitive) 12829 12830 preloadNext = function() { 12831 var nextItem; 12832 12833 if (direction < 0 && curr > 0) { 12834 nextItem = thumbs.eq(curr - 1).data(J.OBJ); 12835 } else if (curr < thumbs.length - 1) { 12836 nextItem = thumbs.eq(curr + 1).data(J.OBJ); 12837 } 12838 12839 if (nextItem) { 12840 if (nextItem[J.CATEGORY] === 'image') { 12841 var next = new Image(); 12842 next.src = album.getOptimalImagePath(nextItem, getLightboxDims(), settings.displayOriginals); 12843 } 12844 } 12845 }, 12846 12847 // Image clicked :: toggle controls or audio/video controls 12848 12849 imageClicked = function(e) { 12850 12851 if ($(e.target).closest('.lightbox-caption').length) { 12852 return true; 12853 } 12854 12855 var touched = $('html').data('whatinput') === 'touch' || e.type === 'touchend'; 12856 12857 //log(e.type); 12858 12859 if (isVideo || isAudio) { 12860 var m = media[0]; 12861 12862 // If the tap/click landed on the native media control strip (poster 12863 // play button, scrubber, volume, etc.), let the browser handle it 12864 // natively instead of hijacking play/pause and yanking the controls 12865 // attribute mid-gesture. There's no DOM node to .closest() into here 12866 // since native controls live in the user-agent shadow root, so we 12867 // test the tap's vertical position against the control strip height. 12868 if (m.controls && isTapOnMediaControls(e, m)) { 12869 return true; 12870 } 12871 12872 if (m.paused) { 12873 m.play(); 12874 if (VEND !== 'ms') { 12875 m.controls = false; 12876 } 12877 if (touched) { 12878 toggleControls(); 12879 if (lightbox.hasClass(settings.thumbsVisibleClass)) { 12880 hideThumbs(); 12881 } 12882 if (lightbox.hasClass(settings.captionVisibleClass)) { 12883 hideCaption(); 12884 } 12885 } 12886 } else { 12887 m.pause(); 12888 if (VEND !== 'ms') { 12889 m.controls = true; 12890 } 12891 if (touched) { 12892 if (settings.clickAction === 'togglecontrols') { 12893 toggleControls(); 12894 } else if (settings.clickAction === 'toggleall') { 12895 if (touched && lightbox.hasClass(settings.controlsHideClass)) { 12896 lightbox.removeClass(settings.controlsHideClass); 12897 } else { 12898 toggleAll(); 12899 } 12900 } 12901 } 12902 } 12903 return false; 12904 12905 } else if (isImage) { 12906 12907 if (settings.clickAction === 'nextimage') { 12908 // image navigation 12909 if ((getCoords(e).x - main.position().left) > (main.width() / 2)) { 12910 nextImage(); 12911 } else { 12912 previousImage(); 12913 } 12914 if (touched) { 12915 showControls(); 12916 } 12917 } else if (settings.clickAction === 'togglecontrols') { 12918 toggleControls(); 12919 } else if (settings.clickAction === 'toggleall') { 12920 if (touched && lightbox.hasClass(settings.controlsHideClass)) { 12921 lightbox.removeClass(settings.controlsHideClass); 12922 } else { 12923 toggleAll(); 12924 } 12925 } 12926 } 12927 }, 12928 12929 removeOld = function() { 12930 if (oldMain.length) { 12931 oldMain.remove(); 12932 oldMain = $(); 12933 } 12934 }, 12935 12936 // Doing all the animations in one timeframe 12937 12938 doTransitions = function(doneFn) { 12939 12940 switch (settings.transitionType) { 12941 12942 case 'crossFadeAndSlide': 12943 var mx = main.getMatrix(), 12944 ox = mx[4]; 12945 12946 if (direction) { 12947 // Offset by 100 pixels 12948 mx[4] += 100 * direction; 12949 12950 main.css({
12951 transition: 'none', 12952 transform: 'matrix(' + mx.join(',') + ')' 12953 }); 12954 } 12955 12956 window.requestAnimationFrame(function() { 12957 12958 if (oldMain.length && oldMain !== main) { 12959 12960 if (oldMain.data('swipe') === 'swipe') { 12961 setTimeout(removeOld, transitionSpeed / 2); 12962 } else { 12963 var omx = oldMain.getMatrix(); 12964 12965 omx[4] -= 100 * direction; 12966 12967 oldMain.one('transitionend', removeOld).css({ 12968 transition: 'transform ' + (transitionSpeed / 2) + 'ms ease-out, opacity ' + (transitionSpeed / 2) + 'ms ease-out' 12969 }).css({ 12970 transform: 'matrix(' + omx.join(',') + ')', 12971 opacity: 0 12972 }); 12973 } 12974 } 12975 12976 main.one('transitionend', function() { 12977 afterTransition(doneFn); 12978 }); 12979 12980 setTimeout(function() { 12981 if (direction) { 12982 // Restore final pos 12983 mx[4] = ox; 12984 // Animate to the final position and opacity 12985 main.css({ 12986 transition: 'transform ' + transitionSpeed + 'ms ease-out, opacity ' + transitionSpeed + 'ms ease-out' 12987 }).css({ 12988 transform: 'matrix(' + mx.join(',') + ')', 12989 opacity: 1 12990 }); 12991 } else { 12992 main.css({ 12993 transition: 'transform ' + transitionSpeed + 'ms ease-out, opacity ' + transitionSpeed + 'ms ease-out' 12994 }).css({ 12995 opacity: 1 12996 }); 12997 } 12998 }, TOUCHENABLED? 350 : 50); 12999 }); 13000 13001 break; 13002 13003 case 'crossFadeAndZoom': 13004 13005 var mx = main.getMatrix(), 13006 osx = mx[0], 13007 osy = mx[3]; 13008 13009 if (direction) { 13010 mx[0] *= (1 - direction / 40); 13011 mx[3] *= (1 - direction / 40); 13012 13013 main.css({ 13014 transition: 'none', 13015 transform: 'matrix(' + mx.join(',') + ')' 13016 }); 13017 } 13018 13019 window.requestAnimationFrame(function() { 13020 13021 if (oldMain.length && oldMain !== main) { 13022 13023 if (oldMain.data('swipe') === 'swipe') { 13024 setTimeout(removeOld, transitionSpeed / 2); 13025 } else { 13026 var omx = oldMain.getMatrix(); 13027 13028 omx[0] *= (1 + direction / 20); 13029 omx[3] *= (1 + direction / 20); 13030 13031 oldMain.one('transitionend', function() { 13032 oldMain.remove(); 13033 oldMain = $(); 13034 }).css({ 13035 transition: 'transform ' + (transitionSpeed / 2) + 'ms ease-out, opacity ' + (transitionSpeed / 2) + 'ms ease-out', 13036 transform: 'matrix(' + omx.join(',') + ')', 13037 opacity: 0 13038 }); 13039 } 13040 } 13041 13042 main.one('transitionend', function() { 13043 afterTransition(doneFn); 13044 }); 13045 13046 setTimeout(function() { 13047 if (direction) { 13048 // Restore final scale 13049 mx[0] = osx; 13050 mx[3] = osy; 13051 // Animate to the final scale and opacity 13052 main.css({ 13053 transition: 'transform ' + transitionSpeed + 'ms ease-out, opacity ' + transitionSpeed + 'ms ease-out', 13054 transform: 'matrix(' + mx.join(',') + ')', 13055 opacity: 1 13056 }); 13057 } else { 13058 // No direction -> no scale 13059 main.css({ 13060 transition: 'transform ' + transitionSpeed + 'ms ease-out, opacity ' + transitionSpeed + 'ms ease-out', 13061 opacity: 1 13062 }); 13063 } 13064 }, TOUCHENABLED? 350 : 50); 13065 }); 13066 13067 break; 13068 13069 default: 13070 13071 main.css({ 13072 transition: 'none', 13073 opacity: 0 13074 }); 13075 13076 window.requestAnimationFrame(function() { 13077 13078 if (oldMain.length && oldMain !== main) { 13079 13080 if (oldMain.data('swipe') === 'swipe') { 13081 setTimeout(removeOld, transitionSpeed / 2); 13082 } else { 13083 oldMain.one('transitionend', function() { 13084 oldMain.remove(); 13085 oldMain = $(); 13086 }).css({ 13087 transition: 'opacity ' + (transitionSpeed / 2) + 'ms ease-out', 13088 opacity: 0 13089 });
13090 } 13091 } 13092 13093 main.one('transitionend', function() { 13094 afterTransition(doneFn); 13095 }).css({ 13096 transition: 'opacity ' + transitionSpeed + 'ms ease-out', 13097 opacity: 1 13098 }); 13099 13100 }); 13101 } 13102 13103 }, 13104 13105 // Transition has completed 13106 13107 afterTransition = function(doneFn) { 13108 13109 if (typeof doneFn === FUNCTION) { 13110 doneFn.call(); 13111 } 13112 13113 if (typeof settings.onLoadEnd === FUNCTION) { 13114 settings.onLoadEnd(thumbs.eq(curr)); 13115 } 13116 13117 if (settings.preloadNext) { 13118 preloadNext(); 13119 } 13120 }, 13121 13122 // The image is loaded 13123 13124 imageReady = function() { 13125 13126 loadCounter--; 13127 13128 if (loadCounter > 0) { 13129 // Still has to load another image: (poster image) 13130 return; 13131 } 13132 13133 lb_activity.hide(); 13134 13135 /* 13136 // Performace metrics 13137 if (typeof performance !== UNDEF) { 13138 13139 console.log('performance exists'); 13140 13141 var pl = performance.getEntriesByType("resource"); 13142 13143 if (pl && Array.isArray(pl)) { 13144 var p = pl[pl.length - 1]; 13145 13146 console.log('performance last entry: ' + p); 13147 13148 if (typeof image[0] !== UNDEF && p.name === image[0].src) { 13149 connectionSpeed = connectionSpeed? ((connectionSpeed + p.decodedBodySize / p.duration) / 2) : (p.decodedBodySize / p.duration); 13150 } 13151 13152 if (DEBUG) { 13153 console.log(p.initiatorType + ':' + p.name.substring(p.name.lastIndexOf('/') + 1) + ' [' + Math.round(p.decodedBodySize / 1000) + 'kB] loaded in ' + Math.round(p.duration) + 'ms => ' + (p.decodedBodySize / p.duration / 1000).toFixed(2) + 'MB/s' + ' (avg:' + (connectionSpeed / 1000).toFixed(2) + 'MB/s)'); 13154 } 13155 } 13156 13157 } 13158 */ 13159 13160 // Reporting image load in Debug mode 13161 if (DEBUG) { 13162 var d = new Date(); 13163 if (isExternal || isVr || isWebPage) { 13164 console.log((isExternal? 'External content' : isWebPage? 'Web page' : '360 player') + ' loaded: ' + (d - loadTimer) + 'ms'); 13165 } else if (image.length) { 13166 console.log(((isVideo || isAudio)? 'Media' : 'Image') + ' [' + curr + '] loaded: ' + (d - loadTimer) + 'ms src="' + image[0].src + '"' + (connectionSpeed? (' avg. speed: ' + (connectionSpeed / 1000).toFixed(2) + 'MB/s') : '')); 13167 if (typeof image[0] === UNDEF || typeof media[0] === UNDEF) { 13168 console.log('Premature ' + ((typeof image[0] === UNDEF)? ('loadImage.done(' + image[0].src + ')') : ('loadMedia.done('+ media[0].src + ')'))); 13169 } 13170 } 13171 } 13172 13173 // Track the view in Google Analytincs 13174 if (typeof window.gtag === FUNCTION) { 13175 var src = (isVideo || isAudio)? media[0].src : isVr? main.children().eq(0).data('src') : image[0].src, 13176 path = src.getDir().replace(/^(https?\:\/\/[^\/]+\/)/, ''); 13177 13178 window.gtag('event', 'image_view', { 13179 image_name: src, 13180 album_name: path, 13181 image_index: curr + 1 13182 }); 13183 } 13184 13185 // Right click protect 13186 if ((isImage || isVideo || isAudio) && settings.rightClickProtect) { 13187 media.on('contextmenu', function(e) { 13188 e.preventDefault(); 13189 return false; 13190 });
13191 } 13192 13193 // Audio / video initilazion 13194 if (isVideo || isAudio) { 13195 13196 media.attr({ 13197 autoplay: settings.videoAuto, 13198 loop: settings.videoLoop 13199 }); 13200 media[0].volume = settings.volume; 13201 13202 media.on('volumechange.' + ns, function() { 13203 settings.volume = this.volume; 13204 }); 13205 } 13206 13207 if (oldCaption.length) { 13208 13209 // Removing the old caption 13210 oldCaption.find('.buttons a').trigger('removeTooltip'); 13211 13212 if (oldCaption.length > 1) { 13213 oldCaption.eq(-1).prevAll('.' + settings.lightboxCaptionClass).remove(); 13214 oldCaption = lightbox.find('.' + settings.lightboxCaptionClass); 13215 } 13216 13217 if (lightbox.hasClass(settings.captionVisibleClass)) { 13218 oldCaption.one('transitionend', function() { 13219 oldCaption.remove(); 13220 oldCaption = $(); 13221 }).css({ 13222 transition: 'opacity ' + (transitionSpeed / 2) + 'ms ease-out', 13223 opacity: 0 13224 }); 13225 } else { 13226 oldCaption.remove(); 13227 } 13228 } 13229 13230 if (!isVr && !isPdf && !isWebPage && !isVideo && (!isExternal || !main.children('iframe').length)) { 13231 // Swipe handler 13232 main.swipe({ 13233 avoidNavigationOnZoomed: 13234 settings.avoidNavigationOnZoomed, 13235 13236 onSwipeStart: function() { 13237 $(this).trigger('autopanoStop'); 13238 $(this).hideAllTooltips(); 13239 }, 13240 13241 onSwipedLeft: function() { 13242 main.data('swipe', 'swipe'); 13243 nextImage(); 13244 }, 13245 13246 onSwipedRight: function() { 13247 main.data('swipe', 'swipe'); 13248 previousImage(); 13249 }, 13250 13251 onSwipedDown: function() { 13252 main.data('swipe', 'down'); 13253 quitLightbox(); 13254 }, 13255 13256 onPinchZoom: function(e) { 13257 main.data('swipe', 'pinch'); 13258 if (lightbox.data('panomove')) { 13259 e.preventDefault(); 13260 $(this).trigger('autopanoStop'); 13261 } 13262 fixZoom(main.getScale()[0], getCoords(e)); 13263 }, 13264 13265 onFinished: function() { 13266 if (!dontRemove) { 13267 $(this).trigger('removeSwipe'); 13268 $(this).remove(); 13269 } 13270 }, 13271 13272 onClick: function(e) { 13273 if (lightbox.data('panomove')) { 13274 main.data('swipe', 'panostop'); 13275 e.preventDefault(); 13276 $(this).trigger('autopanoStop'); 13277 } else { 13278 main.data('swipe', 'click'); 13279 imageClicked(e); 13280 } 13281 }, 13282 13283 onRightClick: function(e) { 13284 stopAuto(true); 13285 }, 13286 13287 onDoubleClick: (settings.useZoom && settings.doubleClickZoom)? 13288 function(e) { 13289 var c = getCoords(e); 13290 main.data('swipe', 'doubleclick'); 13291 if (settings.zoomSlider) { 13292 if (zoomLevel < 100) { 13293 increaseZoom(50, c); 13294 } else { 13295 setZoom(0); 13296 } 13297 } else { 13298 toggleZoom(c); 13299 } 13300 } 13301 : 13302 null 13303 });
13304 13305 } /*else if (!isVideo) { 13306 13307 main.on('click.' + ns + ' ' + TOUCH.END + '.' + ns, function(e) { 13308 if (!main.data('clickhandled')) { 13309 main.data('swipe', 'click'); 13310 main.data('clickhandled', true); 13311 setTimeout(function() { 13312 // Auto-remove after 200 ms 13313 main.removeData('clickhandled'); 13314 }, 200); 13315 imageClicked(e); 13316 } 13317 }); 13318 13319 }*/ 13320 13321 // Placing and centering the new image 13322 fitImage(false, null, function(doneFn) { 13323 // Running transitions 13324 doTransitions(doneFn); 13325 }); 13326 13327 // Check if video has started 13328 if (isVideo && settings.videoAuto && media[0].paused) { 13329 playVideo(media[0]); 13330 } 13331 13332 // Continuing slideshow 13333 if (slideshow || slideshowSuspended) { 13334 if ((isAudio || isVideo) && settings.videoAuto) { 13335 suspendAuto(); 13336 media[0].onended = function() { 13337 resumeAuto(); 13338 }; 13339 } else { 13340 clearTimeout(slideshow); 13341 slideshow = setTimeout(nextImage, slideshowDelay); 13342 showProgress(); 13343 } 13344 } else { 13345 //stopAuto(true); 13346 if (settings.autohideControls) { 13347 hideControlsLater(); 13348 } 13349 } 13350 13351 }, 13352 13353 // Recentering after swipe for example 13354 13355 recenter = function(el) { 13356 el = el || main; 13357 if (el.data('swipe') === 'swipe') { 13358 var matrix = main.getMatrix(); 13359 matrix[4] = -main.width() / 2; 13360 el.one('transitionend', function() { 13361 el.data('swipe', null); 13362 }).css({ 13363 opacity: 1, 13364 transition: 'opacity ' + (settings.transitionSpeed * 2) + 'ms ease-out, transform ' + (settings.transitionSpeed * 2) + 'ms ease-out', 13365 transform: 'matrix(' + matrix.join(',') + ')' 13366 });
13367 } 13368 }, 13369 13370 // Showing lightbox 13371 13372 showLightbox = function() { 13373 13374 lightbox.css({ 13375 display: 'block', 13376 visibility: 'visible', 13377 transition: 'opacity ' + settings.transitionSpeed + 'ms ease-out' 13378 }) 13379 .one('transitionend', function() { 13380 if (settings.autoStart) { 13381 startAuto(); 13382 } 13383 }) 13384 .css('opacity', 1); 13385 13386 if (typeof settings.onEnter === FUNCTION) { 13387 settings.onEnter(); 13388 } 13389 }, 13390 13391 // Main entry point to load a new image 13392 13393 loadImage = function(n, doneFn) { 13394 var item; 13395 13396 if (typeof n !== 'number') { 13397 n = thumbs.index(n); 13398 } 13399 13400 dontRemove = false; 13401 13402 if (n < 0 || n >= thumbs.length) { 13403 // Out of bounds move 13404 13405 if (n < 0) { 13406 13407 switch (settings.afterLast) { 13408 case 'donothing': 13409 dontRemove = true; 13410 n = 0; 13411 if (main.data('swipe') === 'swipe') { 13412 recenter(); 13413 } 13414 break; 13415 13416 case 'startover': 13417 n = thumbs.length - 1; 13418 break; 13419 13420 case 'onelevelup': 13421 if (settings.level) { 13422 window.location.href = '../' + settings.indexName; 13423 } 13424 n = 0; 13425 break; 13426 13427 case 'nextfolder': 13428 if (settings.baseSet && settings.previousFoldersLast) { 13429 window.location.href = settings.previousFoldersLast; 13430 } else { 13431 n = 0; 13432 if (main.data('swipe') === 'swipe') { 13433 recenter(); 13434 } 13435 } 13436 break; 13437 13438 case 'nextindex': 13439 if (settings.baseSet && settings.previousFolderPath) { 13440 window.location.href = settings.previousFolderPath; 13441 } else { 13442 n = 0; 13443 if (main.data('swipe') === 'swipe') { 13444 recenter(); 13445 } 13446 } 13447 break; 13448 13449 default: 13450 n = 0; 13451 quitLightbox(); 13452 return; 13453 } 13454 13455 } else { 13456 13457 switch (settings.afterLast) { 13458 13459 case 'donothing': 13460 dontRemove = true; 13461 n = thumbs.length - 1; 13462 stopAuto(); 13463 if (main.data('swipe') === 'swipe') { 13464 recenter(); 13465 } 13466 break; 13467 13468 case 'startover': 13469 n = 0; 13470 break; 13471 13472 case 'onelevelup': 13473 if (settings.level) { 13474 window.location.href = '../' + settings.indexName; 13475 } 13476 n = thumbs.length - 1; 13477 stopAuto(); 13478 break; 13479 13480 case 'nextfolder': 13481 if (settings.baseSet && settings.nextFoldersFirst) { 13482 window.location.href = settings.nextFoldersFirst + (slideshow? '&slideshow' : ''); 13483 } else { 13484 n = thumbs.length - 1; 13485 stopAuto(); 13486 if (main.data('swipe') === 'swipe') { 13487 recenter(); 13488 } 13489 } 13490 break; 13491 13492 case 'nextindex': 13493 if (settings.baseSet && settings.nextFolderPath) { 13494 window.location.href = settings.nextFolderPath; 13495 } else { 13496 n = thumbs.length - 1; 13497 stopAuto(); 13498 if (main.data('swipe') === 'swipe') { 13499 recenter(); 13500 } 13501 } 13502 break; 13503 13504 case 'ask': 13505 13506 var sr = stopAuto(), 13507 buttons = new Array(); 13508 13509 dontRemove = true; 13510 13511 if (main.data('swipe') === 'swipe') { 13512 recenter(); 13513 } 13514 13515 n = thumbs.length - 1; 13516 13517 if (thumbs.length > 1) { 13518 // Start over 13519 buttons.push({ 13520 t: text.startOver, 13521 c: 'icon-loop', 13522 h: function(lb) { 13523 loadImage(0); 13524 } 13525 }); 13526 } 13527 13528 if (settings.level) { 13529 // Up one level 13530 buttons.push({ 13531 t: settings.level? text.upOneLevel : (settings.homepageLinkText || text.backToHome), 13532 c: 'icon-one-level-up', 13533 h: function() { 13534 window.location.href = '../' + settings.indexName; 13535 } 13536 });
13537 } 13538 13539 // Back to thumbnails 13540 buttons.push({ 13541 t: text.backToIndex, 13542 c: 'icon-arrow-up', 13543 h: function() { 13544 quitLightbox(); 13545 } 13546 }); 13547 13548 if (settings.baseSet) { 13549 13550 if (settings.nextFoldersFirst) { 13551 // Go to next folder 13552 buttons.push({ 13553 t: text.nextFolder, 13554 c: 'icon-arrow-right', 13555 h: function() { 13556 window.location.href = settings.nextFoldersFirst + (sr? '&slideshow' : ''); 13557 } 13558 }); 13559 } 13560 13561 if (settings.nextFolderPath) { 13562 // Go to next index 13563 buttons.push({ 13564 t: text.nextIndex, 13565 c: 'icon-thumbnails', 13566 h: function() { 13567 window.location.href = settings.nextFolderPath; 13568 } 13569 }); 13570 } 13571 } 13572 13573 lightbox.modal($('<p>', { 13574 'class': 'text-center', 13575 text: text.atLastPageQuestion 13576 }), buttons, { 13577 onClose: function() { 13578 loadImage(thumbs.length - 1); 13579 }, 13580 'class': 'secondary', 13581 icon: 'info', 13582 title: text.atLastPage 13583 }); 13584 13585 return; 13586 13587 default: 13588 // Back to index 13589 n = slideshow? 0 : (thumbs.length - 1); 13590 stopAuto(); 13591 // Resetting play position to 0 13592 quitLightbox(thumbs[0]); 13593 return; 13594 } 13595 } 13596 } 13597 13598 // Direction is calculated if the current thumb exists 13599 if (lightbox.is(':visible')) { 13600 // Lightbox is on 13601 if ((Math.abs(n - curr) >= thumbs.length - 1) && settings.afterLast === 'startover') { 13602 // Flip over 13603 direction = 1; 13604 } else { 13605 direction = (curr > n)? -1 : ((curr < n)? 1 : 0); 13606 } 13607 13608 if (curr === n) { 13609 // the requested image is already on screen :: nothing to do 13610 return; 13611 } 13612 13613 main.hideAllTooltips(); 13614 13615 if (settings.autohideControls) { 13616 hideControlsLater(); 13617 } 13618 13619 } else { 13620 // We're on the index page :: show lightbox 13621 listening = true; 13622 $body.add($('html')).css('overflow', 'hidden'); 13623 $body.addClass(settings.lightboxOnClass); 13624 direction = 0; 13625 13626 if (!isFullscreen() && settings.lightboxFullscreen && (SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles)) { 13627 requestFullscreen(); 13628 } 13629 13630 if (settings.useRating) { 13631 updateRating(); 13632 } 13633 13634 showLightbox(); 13635 //backgroundAudioOn = backgroundAudioRoot.length && !backgroundAudioRoot.data('paused'); 13636 13637 lazyloadThumbs(fitThumbstrip); 13638 } 13639 13640 closeModal(); 13641 13642 curr = n; 13643 thumb = thumbs.eq(curr); 13644 item = thumb.data(J.OBJ); 13645 13646 if (settings.useThumbstrip && thumbs.length > 1) { 13647 setActiveThumb(); 13648 } 13649 13650 if (!item) { 13651 console.log('Fatal error: image (' + curr + ') is missing from the database! (Upload data1.json again!)'); 13652 return; 13653 } 13654 13655 lb_activity.show(); 13656 hideProgress(); 13657 getImageType(item); 13658 getSlideshowTiming(item); 13659 13660 if (typeof doneFn === FUNCTION) { 13661 doneFn.call(this); 13662 } 13663 13664 setTimeout(function() { 13665 13666 oldMain = lightbox.find('.' + settings.lightboxMainClass); 13667 oldCaption = lightbox.find('.' + settings.lightboxCaptionClass); 13668 13669 if (oldMain.length) { 13670 13671 oldMain.trigger('autopanoRemove').off('.' + ns).removeClass(settings.currClass); 13672 13673 if (settings.muteBackgroundAudio && (isAudio || isVideo)) { 13674 muteBackgroundAudio(); 13675 } 13676 13677 fadeMedia(oldMain); 13678 pauseAudioClip(); 13679 13680 oldMain.off('click.nonimage'); 13681 } 13682 13683 // Delayed image loading 13684 13685 loadCounter = 1; 13686 loadTimer = new Date(); 13687 13688 main = $('<div>', { 13689 'class': 'lightbox-main ' + settings.currClass + ' ' + (isExternal? 'external' : (isVr? 'vr' : (isPdf? 'pdf' : item[J.CATEGORY]))) 13690 }) 13691 .css({ 13692 opacity: 0, 13693 transform: 'matrix(1,0,0,1,-50%,-50%)' 13694 })
13695 .appendTo(lightbox); 13696 13697 if (isWebPage) { 13698 if (item[J.CUSTOMPAGECONTENT]) { 13699 main.append('<div class="page-wrap content">' + item[J.CUSTOMPAGECONTENT].replace(/\${resPath}/g, settings.resPath) + '</div>') 13700 .css({ 13701 padding: '2rem', 13702 width: Math.min(800, Math.round(lightbox.width() * settings.fitRatio)), 13703 transform: 'translate(-50%, -50%)' 13704 }) 13705 .addClass((album.getOriginalPageName(item) || 'user') + '-page') 13706 .show() 13707 .one('DOMReady', imageReady); 13708 } 13709 // Considered ready after 200ms or the "ready" event, whatever comes first 13710 setTimeout(imageReady, 200); 13711 } else { 13712 13713 // Shop and feedback 13714 if (settings.hasOwnProperty('shop') && album.hasShop(item) || 13715 settings.hasOwnProperty('feedback') && settings.feedback['root'] && !album.disableFeedback(item)) { 13716 main.append($('<a>', { 13717 'class': settings.checkboxClass + (thumb.hasClass(settings.selectedClass)? (' ' + settings.checkmarkClass) : '') 13718 }).on('click', function() { 13719 $(this).toggleClass(settings.checkmarkClass); 13720 thumb.trigger('toggleCard'); 13721 return false; 13722 })); 13723 if (settings.hasOwnProperty('shop') && album.hasShop(item)) { 13724 main.append($('<a>', { 13725 'class': 'icon-shopping-cart-add add-cart-btn' 13726 }) 13727 .addTooltip(text.addCart, { pos: [0,1,2,1] }) 13728 .on('click', function(e) { 13729 if (slideshow) { 13730 suspendAuto(); 13731 settings.shop.root.trigger('onClose', function() { 13732 if (slideshowSuspended) { 13733 resumeAuto(); 13734 } 13735 settings.shop.root.trigger('onClose', null); 13736 }); 13737 } 13738 settings.shop.root.trigger('addItems', item); 13739 return false; 13740 })); 13741 } else { 13742 main.append($('<a>', { 13743 'class': 'icon-email-send add-cart-btn', 13744 text: ' ' + settings.feedbackAddBtnLabel 13745 }).on('click', function(e) { 13746 if (slideshow) { 13747 suspendAuto(); 13748 settings.feedback.root.trigger('onClose', function() { 13749 if (slideshowSuspended) { 13750 resumeAuto(); 13751 } 13752 settings.feedback.root.trigger('onClose', null); 13753 }); 13754 } 13755 settings.feedback.root.trigger('addItems', item); 13756 return false; 13757 })); 13758 } 13759 } 13760 13761 if (isVr) { 13762 13763 main.css({ 13764 width: Math.round(lightbox.width() - 2 * lbPadding), 13765 height: Math.round(lightbox.height() - 2 * lbPadding) 13766 }) 13767 .attr('id', 'vr' + curr) 13768 .addClass('vr') 13769 .show() 13770 .one('DOMReady', imageReady); 13771 13772 // Photosphere viewer by Jeremy Heleine 13773 if (typeof PhotoSphereViewer !== UNDEF) { 13774 setTimeout(function() { 13775 var opt = { container: main[0] }, 13776 psv, 13777 gp; 13778 13779 if (item[J.CATEGORY] === 'video') { 13780 opt.panorama = album.getVideoPath(item); 13781 opt.adapter = PhotoSphereViewer.EquirectangularVideoAdapter; 13782 opt.plugins = [ PhotoSphereViewer.VideoPlugin, {} ]; 13783 opt.navbar = [ 'zoom', 'fullscreen' ]; 13784 } else { 13785 opt.panorama = album.getOriginalPath(item) || album.getImagePath(item); 13786 opt.navbar = [ 'autorotate', 'zoom', 'fullscreen' ]; 13787 if (settings.autoRotate360 && settings.rotateSpeed360) { 13788 opt.autorotateDelay = 1000; 13789 } 13790 opt.autorotateSpeed = settings.rotateSpeed360 + 'rpm'; 13791 } 13792 13793 if (gp = item['gpano']) { 13794 if (gp['fov']) { 13795 opt.defaultZoomLvl = gp.fov; 13796 } 13797 if (gp['pan'] || gp['tilt'] || gp['roll']) { 13798 opt.sphereCorrection = { 13799 pan: Math.PI * gp['pan'] / 180, 13800 tilt: Math.PI * gp['tilt'] / 180, 13801 roll: Math.PI * gp['roll'] / 180 13802 }; 13803 } 13804 } 13805 13806 psv = new PhotoSphereViewer.Viewer(opt); 13807 $(psv.container).data('src', opt.panorama); 13808 // Considered ready after 100ms 13809 setTimeout(imageReady, 100); 13810 13811 }, 50); 13812 } else { 13813 console.log('Fatal Error: Missing "photo-sphere-viewer.min.js"!'); 13814 } 13815 13816 // Considered ready after 200ms or the "ready" event, whatever comes first 13817 setTimeout(imageReady, 200); 13818 13819 } else if (isImage) { 13820 13821 // Image 13822 // Creating 13823 media = image = $('<img>').appendTo(main); 13824 13825 if (DEBUG) { 13826 console.log('Loading image [' + curr + '] src="' + album.getOptimalImagePath(item, getLightbox
13826Dims(), settings.displayOriginals) + '"'); 13827 } 13828 13829 if (image[0].complete && image[0].naturalWidth > 0) { 13830 // From cache 13831 imageReady(); 13832 13833 } else { 13834 // Loading 13835 image.one('load.' + ns, function(e) { 13836 // Triggered on load event 13837 imageReady(); 13838 }); 13839 } 13840 13841 // Applying the source 13842 image[0].src = album.getOptimalImagePath(item, getLightboxDims(), settings.displayOriginals); 13843 13844 } else if (isAudio || isVideo) { 13845 13846 if (isVideo) { 13847 13848 var dur = album.getVideoDuration(item); 13849 13850 // Video 13851 media = image = $('<video>', { 13852 preload: 'auto', 13853 controlsList: 'nodownload', 13854 poster: album.getPosterPath(item) 13855 }); 13856 13857 if (dur === null || dur >= 2000) { 13858 // Normal video 13859 media.attr('controls', 'controls'); 13860 } else { 13861 // Short video < 2s 13862 media.attr('loop', 'loop'); 13863 } 13864 13865 } else { 13866 13867 var src = album.getPosterPath(item); 13868 13869 // Audio 13870 loadCounter = 2; 13871 13872 image = $('<img>', { 13873 'class': 'poster' + (src.endsWith('poster.png')? ' default' : '') 13874 }).one('load', function(e) { 13875 imageReady(); 13876 }) 13877 .attr('src', src) 13878 .appendTo(main); 13879 13880 media = $('<audio>', { 13881 preload: 'auto', 13882 controls: 'true', 13883 controlsList: 'nofullscreen nodownload' 13884 }); 13885 } 13886 13887 media.one('loadedmetadata', function(e) { 13888 imageReady(); 13889 }) 13890 .attr('src', album.getSourcePath(item)) 13891 .appendTo(main); 13892 13893 media.on({ 13894 playing: mediaPlaying, 13895 pause: mediaPaused, 13896 ended: mediaEnded 13897 }); 13898 13899 if (settings.rightClickProtect) { 13900 media.on('contextmenu', function(e) { 13901 e.preventDefault(); 13902 return false; 13903 }); 13904 } 13905 13906 } else if (isExternal) { 13907 13908 var cont = item[J.EXTERNAL]['cont'], 13909 size = item[J.EXTERNAL]['size']; 13910 13911 if (size) { 13912 size = size.split('x'); 13913 main.data({ 13914 oWidth: parseInt(size[0]), 13915 oHeight: parseInt(size[1]) 13916 }) 13917 } 13918 13919 main.one('DOMReady', function(e) { 13920 imageReady(); 13921 }); 13922 13923 if (cont.match(/^https?\:\/\//i) || cont.match(/\.html?$/i) || cont.match(/^\.\.\//)) { 13924 main.addClass('iframe') 13925 .append($('<iframe>', { 13926 width: '100%', 13927 height: '100%', 13928 src: cont, 13929 frameborder: 0, 13930 allowfullscreen: 'allowfullscreen' 13931 })); 13932 } else { 13933 if (cont.match(/^<iframe/i)) { 13934 main.addClass('iframe'); 13935 } 13936 main.append(cont); 13937 } 13938 13939 // Considered ready after 200ms or the "ready" event, whatever comes first 13940 setTimeout(imageReady, 200); 13941 13942 } else { 13943 // Other 13944 var appendIcon = function(target, item) { 13945 target.append($('<div>', { 13946 'class': 'icon', 13947 html: '<a href="' + album.getSourcePath(item) + '" target="_blank"><img class="other" src="' + album.getImagePath(item) + '"></a><p class="icon-external"> ' + text.clickToOpen + '</p></div>' 13948 })); 13949 }; 13950 // Other 13951 if (isPdf && HASPDFVIEWER && !ISIOSDEVICE) { 13952 // using the PDF viewer plugin 13953 var o = $('<object>', { 13954 type: 'application/pdf' 13955 }) 13956 .attr('data', album.getSourcePath(item)); 13957 13958 appendIcon(o, item); 13959 main.append(o); 13960 setTimeout(imageReady, 400); 13961 13962 } else if (settings.useGoogleDocs && !LOCAL && 13963 '.docx.xlsx.txt.pdf'.indexOf('.' + (item.hasOwnProperty(J.ORIGINAL)? item[J.ORIGINAL][J.PATH] : item[J.NAME]).getExt()) >= 0) { 13964 // Fallback to Google Doc Viewer 13965 main.addClass('iframe'); 13966 var target = $('<iframe>', { 13967 width: '100%', 13968 height: '100%', 13969 frameborder: 0, 13970 allowfullscreen: 'allowfullscreen' 13971 }).appendTo(main); 13972 13973 setTimeout(function(target, item) { 13974 target.attr('src', 'https://docs.google.com/viewer?url=' + window.location.href.getDir() + album.getSourcePath(item) + '&embedded=true'); 13975 setTimeout(imageReady, 400); 13976 }, 100, target, item); 13977 13978 } else { 13979 // Generic case with a download button 13980 appendIcon(main, item); 13981 imageReady(); 13982 } 13983 } 13984 13985 prepareCaption(item); 13986 } 13987 13988 if (typeof settings.onLoadStart === FUNCTION) { 13989 settings.onLoadStart(thumb); 13990 } 13991 13992 }, 30); 13993 }, 13994 13995 // Hiding the lightbox overlay 13996 13997 quitLightbox = function(thumb) { 13998 13999 stopAuto(true); 14000 14001 self.hideAllTooltips(); 14002 14003 listening = false; 14004 $body.add($('html')).css('overflow', ''); 14005 $body.removeClass(settings.lightboxOnClass); 14006 $body.hideAllTooltips(); 14007 audioClip.remove(); 14008
14009 if (settings.muteBackgroundAudio) { 14010 unmuteBackgroundAudio(); 14011 } 14012 14013 if (main && main.length) { 14014 14015 if (lightbox.data('panomove')) { 14016 // Panorama playing 14017 main.trigger('autopanoStop'); 14018 } 14019 14020 if ((isAudio || isVideo) && media) { 14021 var sv = media[0].volume, 14022 tm = transitionSpeed / (sv * 50), 14023 fade = function() { 14024 if (media) { 14025 var v = Math.max(media[0].volume - 0.02, 0); 14026 if (v > 0.005) { 14027 media[0].volume = v; 14028 setTimeout(fade, tm); 14029 } else { 14030 media[0].pause(); 14031 } 14032 } 14033 }; 14034 14035 media.off('.' + ns); 14036 fade(); 14037 } 14038 14039 window.requestAnimationFrame(function() { 14040 14041 lightbox.css({ 14042 transition: 'opacity ' + settings.transitionSpeed + 'ms ease-out' 14043 }) 14044 .one('transitionend', function() { 14045 // Avoid flicker 14046 lightbox.css('visibility', 'hidden'); 14047 setTimeout(function() { 14048 lightbox.css('display', 'none'); 14049 }, 20); 14050 14051 if (isFullscreen() && settings.lightboxFullscreen && (SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles)) { 14052 exitFullscreen(); 14053 } 14054 14055 if (main.length) { 14056 main.remove(); 14057 main = $(); 14058 } 14059 14060 if (caption.length) { 14061 caption.remove(); 14062 caption = $(); 14063 } 14064 }) 14065 .css({ 14066 opacity: 0 14067 }); 14068 }); 14069 } 14070 14071 if (typeof settings.onClose === FUNCTION) { 14072 if (typeof thumb !== UNDEF) { 14073 settings.onClose(thumb); 14074 } else { 14075 settings.onClose(); 14076 } 14077 } 14078 14079 $body.hideAllTooltips(); 14080 }, 14081 14082 // Removing the lightbox completely 14083 14084 removeLightbox = function() { 14085 var remove = function() { 14086 if (isFullscreen() && ( 14087 settings.lightboxFullscreen && (SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles) 14088 || 14089 settings.slideshowFullscreen && (SMALLSCREEN === settings.slideshowFullscreenOnlyMobiles) 14090 )) { 14091 exitFullscreen(); 14092 } 14093 lightbox.remove(); 14094 }; 14095 // Stop slideshow 14096 stopAuto(true); 14097 listening = false; 14098 // removing event handlers 14099 thumbs.off('.' + ns); 14100 $document.add($window).add(lightbox).off('.' + ns); 14101 $body.add($('html')).css('overflow', ''); 14102 $body.removeClass(settings.lightboxOnClass); 14103 14104 if (!main.length) { 14105 // No image; remove immediately 14106 remove(); 14107 } else { 14108 // Fade out 14109 lightbox.css('transition', 'opacity ' + settings.transitionSpeed + 'ms ease-out') 14110 .one('transitionend', remove) 14111 .css('opacity', 0); 14112 } 14113 }, 14114 14115 // Set play/pause button 14116 14117 setPlayPause = function(on) { 14118 14119 if (lb_play_pause) { 14120 if (on) { 14121 lb_play_pause.removeClass('icon-play').addClass('icon-pause'); 14122 lb_play_pause.children('.lbl').text(text.pauseShort); 14123 } else { 14124 lb_play_pause.removeClass('icon-pause').addClass('icon-play').removeClass('show-progress'); 14125 lb_play_pause.children('.lbl').text(text.startSlideshowShort); 14126 } 14127 } 14128 }, 14129 14130 // Toggling auto slideshow 14131 14132 toggleAuto = function() { 14133 14134 if (slideshow) { 14135 stopAuto(); 14136 } else { 14137 startAuto(); 14138 } 14139 14140 }, 14141 14142 // Should the background audio be resumed? 14143 14144 isBackgroundAudioOn = function() { 14145 return backgroundAudioRoot.length && !backgroundAudioRoot.data('paused'); 14146 }, 14147 14148 // Mute background audio temporarily 14149 14150 muteBackgroundAudio = function() { 14151 if (isBackgroundAudioOn()) { 14152 backgroundAudioRoot.data('muted', true).trigger('fadeOutPlayer'); 14153 } 14154 }, 14155 14156 // Mute background audio temporarily 14157
14158 unmuteBackgroundAudio = function() { 14159 if (!isBackgroundAudioOn() && backgroundAudioRoot.data('muted')) { 14160 backgroundAudioRoot.data('muted', false).trigger('fadeInPlayer'); 14161 } 14162 }, 14163 14164 // Starting auto slideshow 14165 14166 startAuto = function(keepMusic) { 14167 14168 clearTimeout(slideshow); 14169 14170 setPlayPause(true); 14171 14172 $body.add(lightbox).addClass(settings.immerseClass); 14173 14174 if (settings.autohideControls) { 14175 hideControlsLater(); 14176 } 14177 14178 caption.addClass('slideshow'); 14179 14180 // Resetting zoom 14181 fitImages = settings.fitImages; 14182 14183 // Starting background music 14184 if (!(keepMusic === true) && settings.backgroundAudioSlideshowControl) { 14185 backgroundAudioRoot.trigger('fadeInPlayer'); 14186 } 14187 14188 // Full screen mode 14189 if (!isFullscreen() && settings.slideshowFullscreen && (SMALLSCREEN === settings.slideshowFullscreenOnlyMobiles)) { 14190 requestFullscreen(); 14191 } 14192 14193 // Wake lock 14194 requestWakeLock(); 14195 14196 if (audioClipTimeout) { 14197 // Wait until audioclip ends 14198 slideshow = null; 14199 slideshowSuspended = true; 14200 onAudioClipEnded(nextImage); 14201 } else { 14202 // Immediately start 14203 slideshowSuspended = false; 14204 slideshow = setTimeout(nextImage, settings.slideshowDelay / 4); 14205 } 14206 14207 if (typeof settings.onSlideshowStart === FUNCTION) { 14208 settings.onSlideshowStart(thumb); 14209 } 14210 }, 14211 14212 resumeAuto = function() { 14213 14214 setPlayPause(true); 14215 14216 if (settings.autohideControls) { 14217 hideControlsLater(); 14218 } 14219 14220 $body.add(lightbox).addClass(settings.immerseClass); 14221 slideshow = setTimeout(nextImage, settings.slideshowDelay / 4); 14222 slideshowSuspended = false; 14223 14224 if (settings.backgroundAudioSlideshowControl) { 14225 backgroundAudioRoot.trigger('fadeInPlayer'); 14226 } 14227 }, 14228 14229 stopAuto = function(keepMusic) { 14230 var sr = slideshow !== null; 14231 14232 if (typeof settings.onSlideshowPause === FUNCTION) { 14233 settings.onSlideshowPause(thumb); 14234 } 14235 14236 if (!(keepMusic === true) && settings.backgroundAudioSlideshowControl) { 14237 backgroundAudioRoot.trigger('fadeOutPlayer'); 14238 } 14239 14240 setPlayPause(false); 14241 14242 clearTimeout(slideshow); 14243 slideshow = null; 14244 slideshowSuspended = false; 14245 caption.show().removeClass('slideshow'); 14246 $body.add(lightbox).removeClass(settings.immerseClass); 14247 14248 showControls(); 14249 14250 if (isFullscreen() && settings.slideshowFullscreen && (SMALLSCREEN === settings.slideshowFullscreenOnlyMobiles)) { 14251 exitFullscreen(); 14252 } 14253 14254 releaseWakeLock(); 14255 14256 return sr; 14257 }, 14258 14259 suspendAuto = function() { 14260 14261 if (settings.backgroundAudioSlideshowControl) { 14262 muteBackgroundAudio(); 14263 } 14264 14265 clearTimeout(slideshow); 14266 slideshow = null; 14267 slideshowSuspended = true; 14268 }, 14269 14270 updateAudioClipProgress = function(pb) { 14271 14272 if (audioClip.length && pb.length) { 14273 pb.css('width', (100 * audioClip[0].currentTime / audioClip[0].duration) + '%'); 14274 } 14275 }, 14276 14277 startAudioClip = function() { 14278 14279 if (audioClip.length) { 14280 14281 audioClip[0].play().then(function() { 14282 var btn = audioClip.siblings('.audioclip-btn'), 14283 pb = btn.children('.progress'); 14284 14285 btn.removeClass('icon-volume-up').addClass('icon-pause'); 14286 audioClip.parent().removeClass('paused').addClass('playing'); 14287 14288 if (slideshow) { 14289 suspendAuto(); 14290 } 14291 14292 if (!pb.length) { 14293 pb = $('<div>', { 14294 'class': 'progress' 14295 }).appendTo(btn); 14296 } 14297 14298 updateAudioClipProgress(pb); 14299 14300 audioClipTimeout = setInterval(updateAudioClipProgress, 100, pb); 14301 14302 audioClip.on('ended', function() { 14303 14304 btn.removeClass('icon-pause').addClass('icon-volume-up'); 14305 audioClip.parent().removeClass('playing').addClass('paused'); 14306 14307 if (slideshowSuspended) { 14308 resumeAuto(); 14309 } 14310 14311 pb.remove(); 14312 14313 clearInterval(audioClipTimeout); 14314 });
14315 14316 }, function(err) { 14317 14318 btn.removeClass('icon-pause').addClass('icon-volume-up'); 14319 console.log(err); 14320 }); 14321 } 14322 }, 14323 14324 pauseAudioClip = function() { 14325 14326 if (audioClip.length) { 14327 var btn = audioClip.siblings('.audioclip-btn'); 14328 14329 audioClip[0].pause(); 14330 clearInterval(audioClipTimeout); 14331 audioClipTimeout = null; 14332 14333 btn.removeClass('icon-pause').addClass('icon-volume-up'); 14334 audioClip.parent().removeClass('playing').addClass('paused'); 14335 14336 if (slideshowSuspended) { 14337 resumeAuto(); 14338 } 14339 } 14340 }, 14341 14342 onAudioClipEnded = function(fn, params) { 14343 14344 if (audioClip.length && typeof fn === FUNCTION) { 14345 if (audioClip[0].paused) { 14346 if (typeof params !== UNDEF) { 14347 fn.apply(null, params); 14348 } else { 14349 fn.call(null); 14350 } 14351 } else { 14352 audioClip.on('ended', fn, params); 14353 } 14354 } 14355 }, 14356 14357 mediaPlaying = function() { 14358 14359 if (settings.muteBackgroundAudio) { 14360 muteBackgroundAudio(); 14361 } 14362 startAudioClip(); 14363 main.addClass(settings.playingClass); 14364 }, 14365 14366 mediaPaused = function() { 14367 14368 if (settings.muteBackgroundAudio) { 14369 // Unmute after 500ms so it won't start between videos in slideshows 14370 setTimeout(unmuteBackgroundAudio, 500); 14371 } 14372 pauseAudioClip(); 14373 main.removeClass(settings.playingClass); 14374 }, 14375 14376 mediaEnded = function() { 14377 14378 pauseAudioClip(); 14379 main.removeClass(settings.playingClass); 14380 }, 14381 14382 fadeMedia = function(el) { 14383 var media = (el || lightbox).find('audio,video').eq(0); 14384 14385 if (media.length) { 14386 var sv = media[0].volume, 14387 tm = settings.transitionSpeed / (sv * 50), 14388 fade = function() { 14389 if (media) { 14390 var v = Math.max(media[0].volume - 0.02, 0); 14391 if (v > 0.005) { 14392 media[0].volume = v; 14393 setTimeout(fade, tm); 14394 } else { 14395 media[0].pause(); 14396 } 14397 } 14398 }; 14399 14400 media.off('volumechange.' + ns); 14401 fade(); 14402 } 14403 }, 14404 14405 stopMedia = function(el) { 14406 var m = (el || lightbox).find('audio,video').eq(0); 14407 14408 if (m.length) { 14409 m[0].pause(); 14410 } 14411 }, 14412 14413 showControls = function() { 14414 clearTimeout(controlsTimeout); 14415 lightbox.removeClass(settings.controlsHideClass); 14416 }, 14417 14418 hideControls = function() { 14419 clearTimeout(controlsTimeout); 14420 controls.hideAllTooltips(); 14421 lightbox.addClass(settings.controlsHideClass); 14422 }, 14423 14424 hideControlsLater = function() { 14425 clearTimeout(controlsTimeout); 14426 controlsTimeout = setTimeout(function() { 14427 //$body.hideAllTooltips(); 14428 hideControls(); 14429 }, /*slideshow? Math.min(settings.slideshowDelay / 2, settings.hideControlsDelay) :*/ settings.hideControlsDelay); 14430 }, 14431 14432 toggleControls = function() { 14433 clearTimeout(controlsTimeout); 14434 lightbox.toggleClass(settings.controlsHideClass, !controlsHidden()); 14435 }, 14436 14437 controlsHidden = function() { 14438 return lightbox.hasClass(settings.controlsHideClass); 14439 }, 14440 14441 toggleFullscreen = function() { 14442 if (isFullscreen()) { 14443 exitFullscreen(); 14444 } else { 14445 requestFullscreen(); 14446 } 14447 }, 14448 14449 showThumbs = function() { 14450 controls.hideAllTooltips(); 14451 lightbox.addClass(settings.thumbsVisibleClass); 14452 settings.thumbsVisible = thumbsVisibleDefault = true; 14453 savePrefs(settings, settings.prefsKept, 'lb_pref'); 14454 if (main.data('fitted')) { 14455 // re-adjusting position 14456 fitImage(true); 14457 } 14458 }, 14459 14460 hideThumbs = function() { 14461 controls.hideAllTooltips(); 14462 lightbox.removeClass(settings.thumbsVisibleClass); 14463 settings.thumbsVisible = thumbsVisibleDefault = false; 14464 savePrefs(settings, settings.prefsKept, 'lb_pref'); 14465 if (main.data('fitted')) { 14466 // re-adjusting position 14467 fitImage(true); 14468 } 14469 }, 14470 14471 showCaption = function() { 14472 lightbox.addClass(settings.captionVisibleClass); 14473 settings.captionVisible = captionVisibleDefault = true; 14474 savePrefs(settings, settings.prefsKept, 'lb_pref'); 14475 if (main.data('fitted')) { 14476 // re-adjusting position 14477 fitImage(true); 14478 } 14479 }, 14480 14481 hideCaption = function() { 14482 controls.hideAllTooltips(); 14483 lightbox.removeClass(settings.captionVisibleClass); 14484 settings.captionVisible = captionVisibleDefault = false; 14485 savePrefs(settings, settings.prefsKept, 'lb_pref'); 14486 if (main.data('fitted')) { 14487 // re-adjusting position 14488 fitImage(true); 14489 } 14490 }, 14491 14492 toggleCaption = function() { 14493 if (settings.captionVisible) { 14494 hideCaption(); 14495 } else { 14496 showCaption(); 14497 } 14498 }, 14499 14500 toggleAll = function() { 14501 if (settings.captionVisible || settings.thumbsVisible) { 14502 thumbsVisibleDefault = settings.thumbsVisible; 14503 captionVisibleDefault = settings.captionVisible; 14504 // Hide everything 14505 controls.hideAllTooltips();
14506 clearTimeout(controlsTimeout); 14507 lightbox.addClass(settings.controlsHideClass); 14508 lightbox.removeClass(settings.thumbsVisibleClass + ' ' + settings.captionVisibleClass); 14509 settings.thumbsVisible = settings.captionVisible = false; 14510 } else { 14511 showControls(); 14512 // Show items only to be visible by default 14513 if ((thumbsVisibleDefault || !captionVisibleDefault) && !settings.thumbsVisible) { 14514 lightbox.addClass(settings.thumbsVisibleClass); 14515 settings.thumbsVisible = true; 14516 } 14517 if ((captionVisibleDefault || !thumbsVisibleDefault) && !settings.captionVisible) { 14518 lightbox.addClass(settings.captionVisibleClass); 14519 settings.captionVisible = true; 14520 } 14521 } 14522 14523 savePrefs(settings, settings.prefsKept, 'lb_pref'); 14524 if (settings.fitBetween !== 'screen') { 14525 if (main.data('fitted')) { 14526 // re-adjusting position 14527 fitImage(true); 14528 } 14529 } 14530 }, 14531 14532 previousImage = function() { 14533 loadImage(curr - 1); 14534 }, 14535 14536 nextImage = function() { 14537 loadImage(curr + 1); 14538 }, 14539 14540 moveThumbs = function(x) { 14541 14542 lb_thumbs 14543 .css({ 14544 transition: 'transform ' + settings.thumbSpeed + 'ms ease-out', 14545 transform: 'translateX(' + x + 'px)' 14546 }); 14547 14548 lazyloadThumbs(); 14549 }, 14550 14551 setActiveThumb = function() { 14552 14553 if (!settings.useThumbstrip || thumbs.length < 2) { 14554 return; 14555 } 14556 14557 var cthumb = lb_thumbs.children().eq((curr < 0)? 0 : curr), 14558 tl = cthumb.position().left, 14559 tw = cthumb.width(), 14560 cl = lb_thumbs.getTranslate()[0], 14561 cw = lb_thumb_cont.width(), 14562 tsw = lb_thumbs.width(); 14563 14564 lb_thumbs.children().removeClass(settings.activeClass); 14565 cthumb.addClass(settings.activeClass); 14566 14567 if (tl < -cl) { 14568 moveThumbs(Math.min(0, settings.thumbOverhead - tl)); 14569 } else if ((tl + tw) > (cw - cl)) { 14570 moveThumbs(Math.max(cw - tl - tw - settings.thumbOverhead, cw - tsw)); 14571 } else if ((cw - cl) > tsw) { 14572 moveThumbs(cw - tsw); 14573 } 14574 14575 }, 14576 14577 // Adding one thumb 14578 14579 loadThumb = function(thumb) { 14580 14581 if (thumb.length) { 14582 var i = $('<img>', { 14583 'class': 'hide-image' 14584 }) 14585 // Onload action 14586 .one('load', function() { 14587 $(this).removeClass('hide-image').addClass('show-image'); 14588 $(this).parent().removeClass(settings.lazyloadClass); 14589 }) 14590 .attr('src', thumb.attr('href')); 14591 14592 if (settings.rightClickProtect) { 14593 i.on('contextmenu', function(e) { 14594 e.preventDefault(); 14595 return false; 14596 }); 14597 } 14598 14599 thumb.append(i).removeAttr('href'); 14600 } 14601 14602 }, 14603 14604 // Loading visible thumbs 14605 14606 lazyloadThumbs = function(callback) { 14607 14608 if (!lightbox.is(':visible') || !settings.useThumbstrip || thumbs.length < 2) { 14609 return; 14610 } 14611 14612 var cw = lb_thumb_cont.width(), 14613 cl = lb_thumbs.getTranslate()[0]; 14614 14615 lb_thumbs.children('.' + settings.lazyloadClass).each(function() { 14616 var t = $(this), 14617 tl = t.position().left, 14618 tw = t.width(); 14619 14620 if (((tl + cl) < (cw * 7 / 4)) && ((tl + tw + cl) > (0 - cw * 3 / 4))) { 14621 // In view 14622 t.removeClass(settings.lazyloadClass); 14623 loadThumb(t); 14624 } else if ((tl + cl) >= cw) { 14625 // Right to clip window 14626 return false; 14627 } 14628 }); 14629 14630 if (typeof callback === FUNCTION) { 14631 callback.call(this); 14632 } 14633 }, 14634 14635 // Fit thumbnail strip if shorter than the original space 14636 14637 fitThumbstrip = function() { 14638 if (!lightbox.is(':visible')) { 14639 return; 14640 } 14641 14642 var lt = lb_thumbs.children(':last-child'); 14643 14644 if ((lt.position().left + lt.outerWidth()) > lb_thumb_cont.width()) { 14645 lb_thumbstrip.addClass(settings.scrollClass); 14646 setActiveThumb(); 14647 } else { 14648 lb_thumbstrip.removeClass(settings.scrollClass); 14649 moveThumbs(0); 14650 } 14651 }, 14652 14653 // Scrolling the thumb strip 14654 14655 scrollThumbs = function(direction) { 14656 var cw = lb_thumb_cont.width(), 14657 cl = lb_thumbs.getTranslate()[0]; 14658 14659 if (cw > lb_thumbs.width()) { 14660 if (cl) { 14661 moveThumbs(0); 14662 } 14663 } else { 14664 cl = (direction < 0)? 14665 Math.min(0, cl + cw) 14666 : 14667 Math.max(cw - lb_thumbs.width(), cl - cw); 14668 moveThumbs(cl); 14669 } 14670 }, 14671 14672 // Settings up the thumb strip 14673 14674 initThumbstrip = function() { 14675 14676 if (settings.thumbsVisible) { 14677 lightbox.addClass(settings.thumbsVisibleClass); 14678 } 14679 14680 // Thumbsstrip 14681 lb_thumbstrip = $('<div>', { 14682 'class': 'thumb-strip' 14683 })
14684 .appendTo(lightbox); 14685 14686 lb_thumbstrip.append($('<button>', { 14687 'class': 'left icon-caret-left' 14688 }).on('click.' + ns, function(e) { 14689 scrollThumbs(-1); 14690 return false; 14691 })); 14692 14693 lb_thumbstrip.append($('<button>', { 14694 'class': 'right icon-caret-right' 14695 }).on('click.' + ns, function(e) { 14696 scrollThumbs(1); 14697 return false; 14698 })); 14699 14700 lb_thumb_cont = $('<div>', { 14701 'class': 'thumb-cont' 14702 }).appendTo(lb_thumbstrip); 14703 14704 lb_thumb_cont.on('selectstart.' + ns, function(e) { 14705 return false; 14706 }); 14707 14708 lb_thumbs = $('<div>', { 14709 'class': 'thumbs' 14710 }).appendTo(lb_thumb_cont); 14711 14712 var item, 14713 w, 14714 h = lb_thumbs.height() || 60, 14715 a, 14716 tw = 0; 14717 14718 thumbs.each(function(i) { 14719 14720 item = $(this).data(J.OBJ); 14721 14722 a = $('<a>', { 14723 'class': settings.lazyloadClass, 14724 title: item[J.THUMBCAPTION] || '', 14725 href: album.getThumbPath(item) 14726 }).appendTo(lb_thumbs); 14727 14728 a.on('click', function(e) { 14729 var a = $(e.target).closest('a'); 14730 14731 loadImage(lb_thumbs.children().index(a)); 14732 return false; 14733 });
14734 14735 w = Math.floor(Math.max((h - 4) * 3 / 4, Math.min(item[J.THUMB][J.WIDTH], (h - 4) * item[J.THUMB][J.WIDTH] / item[J.THUMB][J.HEIGHT]))); 14736 14737 a.width(w); 14738 14739 tw += w; 14740 }); 14741 }, 14742 14743 // Setting up the controls 14744 14745 initControls = function() { 14746 // Control strip 14747 controls = $('<div>', { 14748 'class': settings.controlsClass + (settings.controlsUseText? ' use-text' : '') 14749 }).appendTo(lightbox); 14750 14751 // Left button 14752 if (thumbs.length > 1) { 14753 lb_btn_left = $('<button>', { 14754 type: 'button', 14755 'class': 'previous', 14756 title: text.previousPicture, 14757 text: settings.controlsUseText? text.previousPictureShort : '' 14758 }) 14759 .on('click.' + ns, previousImage) 14760 .appendTo(controls); 14761 } 14762 14763 // Up button 14764 lb_up = $('<button>', { 14765 type: 'button', 14766 'class': 'up icon-arrow-up', 14767 title: text.upOneLevel, 14768 text: settings.controlsUseText? text.upOneLevelShort : '' 14769 }) 14770 .on('click.' + ns, function() { quitLightbox(); }) 14771 .appendTo(controls); 14772 14773 // Zoom button 14774 if (settings.useZoom) { 14775 14776 // Zoom button(s) 14777 if (settings.zoomSlider) { 14778 // Slider 14779 var zoom_panel= $('<div>', { 14780 'class': 'zoom-control' 14781 }), 14782 zoom_in_btn, 14783 zoom_out_btn; 14784 14785 zoom_out_btn = $('<a>', { 14786 'class': 'zoom-out button' 14787 }) 14788 .on('click', function() { 14789 decreaseZoom(); 14790 return false; 14791 }) 14792 .appendTo(zoom_panel); 14793 14794 lb_zoom_slider = $('<input>', { 14795 name: 'zoom-level', 14796 type: 'range' 14797 }) 14798 .on('change', function() { 14799 setZoom(parseInt(lb_zoom_slider.val())); 14800 return true; 14801 }) 14802 .appendTo(zoom_panel); 14803 14804 zoom_in_btn = $('<a>', { 14805 'class': 'zoom-in button' 14806 }) 14807 .on('click', function() { 14808 increaseZoom(); 14809 return false; 14810 }) 14811 .appendTo(zoom_panel); 14812 14813 // Zoom toggle button 14814 lb_zoom = $('<button>', { 14815 type: 'button', 14816 'class': 'zoom-level icon-zoom-level', 14817 text: settings.controlsUseText? text.zoom : '' 14818 }) 14819 .on('click.' + ns, function(){ 14820 if (listening) { 14821 setTimeout(toggleZoom, 50); 14822 if ($('html').data('whatinput') === 'touch') { 14823 // Touch toggle 14824 return true; 14825 } 14826 } 14827 return false; 14828 }) 14829 .addTooltip($('<div>', { 14830 'class': 'zoom-panel' 14831 }) 14832 .append(zoom_panel), { 14833 touchToggle: true, 14834 pos: [1,0,1,2] 14835 }) 14836 .appendTo(controls); 14837 14838 } else { 14839 14840 // Zoom toggle button 14841 lb_zoomin = $('<button>', { 14842 type: 'button', 14843 'class': 'zoom-in icon-expand', 14844 title: text.oneToOneSize, 14845 text: settings.controlsUseText? text.oneToOneSizeShort : '' 14846 }) 14847 .on('click.' + ns, function() { 14848 toggleZoom(); 14849 return false; 14850 }) 14851 .appendTo(controls); 14852 14853 lb_zoomout = $('<button>', { 14854 type: 'button', 14855 'class': 'zoom-out icon-contract', 14856 title: text.fitToScreen, 14857 text: settings.controlsUseText? text.fitToScreenShort : '' 14858 }) 14859 .on('click.' + ns, function() { 14860 toggleZoom(); 14861 return false; 14862 }) 14863 .appendTo(controls); 14864 14865 lb_zoom_slider = $(); 14866 } 14867 } 14868 14869 // Hide/show top thumbs 14870 if (settings.useThumbstrip && thumbs.length > 1) { 14871 lb_showthumbs = $('<button>', { 14872 type: 'button', 14873 'class': 'show-thumbs icon-show-top-panel', 14874 title: text.showThumbs, 14875 text: settings.controlsUseText? text.showThumbsShort : '' 14876 }) 14877 .on('click.' + ns, showThumbs) 14878 .appendTo(controls); 14879 14880 lb_hidethumbs = $('<button>', { 14881 type: 'button', 14882 'class': 'hide-thumbs icon-hide-top-panel', 14883 title: text.hideThumbs, 14884 text: settings.controlsUseText? text.hideThumbsShort : '' 14885 }) 14886 .on('click.' + ns, hideThumbs) 14887 .appendTo(controls); 14888 } 14889 14890 // Hide/show captions 14891 lb_showcaption = $('<button>', { 14892 type: 'button', 14893 'class': 'show-caption icon-show-bottom-panel', 14894 title: text.showInfo, 14895 text: settings.controlsUseText? text.showInfoShort : '' 14896 }) 14897 .on('click.' + ns, showCaption) 14898 .appendTo(controls); 14899 14900 lb_hidecaption = $('<button>', { 14901 type: 'button', 14902 'class': 'hide-caption icon-hide-bottom-panel', 14903 title: text.hideInfo, 14904 text: settings.controlsUseText? text.hideInfoShort : '' 14905 }) 14906 .on('click.' + ns, hideCaption) 14907 .appendTo(controls); 14908 14909 if (settings.useSlideshow && thumbs.length > 1) { 14910 14911 // Play/Pause button 14912 lb_play_pause = $('<button>', { 14913 type: 'button', 14914 'class': 'play-pause icon-play', 14915 'title': text.slideshow 14916 }) 14917 .appendTo(controls); 14918 14919 if (settings.controlsUseText) { 14920 lb_play_pause.append($('<span>', { 14921 'class': 'lbl', 14922 html: text.startSlideshowShort 14923 })); 14924 } 14925 14926 lb_progress = $('<span>', { 14927 'class': 'progress' 14928 }) 14929 .appendTo(lb_play_pause); 14930 14931 lb_play_pause.on('click.' + ns, function(){ 14932 if (listening) { 14933 toggleAuto(); 14934 if ($('html').data('whatinput') === 'touch') { 14935 // Touch toggle 14936 return true; 14937 } 14938 } 14939 return false; 14940 });
14941 14942 if (settings.timingControl) { 14943 // Slideshow timing control 14944 var timing_panel= $('<div>', { 14945 id: 'timing-control', 14946 'class': 'timing-control' 14947 }), 14948 decr_time_btn = $('<a>', { 14949 'class': 'decr-time' 14950 }).appendTo(timing_panel); 14951 14952 lb_timing_value = $('<span>').appendTo(timing_panel); 14953 14954 var incr_time_btn = $('<a>', { 14955 'class': 'incr-time' 14956 }).appendTo(timing_panel); 14957 14958 lb_play_pause.addTooltip($('<div>', { 14959 'class': 'timing-panel' 14960 }).append($('<div>', { 14961 'class': 'label', 14962 html: text.timing 14963 })).append(timing_panel), { 14964 touchToggle: true, 14965 delay: 600, 14966 pos: [1,0,1,2] 14967 }); 14968 14969 incr_time_btn.on('click', function() { 14970 increaseTiming(); 14971 return false; 14972 }); 14973 14974 decr_time_btn.on('click', function() { 14975 decreaseTiming(); 14976 return false; 14977 }); 14978 14979 setTimeout(function() { 14980 setTiming(settings.slideshowDelay); 14981 }, 500); 14982 14983 } else { 14984 // No timing control 14985 lb_play_pause.attr('title', text.startStopSlideshow); 14986 } 14987 } 14988 14989 // Full screen toggle button 14990 if (settings.showFullscreen && hasFullscreen()) { 14991 lb_fullscr = $('<button>', { 14992 type: 'button', 14993 'class': 'gofullscreen icon-fullscreen', 14994 title: text.fullscreen, 14995 text: settings.controlsUseText? text.fullscreenShort : '' 14996 }) 14997 .on('click.' + ns, requestFullscreen) 14998 .appendTo(controls); 14999 15000 lb_exitfullscr = $('<button>', { 15001 type: 'button', 15002 'class': 'exitfullscreen icon-fullscreen-off', 15003 title: text.exitFullscreen, 15004 text: settings.controlsUseText? text.exitFullscreenShort : '' 15005 }) 15006 .on('click.' + ns, exitFullscreen) 15007 .appendTo(controls); 15008 15009 if (isFullscreen()) { 15010 lightbox.addClass('fullscreen'); 15011 } 15012 } 15013 15014 // Right button 15015 if (thumbs.length > 1) { 15016 lb_btn_right = $('<button>', { 15017 type: 'button', 15018 'class': 'next', 15019 title: text.nextPicture, 15020 text: settings.controlsUseText? text.nextPictureShort : '' 15021 }) 15022 .on('click.' + ns, nextImage) 15023 .appendTo(controls); 15024 } 15025 15026 // Adding tooltips 15027 controls.children('button').not('.zoom-level').not('.pause').addTooltip({ 15028 delay: 1000, 15029 pos: [1,0,1,2] 15030 }); 15031 15032 }, 15033 15034 // Setting up the structure 15035 15036 initLightbox = function() { 15037 15038 lightbox = $('<div>', { 15039 id: ns, 15040 'class': 'lightbox' 15041 }) 15042 .hide() 15043 .appendTo(self); 15044 15045 // Darken background 15046 lb_overlay = $('<div>', { 15047 'class': 'lightbox-overlay' 15048 }) 15049 .appendTo(lightbox); 15050 15051 // Activity indicator 15052 lb_activity = $('<div>', { 15053 'class': 'lightbox-loading' 15054 }) 15055 .append('<div>') 15056 .append('<div>') 15057 .append('<div>') 15058 .appendTo(lightbox); 15059 15060 // Apply visibility classes 15061 lightbox.toggleClass(settings.thumbsVisibleClass, settings.thumbsVisible && thumbs.length > 1); 15062 lightbox.toggleClass(settings.captionVisibleClass, settings.captionVisible); 15063 //lightbox.toggleClass('zoomed', zoomed); 15064 15065 // Controls 15066 initControls(); 15067 15068 // Thumb strip 15069 if (settings.useThumbstrip && thumbs.length > 1) { 15070 initThumbstrip(); 15071 } 15072 15073 // Slideshow delay and transition speed 15074 if (!settings.hasOwnProperty('slideshowDelay')) { 15075 slideshowDelay = settings.slideshowDelay = 4000; 15076 } 15077 if (!settings.hasOwnProperty('transitionSpeed')) { 15078 transitionSpeed = settings.transitionSpeed = 400; 15079 } 15080 15081 // Quit beside handler 15082 if (!settings.quitOnDocClick) { 15083 lb_overlay.on('click.' + ns, function(e) { 15084 15085 if (listening) { 15086 if (this === e.target) { 15087 var p = getCoords(e); 15088 15089 if (p.x >= lightbox.width() / 2) { 15090 nextImage(); 15091 } else { 15092 previousImage(); 15093 } 15094 } 15095 return false; 15096 } 15097 });
15098 } 15099 }; 15100 15101 15102 // Initializing Lightbox 15103 15104 if (ns) { 15105 // should never happen, but still living lightbox attached to thumb elements 15106 cleanup(ns); 15107 } 15108 15109 // Creating new namespace 15110 self.data('llb_ns', ns = getUniqueId('llb')); 15111 15112 // Finding thumbs 15113 thumbs = self.find(settings.delegate); 15114 15115 if (!settings.useThumbstrip) { 15116 settings.prefsKept.splice(0, 1); 15117 } 15118 15119 loadPrefs(settings, settings.prefsKept, 'lb_pref'); 15120 15121 // Initializing controls 15122 initLightbox(); 15123 15124 // Setting up events 15125 15126 // Resize 15127 $window.on('resize.' + ns /* + ' orientationchange.' + ns*/, function() { 15128 clearTimeout(resizeTimeout); 15129 // Executed only if 50ms has passed since the last resize event 15130 resizeTimeout = setTimeout(function() { 15131 clearTimeout(resizeTimeout); 15132 if (main && main.length && !(isVr && isFullscreen())) { 15133 fitImage(); 15134 } 15135 lazyloadThumbs(fitThumbstrip); 15136 }, 50); 15137 }); 15138 15139 // Quit on document touch 15140 if (settings.quitOnDocClick) { 15141 15142 lb_overlay.on('click.' + ns, function(e) { 15143 15144 if (listening) { 15145 if (settings.autohideControls && controlsHidden()) { 15146 // Toggle controls back 15147 showControls(); 15148 return false; 15149 } 15150 15151 if (main.length && !$(e.target).is(main)) { 15152 // Quit 15153 quitLightbox(); 15154 return false; 15155 } 15156 } 15157 15158 return true; 15159 }); 15160 } 15161 15162 // On mouse-driven devices 15163 if (settings.autohideControls) { 15164 15165 // Auto hide controls 15166 $document.on('mousemove.' + ns, function() { 15167 showControls(); 15168 hideControlsLater(); 15169 }); 15170 } 15171 15172 // Keyboard 15173 if (settings.enableKeyboard) { 15174 15175 $document.on('keyup.' + ns, function(e) { 15176 if (!listening || !main.length || lightbox.children('.modal:visible').length || document.activeElement && (document.activeElement.nodeName === 'INPUT' || document.activeElement.nodeName === 'TEXTAREA')) { 15177 return true; 15178 } 15179 15180 e.preventDefault(); 15181 15182 switch (e.keyCode) { 15183 15184 case 27: 15185 quitLightbox(); 15186 break; 15187 15188 case 33: 15189 case 37: 15190 previousImage(); 15191 break; 15192 15193 case 34: 15194 case 39: 15195 nextImage(); 15196 break; 15197 15198 case 97: 15199 case 35: 15200 loadImage(thumbs.length - 1); 15201 break; 15202 15203 case 103: 15204 case 36: 15205 loadImage(0); 15206 break; 15207 15208 case 106: 15209 case 179: 15210 toggleAuto(); 15211 break; 15212 15213 case 107: 15214 if (settings.useZoom) { 15215 if (settings.zoomSlider) { 15216 increaseZoom(); 15217 } else { 15218 toggleZoom(); 15219 } 15220 } else { 15221 toggleCaption(); 15222 } 15223 break; 15224 15225 case 109: 15226 if (settings.useZoom) { 15227 if (settings.zoomSlider) { 15228 decreaseZoom(); 15229 } else { 15230 toggleZoom(); 15231 } 15232 } else { 15233 toggleAll(); 15234 } 15235 break; 15236 15237 case 32: 15238 if (isVideo) { 15239 if (media[0].paused) { 15240 playVideo(media[0]); 15241 } else { 15242 media[0].pause(); 15243 } 15244 } else if (isAudio) { 15245 if (media[0].paused) { 15246 media[0].play(); 15247 } else { 15248 media[0].pause(); 15249 } 15250 } else { 15251 toggleAuto(); 15252 } 15253 break; 15254 15255 default: 15256 return true; 15257 15258 } 15259 15260 return false; 15261 }); 15262 } 15263 15264 // Mouse wheel 15265 if (settings.mouseWheelAction) { 15266 var wheelTimeout = null; 15267 15268 lightbox.on('mousewheel.' + ns + ' DOMMouseScroll.' + ns, function(e) { 15269 if (listening) { 15270 clearTimeout(wheelTimeout); 15271 15272 if ( !main.length || 15273 $(e.target).closest('.' + settings.lightboxCaptionClass).length || 15274 main.hasClass('pdf') || 15275 main.hasClass('external') || 15276 lightbox.children('.modal:visible').length 15277 ) { 15278 return true; 15279 } 15280 15281 if (e.ctrlKey == true || settings.mouseWheelAction === 'zoom') { 15282 if (settings.useZoom) { 15283 if (settings.zoomSlider) { 15284 wheelTimeout = setTimeout((e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0)? 15285 function(e) { 15286 increaseZoom(); 15287 } 15288 : 15289 function(e) { 15290 decreaseZoom(); 15291 }, 100); 15292 } else { 15293 wheelTimeout = setTimeout((e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0)? 15294 function(e) { 15295 if (!fitImages) { 15296 fitImages = true; 15297 fitImage(true); 15298 } 15299 } 15300 : 15301 function(e) { 15302 if (fitImages) { 15303 fitImages = false; 15304 fitImage(true); 15305 } 15306 }, 100); 15307 } 15308 } 15309 } else if (settings.mouseWheelAction === 'navigation') { 15310 var d = new Date(); 15311 if (typeof lightbox.data('lastWheel') === UNDEF || (d - lightbox.data('lastWheel')) > 250) { 15312 // Only if 250ms apart from the last wheel event 15313 wheelTimeout = setTimeout(function(dir) { 15314 lightbox.data('lastWheel', new Date()); 15315 if (dir) { 15316 previousImage(); 15317 } else { 15318 nextImage(); 15319 } 15320 }, 100, (e.originalEvent.wheelDelta > 0 || e.originalEvent.detail < 0)); 15321 } 15322 } 15323 } 15324 return false; 15325 });
15326 } 15327 15328 // Events that can be triggered from outside 15329 15330 // External trigger to switch image 15331 self.on('lightboxLoad.' + ns, function(e, n, auto) { 15332 15333 if (typeof n === NUMBER) { 15334 n = Math.minMax(0, n, thumbs.length); 15335 } else if (typeof n === OBJECT) { 15336 n = thumbs.index($(n)); 15337 } else { 15338 n = 0; 15339 } 15340 15341 if (typeof settings.onStart === FUNCTION) { 15342 settings.onStart(thumbs.eq(n)); 15343 } 15344 15345 if (slideshow || slideshowSuspended) { 15346 stopAuto(true); 15347 } 15348 15349 loadImage(n, function() { 15350 // Executed when the image is loaded 15351 if (auto) { 15352 startAuto(); 15353 //hideControlsLater(); 15354 } 15355 }); 15356 }) 15357 // External trigger to quit = does not remove, just hides the lightbox 15358 .on('lightboxQuit.' + ns, function() { 15359 quitLightbox(); 15360 return false; 15361 }) 15362 // Removing the lightbox completely 15363 .on('lightboxRemove.' + ns, function() { 15364 removeLightbox(); 15365 return false; 15366 }) 15367 // Stopping auto hide controls 15368 .on('stopAutoHide.' + ns, function() { 15369 clearTimeout(controlsTimeout); 15370 }) 15371 // Returning the current lightbox container 15372 .on('lightboxContainer.' + ns, function() { 15373 return lightbox; 15374 }) 15375 // External trigger to go to the previous image 15376 .on('lightboxNext.' + ns, function() { 15377 stopAuto(true); 15378 nextImage(); 15379 return false; 15380 }) 15381 // External trigger to go to next image 15382 .on('lightboxPrevious.' + ns, function() { 15383 stopAuto(true); 15384 previousImage(); 15385 return false; 15386 }); 15387 15388 // Ready event 15389 15390 if (typeof settings.onReady === FUNCTION) { 15391 settings.onReady(thumbs.eq(curr)); 15392 } 15393 15394 // Auto start 15395 15396 if (settings.autoStart && settings.baseSet) { 15397 startAuto(); 15398 } 15399 15400 return this; 15401 }; 15402 15403 $.fn.lightbox.defaults = { 15404 delegate: '.card.lbable', 15405 lightboxMainClass: 'lightbox-main', 15406 currClass: 'curr', 15407 lightboxCaptionClass: 'lightbox-caption', 15408 lightboxOnClass: 'lightbox-on', 15409 controlsClass: 'controls', 15410 buttonClass: 'button', 15411 lazyloadClass: 'lazyload', 15412 activeClass: 'active', 15413 scrollClass: 'scroll', 15414 regionsClass: 'regions', 15415 captionVisibleClass: 'caption-visible', 15416 thumbsVisibleClass: 'thumbs-visible', 15417 hasbuttonsClass: 'hasbuttons', 15418 immerseClass: 'immerse', 15419 controlsVisibleClass: 'controls-visible', 15420 controlsHideClass: 'controls-hide', 15421 checkboxClass: 'checkbox', 15422 checkmarkClass: 'icon-checkmark', 15423 selectedClass: 'checked', 15424 playingClass: 'playing', 15425 transitionProps: '300ms ease-out', 15426 slideshowDelay: 4000, 15427 timingControl: false, 15428 hideControlsDelay: 2500, 15429 prefsKept: [ 15430 'thumbsVisible', 15431 'captionVisible', 15432 'audioClipVolume' 15433 ], 15434 controlsUseText: false, 15435 thumbsVisible: true, 15436 useThumbstrip: true, 15437 captionVisible: true, 15438 fitImages: true, 15439 fitBetween: 'screen', 15440 maxZoom: 1.0, 15441 horzPanoThreshold: 1.8, 15442 vertPanoThreshold: 0.5, 15443 displayOriginals: true, 15444 useZoom: true, 15445 zoomSlider: false, 15446 useOriginals: false, 15447 hiDpiThumbs: false, 15448 hiDpiImages: false, 15449 showFullscreen: false, 15450 indexName: 'index.html',
15451 baseSet: true, 15452 autohideControls: false, 15453 autoStart: false, 15454 clickAction: 'donothing', 15455 mouseWheelAction: 'navigation', 15456 useSlideshow: true, 15457 autoplayAudioClips: true, 15458 backgroundAudioSlideshowControl:false, 15459 muteBackgroundAudio: true, 15460 use360Player: true, 15461 autoRotate360: true, 15462 rotateSpeed360: -2, 15463 useGoogleDocs: true, 15464 lightboxFullscreen: false, 15465 lightboxFullscreenOnlyMobiles: false, 15466 slideshowFullscreen: false, 15467 slideshowFullscreenOnlyMobiles: true, 15468 afterLast: 'donothing', 15469 avoidNavigationOnZoomed: true, 15470 doubleClickZoom: true, 15471 mapHeight: 0.8, 15472 mapApiKey: '', 15473 mapType: 'hybrid', 15474 mapZoom: 15, 15475 showShop: false, 15476 showFeedback: false, 15477 showLowestPrice: false, 15478 videoAuto: false, 15479 videoLoop: false, 15480 autoPano: false, 15481 autoPanoStart: 0, 15482 autoPanoSpeed: 80, 15483 volume: 0.5, 15484 audioClipVolume: 0.75, 15485 rightClickProtect: false, 15486 useRating: false, 15487 jalbumRating: false, 15488 visitorRating: false, 15489 buttonLabels: true, 15490 showNumbers: false, 15491 infoPanelAdapt: false, 15492 showShare: false, 15493 showDownload: false, 15494 allowDownloadScaled: false, 15495 allowDownloadOthers: false, 15496 showMap: false, 15497 showRegions: true, 15498 regionsVisible: false, 15499 printImage: false, 15500 metaAsPopup: true, 15501 transitionType: 'crossFadeAndSlide', 15502 transitionSpeed: 400, 15503 panoramaAutoDetect: 1.3333, 15504 panoramaTreshold: [ 2.5, 2.5 ], 15505 defaultPosterSize: [ 628, 360 ], 15506 thumbSpeed: 400, 15507 thumbOverhead: 40, 15508 preloadNext: true, 15509 enableKeyboard: true, 15510 enableMouseWheel: true, 15511 quitOnEnd: true, 15512 quitOnDocClick: true, 15513 onStart: false, 15514 onClose: false, 15515 onLoadStart: false, 15516 onLoadEnd: false, 15517 onAfterLast: false, 15518 onBeforeFirst: false, 15519 onReady: false, 15520 onSlideshowStart: false, 15521 onSlideshowPause: false, 15522 onVolumeChange: false, 15523 feedbackBtnLabel: 'Write feedback', 15524 feedbackBtnTooltip: 'View feedback window' 15525 }; 15526 15527 $.fn.lightbox.text = { 15528 rating: "Rating", 15529 print: "Print", 15530 printLabel: "Print out this photo on your printer", 15531 download: "Download", 15532 addCart: "Add to Cart", 15533 shopLabel: "Add this item to the shopping cart", 15534 viewCart: "View Cart", 15535 viewCartLabel: "View shopping cart", 15536 fotomotoBtn: "Buy / Share", 15537 fotomotoTooltip: "Buy prints or digital files, share, send free eCards", 15538 metaBtn: "Photo data", 15539 metaLabel: "Display photographic (Exif/Iptc) data", 15540 regionsBtn: "People", 15541 regionsLabel: "Show tagged people", 15542 mapBtn: "Map", 15543 mapLabel: "Show the photo location on the map", 15544 mostphotosBtn: "Purchase", 15545 mostphotosLabel: "Download this image from <b>mostphotos.com</b>!", 15546 share: "Share", 15547 shareLabel: "Share this photo over social sites",
15548 shareOn: "Share on", 15549 play: "Play", 15550 audioClipLabel: "Play / pause the attached audio clip", 15551 startOver: "Start over", 15552 upOneLevel: "Up one level", 15553 backToHome: "Back to home", 15554 backToIndex: "Back to index page", 15555 nextFolder: "Next folder", 15556 nextIndex: "Next index page", 15557 atLastPageQuestion: "Where to go next?", 15558 atLastPage: "At last page", 15559 clickToOpen: "Click to open this document with the associated viewer", 15560 pauseShort: "Pause", 15561 startSlideshowShort: "Play", 15562 previousPicture: "Previous picture", 15563 previousPictureShort: "Prev", 15564 upOneLevelShort: "Exit", 15565 zoom: "Zoom", 15566 oneToOneSize: "1:1 size", 15567 oneToOneSizeShort: "1:1", 15568 fitToScreen: "Fit to screen", 15569 fitToScreenShort: "Fit", 15570 showThumbs: "Show thumbnails", 15571 showThumbsShort: "Thumbnails", 15572 hideThumbs: "Hide thumbnails", 15573 hideThumbsShort: "Hide thumbs", 15574 showInfo: "Show caption / info", 15575 showInfoShort: "Info", 15576 hideInfo: "Hide caption / info", 15577 hideInfoShort: "Hide info", 15578 slideshow: "Slideshow", 15579 timing: "Timing", 15580 startStopSlideshow: "Start / stop slideshow", 15581 fullscreen: "Fullscreen", 15582 fullscreenShort: "Fullscr", 15583 exitFullscreen: "Exit fullscreen", 15584 exitFullscreenShort: "Normal", 15585 nextPicture: "Next picture", 15586 nextPictureShort: "Next" 15587 }; 15588 15589 15590})(jQuery, jQuery(window), jQuery(document)); 15591/* 15592 * main.js - the skin's javascript functions 15593 */ 15594
15595;(function($, $window, $document) { 15596 'use strict'; 15597 15598 var $body = $(document.body); 15599 15600 /******************************************************** 15601 * 15602 * Implementing select functions on images 15603 * 15604 ********************************************************/ 15605 15606 $.fn.selectable = function(options) { 15607 15608 options = $.extend({ 15609 cardClass: 'card', 15610 thumbClass: 'thumb', 15611 checkboxClass: 'checkbox', 15612 selectedClass: 'checked', 15613 checkmarkClass: 'icon-checkmark', 15614 hasSelectedClass: 'has-selected', 15615 allSelectedClass: 'all-selected', 15616 selectionChange: null 15617 }, options); 15618 15619 var overlay = $(this).eq(0), 15620 cards, 15621 15622 update = function(n) { 15623 15624 if (typeof options.selectionChange === FUNCTION) { 15625 options.selectionChange.call(overlay[0], n); 15626 } 15627 15628 $body 15629 .toggleClass(options.hasSelectedClass, n > 0) 15630 .toggleClass(options.allSelectedClass, n === cards.length); 15631 }, 15632 15633 selectedCnt = function() { 15634 return cards.filter('.' + options.selectedClass).length; 15635 }, 15636 15637 getCards = function() { 15638 return options.selector? overlay.find(options.selector) : overlay.find('.' + options.cardClass); 15639 }, 15640 15641 selectCard = function(e) { 15642 var card = (e instanceof $)? e : $(this); 15643 15644 card.addClass(options.selectedClass) 15645 .find('.' + options.checkboxClass) 15646 .addClass(options.checkmarkClass); 15647 }, 15648 15649 unselectCard = function(e) { 15650 var card = (e instanceof $)? e : $(this); 15651 15652 card.removeClass(options.selectedClass) 15653 .find('.' + options.checkboxClass) 15654 .removeClass(options.checkmarkClass); 15655 }, 15656 15657 toggleCard = function(e) { 15658 var card = (e instanceof $)? e : $(this); 15659 15660 if (card.hasClass(options.selectedClass)) { 15661 unselectCard(card); 15662 } else { 15663 selectCard(card); 15664 } 15665 }, 15666 15667 clicked = function(e) { 15668 var card = $(e.target).closest('.' + options.cardClass), 15669 n = selectedCnt(); 15670 15671 if (card.hasClass(options.selectedClass)) { 15672 unselectCard(card); 15673 n -= 1; 15674 } else { 15675 selectCard(card); 15676 n += 1; 15677 } 15678 15679 update(n); 15680 15681 return false; 15682 }, 15683 15684 selectAll = function() { 15685 15686 if (overlay.is(':visible')) { 15687 var n = selectedCnt(); 15688 15689 if (n < cards.length) { 15690 n = cards.length; 15691 cards.not('.' + options.selectedClass).each(function() { 15692 selectCard($(this)); 15693 }); 15694 } 15695 15696 update(n); 15697 } 15698 }, 15699 15700 selectNone = function() { 15701 15702 if (overlay.is(':visible')) { 15703 var n = selectedCnt(); 15704 15705 if (n > 0) { 15706 n = 0; 15707 cards.filter('.' + options.selectedClass).each(function() { 15708 unselectCard($(this)); 15709 }); 15710 } 15711 15712 update(n); 15713 } 15714 }, 15715 15716 selectInverse = function() { 15717 15718 if (overlay.is(':visible')) { 15719 var n = cards.length - selectedCnt(); 15720 15721 if (n === 0) { 15722 // All selected 15723 cards.each(function() { 15724 unselectCard($(this)); 15725 }); 15726 } else { 15727 // Some selected 15728 cards.each(function() { 15729 toggleCard($(this)); 15730 }); 15731 } 15732 15733 update(n); 15734 } 15735 }, 15736 15737 selectRefresh = function() { 15738 var th, 15739 cb; 15740 15741 cards = getCards(); 15742 15743 cards.each(function() { 15744 15745 if (!($(this).find('span.' + options.checkboxClass).length)) { 15746 15747 th = $(this).children('.' + options.thumbClass).eq(0); 15748 15749 if (th.length) { 15750 cb = $('<span>', { 15751 'class': options.checkboxClass 15752 }).appendTo(th); 15753 } else { 15754 cb = $('<span>', { 15755 'class': options.checkboxClass 15756 }).prependTo($(this)); 15757 } 15758 15759 cb.on({ 15760 click: clicked 15761 });
15762 15763 $(this).on({ 15764 selectCard: selectCard, 15765 unselectCard: unselectCard, 15766 toggleCard: toggleCard 15767 }); 15768 } 15769 15770 }); 15771 15772 if (typeof options.selectionChange === FUNCTION) { 15773 options.selectionChange.call(overlay[0], selectedCnt()); 15774 } 15775 15776 }; 15777 15778 selectRefresh(); 15779 15780 overlay.on({ 15781 selectAll: selectAll, 15782 selectNone: selectNone, 15783 selectInverse: selectInverse, 15784 selectRefresh: selectRefresh 15785 }); 15786 15787 return this; 15788 }; 15789 15790 15791 /******************************************************** 15792 * 15793 * The main skin code 15794 * 15795 ********************************************************/ 15796 15797 $.fn.skin = function(settings) { 15798 15799 settings = $.extend({}, $.fn.skin.defaults, settings); 15800 15801 var main = $(this), // Main SECTION 15802 content = main.find('#main-cont'), // Main container 15803 text = getTranslations({ // Translated texts 15804 'return': "return", 15805 rendering: "Rendering", 15806 addCart: "Add to Cart", 15807 fotomotoBtn: "Buy / Share", 15808 fotomotoTooltip: "Buy prints or digital files, share, send free eCards", 15809 audioClipLabel: "Play / pause the attached audio clip", 15810 selectedItems: "Selected items", 15811 label: "Label", 15812 search: "Search",
15813 searchNew: "Search new", 15814 loading: "Loading", 15815 foundNTimes: "{0} image(s)", 15816 foundTooMany: "{0} images; displaying only the first {1}", 15817 notFound: "not found", 15818 searchPlaceholder: "Search...", 15819 more: "more", 15820 less: "less", 15821 imagesTaken: "Images taken", 15822 imagesModified: "Images modified", 15823 imagesAdded: "Images added", 15824 betweenDays: "between {0} and {1}", 15825 onDay: "on {0}", 15826 afterDay: "after {0}", 15827 beforeDay: "before {0}", 15828 sinceMyLastVisit: "since my last visit", 15829 results: "Results", 15830 sortedBy: "Sorted by", 15831 ascending: "ascending", 15832 descending: "descending", 15833 noRating: "No rating", 15834 multipleSelectHint: "Use SHIFT to select range, CTRL for multiple entries", 15835 select: "Select", 15836 reset: "Reset", 15837 sortBy: "Sort by", 15838 image: "image", 15839 images: "images", 15840 audio: "audio", 15841 audios: "audios", 15842 video: "video", 15843 videos: "videos", 15844 other: "other", 15845 others: "others", 15846 fatalError: "Fatal error!" 15847 }), 15848 15849 shopRoot = $(), // Root element for cart 15850 shopBox = $(), // Whole Shopping section 15851 feedbackRoot = $(), // Root element for the feedback tool 15852 feedbackBox = $(), // Whole Feedback section 15853 mapRoot, // Root element for map 15854 album, // The JSON database 15855 ns = 'tiger_skin', // Namespace 15856 lastHash = '', // Storing last state 15857 hashChangedByJs = false, // Tracking for internally or externally triggered hash changes 15858 pageExt = settings.indexName.split('.')[1] || 'html', // Index page extension 15859 initByHash = window.location.hash !== '', // Initial URL contains hash 15860 nows = new Date() / 1000, // Now in seconds 15861 currency = 'EUR', 15862 scrollTop = $window.scrollTop(), // initial scroll top 15863 15864 // Determine card type 15865 15866 cardType = function(card) { 15867 var s = card.closest('section'); 15868 if (!s.length) { 15869 return 'image'; 15870 } else if (s.hasClass(settings.folderClass)) { 15871 return 'folder'; 15872 } else if (s.hasClass(settings.thumbnailsClass)) { 15873 return 'thumb'; 15874 } 15875 15876 return ''; 15877 }, 15878 15879 /******************************************************** 15880 * 15881 * Managing overlays 15882 * e.g. after search or filter 15883 * 15884 ********************************************************/ 15885 /* 15886 15887 <section id="main" class="content"> => main 15888 15889 // Base only 15890 <article id="main-cont"> <!-- content --> 15891 <section class="thumbnails base-overlay" data-overlay="base"> <!-- base overlay --> 15892 <div class="cont horizontal caption-over hover-zoom caption-on-hover"> <!-- container --> 15893 </div> 15894 </section> 15895 <section id="tag-cloud" class="row cont-box"><div class="cont box"><h4 class="icon-label"> Labels</h4><div class="tag-cloud"></div><form class="tag-search" data-depth="tree" data-fields="creator,keywords,folder:title,folder:name,webLocation:title,webPage:title" wtx-context="94581790-1AAD-4D31-91BA-67ADA9D1EC6F"><div class="search-input"><input type="text" placeholder="Search..." aria-label="Search" wtx-context="46A4B41B-684F-4DFB-8E45-6D8FAC4C5691"></div><a class="search-btn icon-search"></a></form></div></section> 15896 <section class="prev-next-links"><div class="cont"><div class="next-link has-bg" style="background-image:url(../People/thumbs/Newborn.jpg)"><a rel="next" href="../People/index.html"><span class="caption">People <span class="icon-caret-right"></span></span></a></div></div></section> 15897 </article> 15898 15899 // Base + overlay 15900 <article id="main-cont" class="has-overlay"> <!-- content --> 15901 <section class="thumbnails base-overlay" data-overlay="base"> <!-- base overlay --> 15902 <div class="cont horizontal caption-over hover-zoom caption-on-hover"> <!-- container --> 15903 </div> 15904 </section> 15905 <section class="thumbnails overlay" data-overlay="oly_NNNN"> <!-- overlay --> 15906 <header class="cont overlay-title icon-filter"> <!-- header --> 15907 <h4>Title</h4> 15908 <a class="close">Return</a> 15909 </header> 15910 <div class="cont horizontal caption-over hover-zoom caption-on-hover"> <!-- container --> 15911 </div> 15912 </section> 15913 <section id="tag-cloud" class="row cont-box"><div class="cont box"><h4 class="icon-label"> Labels</h4><div class="tag-cloud"></div><form class="tag-search" data-depth="tree" data-fields="creator,keywords,folder:title,folder:name,webLocation:title,webPage:title" wtx-context="94581790-1AAD-4D31-91BA-67ADA9D1EC6F"><div class="search-input"><input type="text" placeholder="Search..." aria-label="Search" wtx-context="46A4B41B-684F-4DFB-8E45-6D8FAC4C5691"></div><a class="search-btn icon-search"></a></form></div></section> 15914 <section class="prev-next-links"><div class="cont"><div class="next-link has-bg" style="background-image:url(../People/thumbs/Newborn.jpg)"><a rel="next" href="../People/index.html"><span class="caption">People <span class="icon-caret-right"></span></span></a></div></div></section> 15915 </article> 15916 15917 </section> 15918 */ 15919 15920 OLYTAG = 'section', 15921 15922 // Getting active overlay; creating one if missing 15923 15924 getOverlay = function(create) { 15925 var oly = content.find('.' + settings.thumbnailsClass).last(
15925); 15926 15927 if (!oly.length && typeof create !== UNDEF && create) { 15928 oly = createOverlay(); 15929 } 15930 15931 return oly; 15932 }, 15933 15934 // Getting base overlay 15935 15936 getBaseOverlay = function() { 15937 15938 return content.find('.' + settings.baseOverlayClass); 15939 }, 15940 15941 // Content has overlay? 15942 15943 hasOverlay = function() { 15944 15945 return content.find('.' + settings.overlayClass + ':visible').length > 0; 15946 }, 15947 15948 // Is this the base overlay? 15949 15950 isBaseOverlay = function(oly) { 15951 15952 return (typeof oly === UNDEF? getOverlay() : oly).hasClass(settings.baseOverlayClass); 15953 }, 15954 15955 // Getting NameSpace, creating new if not exists 15956 15957 getOverlayNS = function(oly) { 15958 oly = typeof oly === UNDEF? getOverlay() : oly; 15959 15960 if (oly.length) { 15961 var ns = oly.data('overlay'); 15962 15963 if (ns) { 15964 return ns; 15965 } 15966 15967 ns = oly.hasClass(settings.baseOverlayClass)? 'base' : getUniqueId('oly'); 15968 15969 oly.attr('data-overlay', ns); 15970 15971 return ns; 15972 } 15973 15974 return ''; 15975 }, 15976 15977 // Setting NameSpace 15978 15979 setOverlayNS = function(ns, oly) { 15980 oly = typeof oly === UNDEF? getOverlay() : oly; 15981 ns = typeof ns === UNDEF? (oly.hasClass(settings.baseOverlayClass)? 'base' : getUniqueId('oly')) : ns; 15982 15983 oly.attr('data-overlay', ns); 15984 15985 return ns; 15986 }, 15987 15988 // Scroll to overlay 15989 15990 focusOverlay = function(oly) { 15991 oly = typeof oly === UNDEF? getOverlay() : oly; 15992 15993 if (oly.length && (oly.offset().top < $window.scrollTop() || oly.offset().top > ($window.scrollTop() + $window.height() - 100))) { 15994 $window.scrollTop(oly.offset().top); 15995 } 15996 }, 15997 15998 // Creating a new overlay 15999 16000 createOverlay = function() { 16001 var ns = getUniqueId('oly'), 16002 oly = $('<' + OLYTAG + '>', { 16003 'class': settings.thumbnailsClass + ' ' + settings.overlayClass 16004 }).attr({ 16005 id: ns, 16006 'data-overlay': ns 16007 }), 16008 boly = getBaseOverlay(); // Base overlay 16009 16010 if (boly.length) { 16011 // Has base: create after it 16012 boly.after(oly); 16013 } else { 16014 // No base: create after the folders if exists 16015 boly = content.find('.' + settings.foldersClass); 16016 if (boly.length) { 16017 boly.after(oly); 16018 } else { 16019 // No folders: create at first position 16020 content.prepend(oly); 16021 } 16022 } 16023 16024 createImageContainer(oly); 16025 16026 content.addClass(settings.hasOverlayClass); 16027 16028 return oly; 16029 }, 16030 16031 // Creating header 16032 16033 createOverlayHeader = function(options, oly) { 16034 var head = $('<header>', { 16035 'class': [ settings.contClass, 16036 settings.overlayTitleClass, 16037 (options && options.hasOwnProperty('icon'))? options.icon : '' 16038 ].filter(Boolean).join(' ') 16039 }).prependTo(typeof oly === UNDEF? getOverlay() : oly); 16040 16041 if (options && options.hasOwnProperty('title')) { 16042 head.append($('<h' + settings.overlayHeadLevel + '>', { 16043 html: options.title 16044 })); 16045 } 16046 16047 head.append($('<a>', { 16048 'class': 'close', 16049 href: '', 16050 text: text.return 16051 }) 16052 .on('click', function() { 16053 removeOverlay(); 16054 return false; 16055 })); 16056 16057 return head; 16058 }, 16059 16060 // Getting overlay header 16061 16062 getOverlayHeader = function() { 16063 16064 return getOverlay().children('header').eq(0); 16065 }, 16066 16067 // Getting overlay title 16068 16069 getOverlayTitle = function() { 16070 16071 return getOverlayHeader().find('h' + settings.overlayHeadLevel).eq(0); 16072 }, 16073 16074 // Append text to overlay title 16075 16076 appendToOverlayTitle = function(cont) { 16077 var t = getOverlayTitle(); 16078 16079 return t.length? cont.appendTo(t) : $(); 16080 }, 16081 16082 // Adding progressbar 16083 progressAnimateTo, 16084 16085 appendProgressbar = function(title) { 16086 var p = getOverlayHeader().find('.' + settings.progressClass); 16087 16088 if (p.length) { 16089 p.empty(); 16090 } else { 16091 p = $('<div>', { 16092 'class': settings.progressClass 16093 }).insertAfter(getOverlayTitle()); 16094 } 16095 16096 if (typeof title !== UNDEF && title) { 16097 p.append('<div class="title">' + title + '</div>'); 16098 } 16099 16100 p.append('<div class="' + settings.progressbarClass + '"><span></span></div>'); 16101 16102 // Auto animate if no setProgressbar() within 500ms 16103 progressAnimateTo = setTimeout(function() { 16104 p.addClass('animate'); 16105 }, 500); 16106 16107 return p; 16108 }, 16109 16110 // Set progress to 0..1 16111 16112 setProgressbar = function(c) { 16113 clearTimeout(progressAnimateTo); 16114 var p = getOverlayHeader().find('.' + settings.progressClass); 16115 p.removeClass('animate'); 16116 p.find('.' + settings.progressbarClass + ' span').width(c * 100 + '%'); 16117 }, 16118 16119 // Remove progressbar 16120 16121 removeProgressbar = function(target) { 16122 getOverlayHeader().find('.' + settings.progressClass).remove(); 16123 }, 16124 16125 // Associate elements with the current overlay 16126 16127 attachToOverlay = function(el) { 16128 if (el.length) { 16129 var ns = getOverlayNS(); 16130 16131 el.each(function() { 16132 $(this).attr('data-rel', ns); 16133 });
16134 } 16135 }, 16136 16137 // Remove overlay-attached elements 16138 16139 removeAttachedElementsFromOverlay = function(ns) { 16140 if (!ns) { 16141 ns = getOverlayNS(); 16142 } 16143 16144 content.find('[data-rel=' + ns + ']').remove(); 16145 }, 16146 16147 // Creating image container within target overlay 16148 16149 createImageContainer = function(oly) { 16150 if (!oly) { 16151 oly = getOverlay(); 16152 } 16153 16154 return $('<div>', { 16155 'class': [ 16156 settings.contClass, 16157 settings.thumbLayout, 16158 'caption-' + settings.captionPlacement, 16159 (settings.captionPlacement !== 'below' && settings.captionShowOnHover)? 'caption-on-hover' : '' 16160 ].filter(Boolean).join(' ') 16161 }).appendTo(oly); 16162 }, 16163 16164 // Getting base thumbnail container 16165 16166 getBaseImageContainer = function() { 16167 return content 16168 .find('.' + settings.thumbnailsClass + 16169 ':not(.' + settings.overlayClass + ')') 16170 .first() 16171 .children('div.' + settings.contClass); 16172 }, 16173 16174 // Returns the current thumbnail container if exists 16175 16176 getImageContainer = function(create) { 16177 var ic = content 16178 .find('.' + settings.thumbnailsClass) 16179 .last() 16180 .children('div.' + settings.contClass); 16181 16182 if (!ic.length && create) { 16183 ic = createImageContainer(getOverlay(true)); 16184 } 16185 16186 return ic; 16187 }, 16188 16189 // Restoring base overlay's attached elements 16190 16191 restoreBaseOverlay = function() { 16192 var bo = getBaseOverlay(); 16193 16194 if (bo.length) { 16195 // Attached elements 16196 $('[data-rel=' + bo.data('overlay') + ']').not('.hidden').show(); 16197 // Refreshing thumbnails 16198 bo.trigger('refresh'); 16199 // Refreshing folders too 16200 lazyloadFolders(); 16201 // Refreshing the map 16202 getBaseMap().trigger('refresh'); 16203 } 16204 }, 16205 16206 // Getting map on the base layer 16207 16208 getBaseMap = function() { 16209 return getBaseOverlay().find('.' + settings.mapRootClass); 16210 }, 16211 16212 // Getting all base cards 16213 16214 getBaseCards = function(selector) { 16215 return getBaseImageContainer() 16216 .find('.' + settings.cardClass + (selector || '')); 16217 }, 16218 16219 // Returns the current cards (base or overlay) 16220 16221 getCards = function(selector) { 16222 return getImageContainer() 16223 .find('.' + settings.cardClass + (selector || '')); 16224 }, 16225 16226 // Returns base thumbnails 16227 16228 getBaseImages = function(selector) { 16229 return getBaseImageContainer() 16230 .find('.' + settings.cardClass + 16231 ' .' + settings.thumbClass + 16232 (selector || '')); 16233 }, 16234 16235 // Returns the current thumbnails (base or overlay) 16236 16237 getImages = function(selector) { 16238 return getImageContainer() 16239 .find('.' + settings.cardClass + 16240 ' .' + settings.thumbClass + 16241 (selector || '')); 16242 }, 16243 16244 // Returns all videos (base or overlay) 16245 16246 getVideos = function(selector) { 16247 return getOverlay() 16248 .find('.' + settings.cardClass + ' video' + (selector || '')); 16249 }, 16250 16251 // Returns the current cards (base or overlay) 16252 16253 getSelectedCards = function() { 16254 return getImageContainer() 16255 .find('.' + settings.cardClass + 16256 '.' + settings.selectedClass); 16257 }, 16258 16259 // Getting current card 16260 16261 getActiveCard = function() { 16262 return getImageContainer() 16263 .find('.' + settings.cardClass + 16264 '.' + settings.activeClass); 16265 }, 16266 16267 // Returns the current cards (base or overlay) 16268 16269 getSelectableCards = function() { 16270 return getImageContainer() 16271 .find('.' + settings.cardClass + 16272 '.' + settings.selectableClass); 16273 }, 16274 16275 // Removing overlay 16276 16277 removeOverlay = function() { 16278 var oly = getOverlay(); 16279 16280 if (oly.length && !isBaseOverlay()) { 16281 var ns = oly.data('overlay'); 16282 16283 $window.off('.' + ns); 16284 16285 // Removing the lightbox 16286 oly.trigger('removeLightbox'); 16287 16288 // Removing related elements 16289 removeAttachedElementsFromOverlay(ns); 16290 main.removeClass(settings.noMapClass + ' ' + settings.hasMapClass); 16291 16292 oly.trigger('overlayRemoved'); 16293 16294 oly.remove(); 16295 16296 content.removeClass(settings.hasOverlayClass); 16297 16298 restoreBaseOverlay(); 16299 16300 if (settings.hasOwnProperty('search')) { 16301 emptySearchBox(); 16302 } 16303 16304 setHash(); 16305 16306 updateShares(); 16307 16308 if (!getSelectableCards().length) { 16309 $('.hide-on-empty').hide(); 16310 } 16311 16312 content.hideAllTooltips(); 16313 content.find('[data-sticky-rel]').trigger('stickyRefresh'); 16314 } 16315 }, 16316 16317 // Adding new overlay 16318 16319 addOverlay = function(options) { 16320 16321 // Removing existing overlay first 16322 if (hasOverlay()) { 16323 removeOverlay(); 16324 } 16325 16326 content.hideAllTooltips(); 16327 16328 // Creating overlay (section.thumbnails) 16329 var oly = createOverlay(); 16330 16331 // Creating header 16332 createOverlayHeader(options, oly); 16333 16334 // Triggering ready event 16335 content.trigger('overlayReady', oly); 16336 16337 content.find('[data-sticky-rel]').trigger('stickyRefresh'); 16338 16339 return oly; 16340 }, 16341 16342 // Getting file name from thumbnail 16343 16344 getImageFileName = function(thumb) { 16345 var l = thumb.data('href') || thumb.attr('href'); 16346 16347 return l? l.substring(l.lastIndexOf('/') + 1).replace('#img=', '')
16347: ''; 16348 }, 16349 16350 // Checking if file is a custom page and matches base name: "name" 16351 16352 checkPageName = function(fn, name) { 16353 16354 if (fn.slice(-pageExt.length) === pageExt) { 16355 return fn.substring(0, fn.lastIndexOf('.')) === name.substring(0, name.lastIndexOf('.')); 16356 } 16357 16358 return false; 16359 }, 16360 16361 // Getting image by file name, name is mandatory 16362 16363 getCardByName = function(name, base) { 16364 var cards, 16365 t = $(); 16366 16367 cards = (typeof base === UNDEF || !base)? 16368 getCards() 16369 : 16370 getBaseCards(); 16371 16372 name = decodeURIComponent(name); 16373 16374 cards.find('.' + settings.thumbClass).each(function() { 16375 var fn = $(this).data('name') || getImageFileName($(this)); 16376 16377 if (fn === name || checkPageName(fn, name)) { 16378 t = $(this).closest('.' + settings.cardClass); 16379 return false; 16380 } 16381 }); 16382 16383 return t; 16384 }, 16385 16386 // Getting folders container 16387 16388 getFolderContainer = function() { 16389 return content.find('.' + settings.foldersClass + ' .' + settings.contClass); 16390 }, 16391 16392 // Getting all folder cards 16393 16394 getFolders = function(sel) { 16395 var fc = getFolderContainer(); 16396 16397 return fc.length? fc.find('.' + settings.cardClass + (sel || '')) : $(); 16398 }, 16399 16400 // Getting map 16401 16402 getMapRoot = function(create) { 16403 var mapbox = content.find(settings.mapBoxHook); 16404 16405 if (settings.pageType !== 'page') { 16406 // Finding only the map related to the current overlay 16407 var ns = getOverlayNS(); 16408 mapbox = mapbox.filter(function() { return $(this).data('rel') === ns; }); 16409 } 16410 16411 if (mapbox.length && mapbox.is(':visible')) { 16412 16413 return mapbox.find('.' + settings.mapRootClass); 16414 16415 } else if (create) { 16416 16417 var newMap = $('<section class="map show-on-overlay">' + 16418 '<div class="cont box">' + 16419 '<div class="' + settings.mapRootClass + '" data-map-root></div>' + 16420 '</div>' + 16421 '</section>'); 16422 16423 if (mapbox.length) { 16424 mapbox.after(newMap); 16425 } else { 16426 getOverlay().before(newMap); 16427 } 16428 16429 attachToOverlay(newMap); 16430 16431 return newMap.find('.' + settings.mapRootClass); 16432 } 16433 16434 return $(); 16435 }, 16436 16437 // Getting attached lightbox 16438 16439 getLightbox = function() { 16440 return getImageContainer().find('.' + settings.lightboxClass).last(); 16441 }, 16442 16443 // Is lightbox on? 16444 16445 lightboxOn = function() { 16446 return getLightbox().is(':visible'); 16447 }, 16448 16449 // Loading an image in lightbox 16450 16451 lightboxLoad = function(card, auto) { 16452 pausePlayer(getImages(' .playing')); 16453 getImageContainer().trigger('lightboxLoad', (typeof auto === UNDEF)? card : [ card, auto ]); 16454 }, 16455 16456 // Leaving lightbox 16457 16458 lightboxQuit = function() { 16459 getImageContainer().trigger('lightboxQuit'); 16460 }, 16461 16462 // Getting selected items from current overlay 16463 16464 getSelectedItems = function() { 16465 var r = []; 16466 16467 getSelectedCards().each(function() { 16468 r.push($(this).data(J.OBJ)); 16469 }); 16470 16471 return r; 16472 }, 16473 16474 // Getting all items from current overlay 16475 16476 getAllItems = function() { 16477 var r = []; 16478 16479 getCards().each(function() { 16480 var i = $(this).data(J.OBJ); 16481 if (i) r.push(i); 16482 }); 16483 16484 return r; 16485 }, 16486 16487 // Getting all items from base overlay 16488 16489 getBaseItems = function() { 16490 var r = []; 16491 16492 getBaseCards().each(function() { 16493 var i = $(this).data(J.OBJ); 16494 if (i) r.push(i); 16495 }); 16496 16497 return r; 16498 }, 16499 16500 // Getting active element's data 16501 16502 getCurrentItem = function() { 16503 var card = getActiveCard(); 16504 16505 if (card.length) { 16506 return card.data(J.OBJ); 16507 } 16508 16509 return null; 16510 }, 16511 16512 /******************************************************** 16513 * 16514 * Managing buttons 16515 * 16516 ********************************************************/ 16517 16518 // Toggle select buttons: selected count, max count 16519 16520 toggleSelectButtons = function(sc, mc) { 16521 var p = shopRoot.add(feedbackRoot); 16522 16523 if (typeof mc === UNDEF || !mc) { 16524 p.find('.' + settings.selectBoxClass).hide(); 16525 } else { 16526 p.find('.' + settings.selectBoxClass).show(); 16527 p.find('.' + settings.selectNoneClass + 16528 ',.' + settings.addSelectedClass + 16529 ',.' + settings.keepSelectedClass).toggleClass(settings.disabledClass, typeof sc === UNDEF || sc === 0); 16530 16531 p.find('.' + settings.selectAllClass).toggleClass(settings.disabledClass, typeof mc !== UNDEF && sc === mc); 16532 } 16533 }, 16534 16535 // Toggle boxes according to item count 16536 16537 toggleBoxes = function(mc) { 16538 feedbackRoot.add(shopRoot).toggleClass(settings.disabledClass, !mc); 16539 }, 16540 16541 // Toggling 'has-images' class 16542 16543 toggleHasImages = function(n) { 16544 main.toggleClass(settings.hasImagesClass, n).toggleClass(settings.noImagesClass, !n); 16545 }, 16546 16547 // Emptying the Search box 16548 16549 emptySearchBox = function() { 16550 $(settings.search.hook).find('input').val(''); 16551 }, 16552 16553 /******************************************************** 16554 * 16555 * Audio clip handling 16556 * 16557 ********************************************************/ 16558
16559 // Creating the AUDIO element 16560 16561 createPlayer = function(btn) { 16562 var player = $('<audio>', { 16563 'class': 'audioclip-player', 16564 src: btn.attr('href'), 16565 id: btn.attr('rel'), 16566 controlsList: 'nofullscreen nodownload noremote noplaybackrate' 16567 }).hide().insertAfter(btn); 16568 16569 player[0].volume = settings.audioClipVolume; 16570 16571 return player; 16572 }, 16573 16574 // Audio 16575 16576 togglePlayer = function(e) { 16577 var btn = $(this), 16578 pl; 16579 16580 e.preventDefault(); 16581 16582 // Initialized? 16583 if (btn[0].hasAttribute('rel')) { 16584 pl = $('#' + btn.attr('rel')); 16585 } 16586 16587 // Has AUDIO element? 16588 if (!pl || !pl.length) { 16589 // not yet created 16590 btn.attr('rel', getUniqueId('ply')); 16591 pl = createPlayer(btn); 16592 } 16593 16594 if (pl[0].paused) { 16595 16596 pausePlayers(); 16597 16598 // Paused: trying to start 16599 pl[0].play().then(function() { 16600 btn.removeClass('icon-volume-up paused').addClass('icon-pause playing'); 16601 pl.on('ended', function() { 16602 btn.removeClass('icon-pause playing').addClass('icon-volume-up paused'); 16603 }); 16604 }, function(err) { 16605 btn.removeClass('icon-pause playing').addClass('icon-volume-up paused'); 16606 console.log(err); 16607 });
16608 } else { 16609 pl[0].pause(); 16610 btn.removeClass('icon-pause playing').addClass('icon-volume-up paused'); 16611 } 16612 16613 return false; 16614 }, 16615 16616 pausePlayers = function() { 16617 pausePlayer(getImages(' .playing')); 16618 }, 16619 16620 pausePlayer = function(el) { 16621 el.each(function() { 16622 var pl = $('#' + $(this).attr('rel')); 16623 16624 if (pl.length) { 16625 pl[0].pause(); 16626 $(this).removeClass('icon-pause playing').addClass('icon-volume-up paused'); 16627 } 16628 }); 16629 }, 16630 16631 /******************************************************** 16632 * 16633 * Rating 16634 * 16635 ********************************************************/ 16636 16637 // Reads ratings from local storage 16638 16639 loadRatings = function(items) { 16640 16641 if (typeof items === UNDEF || !items.length) { 16642 return; 16643 } 16644 16645 var r = $.cookie(REL_PATH + PAGE_NAME + ':ratings'); 16646 16647 if (r) { 16648 if (typeof r === STRING) { 16649 r = r.split(':'); 16650 } 16651 16652 for (var i = 0; i < items.length && i < r.length; i++) { 16653 if (r[i] !== -1) { 16654 items[i][J.VISITORRATING] = r[i]; 16655 } 16656 } 16657 } 16658 16659 return items; 16660 }, 16661 16662 // Saves visitor ratings to local storage 16663 16664 saveRatings = function(items) { 16665 16666 if (typeof items === UNDEF || !items.length) { 16667 return; 16668 } 16669 16670 var r = []; 16671 16672 for (var i = 0; i < items.length; i++) { 16673 r.push(items[i].hasOwnProperty(J.VISITORRATING)? items[i][J.VISITORRATING] : -1); 16674 } 16675 16676 $.cookie(REL_PATH + PAGE_NAME + ':ratings', r, 604800); 16677 }, 16678 16679 /******************************************************** 16680 * 16681 * Image overlay rendering 16682 * or initializing images in the base overlay 16683 * 16684 ********************************************************/ 16685 16686 lazyloadTimeout = null, // Lazy load timeout 16687 fixTimeout = null, // Layout fixing timeout 16688 16689 renderImages = function(overlay, items, complete) { 16690 16691 if (!overlay || !items || !items.length) { 16692 // Invalid 16693 return; 16694 } 16695 16696 if (!Array.isArray(items)) { 16697 // Single item 16698 items = [ items ]; 16699 } 16700 16701 appendProgressbar(DEBUG? text.rendering : ''); 16702 16703 var baseOly = isBaseOverlay(), // Base overlay? 16704 ns = getOverlayNS(), // Namespace 16705 imageContainer = getImageContainer(true), // image container (for overlays) 16706 cards = getCards(), // Cards that already exist 16707 index = 0, // Batch start 16708 itemCnt = items.length, // Total count 16709 shopCnt = 0, // Shoppable count 16710 cd = settings.hasOwnProperty('markNew')? // Current day 16711 (nows - settings.markNew.days * ONEDAY_S) 16712 : 16713 null, 16714 16715 // Initializing one batch of images 16716 16717 initImages = function() { 16718 16719 if (index >= itemCnt) { 16720 // Finished 16721 finishInit(); 16722 return; 16723 } 16724 16725 var max = Math.min(index + settings.thumbsPerBatch, itemCnt), 16726 item, 16727 isFolder, 16728 isLbable, 16729 isMedia, 16730 isIcon, 16731 ar, 16732 s, 16733 card, 16734 image, 16735 img, 16736 hasImg, 16737 caption, 16738 d; 16739 16740 for (var i = index; i < max; i++) { 16741 item = items[i]; 16742 isFolder = item[J.CATEGORY] === 'folder'; 16743 isLbable = !isFolder && album.isLightboxable(item); 16744 isMedia = item[J.CATEGORY] === 'video' || item[J.CATEGORY] === 'audio'; 16745 isIcon = /res\/\w+\.png$/.test(item[J.THUMB][J.PATH]); 16746 s = item[J.THUMBCAPTION]; 16747 16748 if (i < cards.length) { 16749 // Already exists in the HTML source 16750 card = cards.eq(i); 16751 } else { 16752 // Not yet exists in the HTML source: happens only on overlays 16753 ar = item.hasOwnProperty(J.THUMB)? item[J.THUMB][J.WIDTH] / item[J.THUMB][J.HEIGHT] : 1; 16754 card = $('<div>', { 16755 'class': [ 16756 settings.cardClass, 16757 settings.lazyloadClass, 16758 item[J.CATEGORY], 16759 s? 'hascaption' : '', 16760 (item.hasOwnProperty(J.THUMB) && item[J.THUMB][J.WIDTH] < settings.thumbDims[0] && item[J.THUMB][J.HEIGHT] < settings.thumbDims[1])? 'tiny' : '', 16761 isIcon? 'icon' : '', 16762 (ar >= 1.25)? 'landscape' : ((ar <= 0.8)? 'portrait' : 'square')
16763 ].filter(Boolean).join(' ') 16764 }) 16765 .appendTo(imageContainer); 16766 } 16767 16768 // Lightboxable 16769 if (isLbable) { 16770 card.addClass(settings.lbableClass); 16771 } 16772 16773 // Data pointer 16774 card.data(J.OBJ, item); 16775 16776 // Finding/creating image's <a class="thumb"> wrap 16777 image = card.children('.' + settings.thumbClass); 16778 16779 if (!image.length) { 16780 image = $('<a>', { 16781 'class': settings.thumbClass 16782 }) 16783 .appendTo(card); 16784 } 16785 16786 // Finding/creating image 16787 img = image.find('img').eq(0); 16788 16789 if (!img.length) { 16790 // Creating placeholder DIV 16791 img = $('<div>', { 16792 'class': settings.placeholderClass 16793 }).appendTo(image); 16794 hasImg = false; 16795 } else { 16796 hasImg = true; 16797 } 16798 16799 if (isIcon) { 16800 // Icon 16801 image.addClass('iconthumb'); 16802 } else { 16803 16804 if (item[J.THUMB].hasOwnProperty(J.RENDITIONS) && settings.thumbLayout !== 'fixgrid' && settings.thumbLayout !== 'grid') { 16805 // Has renditions, wait if larger image is needed 16806 img.data('hasrenditions', true); 16807 } 16808 16809 if (!isMedia) { 16810 // Adding width, height 16811 if (hasImg) { 16812 img.attr({ 16813 width: item[J.THUMB][J.WIDTH], 16814 height: item[J.THUMB][J.HEIGHT] 16815 }); 16816 } else { 16817 img.css({ 16818 width: item[J.THUMB][J.WIDTH], 16819 height: item[J.THUMB][J.HEIGHT] 16820 }); 16821 } 16822 16823 // Adding pano class 16824 if ((item[J.THUMB][J.HEIGHT] / item[J.THUMB][J.WIDTH]) < 0.5) { 16825 card.addClass(settings.panoClass); 16826 } 16827 } 16828 } 16829 16830 if (settings.thumbLayout === 'justified') { 16831 // Justified: Saving width as data for later 16832 card.data('width', item[J.THUMB][J.WIDTH]); 16833 } 16834 16835 // Save image name: for later 16836 if (!image.data('name')) { 16837 image.attr('data-name', album.getItemName(item)); 16838 } 16839 16840 // Caption 16841 if (s) { 16842 16843 caption = card.children('.' + settings.captionClass); 16844 16845 if (!caption.length) { 16846 // No caption yet 16847 caption = $('<div>', { 16848 'class': settings.captionClass, 16849 html: s 16850 }).appendTo(card); 16851 16852 if (settings.thumbLayout === 'justified') { 16853 caption.css('maxWidth', item[J.THUMB][J.WIDTH]); 16854 } 16855 } 16856 16857 card.addClass(settings.hascaptionClass); 16858 } 16859 16860 // Preparing for Lightbox 16861 if (isLbable) { 16862 16863 // Click event 16864 image.on('click.' + ns, imageClicked); 16865 16866 // Saving href 16867 if (!image.data('href')) { 16868 image.attr('data-href', image.attr('href') || album.getOptimalImagePath(item, [ image[0].offsetWidth, image[0].offsetHeight ])); 16869 } 16870 16871 // Removing link (to avoid unwanted navigation) 16872 // Links are included to help robots finding the slide images 16873 if (image[0].hasAttribute('href')) { 16874 image.attr('href', ''); 16875 } 16876 16877 var hasShop = shopRoot.length && album.hasShop(item), 16878 hasFeedback = feedbackRoot.length && !album.disableFeedback(item); 16879 16880 // Adding select box 16881 if (hasShop || hasFeedback) { 16882 card.addClass(settings.selectableClass); 16883 } 16884 16885 // Adding shop button 16886 if (hasShop) { 16887 d = $('<a>', { 16888 'class': 'icon-shopping-cart add-cart', 16889 text: ' ' + text.addCart 16890 }) 16891 .on('click.' + ns, function(e) { 16892 var card = $(e.target).closest('.' + settings.cardClass); 16893 if (card.length) { 16894 shopRoot.trigger('addItems', card.data(J.OBJ)); 16895 } 16896 return false; 16897 }) 16898 .appendTo(image); 16899 16900 d.addTooltip(album.getPriceRange(item), { delay: 100 }); 16901 16902 shopCnt += 1; 16903 16904 card.addClass(settings.shoppableClass); 16905 } 16906 16907 // Adding feedback button 16908 if (!hasShop && hasFeedback) { 16909 d = $('<a>', { 16910 'class': 'icon-email-send add-cart', 16911 text: ' ' + settings.feedback.addBtnLabel 16912 }) 16913 .on('click.' + ns, function(e) { 16914 var card = $(e.target).closest('.' + settings.cardClass); 16915 if (card.length) { 16916 feedbackRoot.trigger('addItems', card.data(J.OBJ)); 16917 } 16918 return false; 16919 }) 16920 .appendTo(image); 16921 } 16922 16923 // Adding Fotomoto button (if no shop button) 16924 if ((!LOCAL || DEBUG) && !hasShop && 16925 settings.hasOwnProperty('fotomoto') && !album.hideFotomoto(item)) { 16926 16927 d = $('<a>', { 16928 'class': 'icon-shopping-cart add-cart fotomoto-btn', 16929 text: ' ' + text.fotomotoBtn 16930 }) 16931 .on('click.' + ns, function(e) { 16932 e.preventDefault(); 16933 if (typeof FOTOMOTO !== UNDEF) { 16934 var card = $(e.target).closest('.' + settings.cardClass); 16935 if (card.length) { 16936 FOTOMOTO.API.showWindow(10, 16937 album.getOriginalPath(card.data(J.OBJ)) || album.getItemPath(card.data(J.OBJ)) 16938 ); 16939 } 16940 } 16941 return false; 16942 }) 16943 .appendTo(image); 16944 16945 d.addTooltip(text.fotomotoTooltip); 16946 } 16947 16948 if (!baseOly && settings.linkToFolder) { 16949 // Folder link 16950 var p = album.getPath(item); 16951 16952 if (p) { 16953 image.append($('<a>', { 16954 'class': 'icon-folder folder-link', 16955 href: p + settings.indexName 16956 })); 16957 } 16958 } 16959 16960 // Adding audio clip button 16961 if (settings.useAudioClipButton && item.hasOwnProperty(J.AUDIOCLIP)) { 16962 s = $('<a>', { 16963 'class': 'icon-volume-up audioclip', 16964 href: item[J.AUDIOCLIP][J.PATH] 16965 }).on('click', togglePlayer) 16966 .appendTo(image); 16967 16968 if (!TOUCHENABLED) { 16969 s.data('tooltip', text.audioClipLabel).addTooltip(); 16970 } 16971 } 16972 } else { 16973 // Not lightboxable, e.g. webLocation or folder: using the link 16974 card.addClass(item[J.CATEGORY]); 16975 if (!image[0].href) { 16976 image.attr('href', album.getItemPath(item)); 16977 } 16978 } 16979 16980 // Mark new images 16981 if (cd != null) { 16982 16983 d = item[J.DATES]; 16984 16985 if (d && (d = d[settings.markNew.reference]) && d >= cd) { 16986 s = settings.markNew.text? 16987 $('<span>', { 16988 'class': 'new-image', 16989 text: settings.markNew.text 16990 }) 16991 : 16992 $('<span>', { 16993 'class': 'icon-new-fill new-image' 16994 });
16995 16996 image.append(s); 16997 16998 if (!TOUCHENABLED) { 16999 s.data('tooltip', (new Date(d * 1000)).toLocaleDateString(LOCALE)).addTooltip({ delay: 100 }); 17000 } 17001 } 17002 } 17003 17004 // Rating 17005 if (settings.useRating && isLbable) { 17006 17007 var r = item[J.RATING] || 0; 17008 17009 if (settings.visitorRating || r) { 17010 17011 s = $('<span>', { 17012 'class': 'rating', 17013 html: STARS 17014 }) 17015 .appendTo(image); 17016 17017 if (settings.visitorRating) { 17018 17019 if (item.hasOwnProperty(J.VISITORRATING)) { 17020 // Already exists 17021 r = item[J.VISITORRATING]; 17022 } else { 17023 if (!settings.jalbumRating) { 17024 // Ignore jAlbum rating 17025 r = 0; 17026 } 17027 // Save the default 17028 item[J.VISITORRATING] = r; 17029 } 17030 17031 s.addClass('r' + r + (settings.jalbumRating? '' : ' icon-close')) 17032 .on('click.' + ns, function(e) { 17033 var el = $(this), 17034 r = Math.minMax(0, Math.floor((getCoords(e).x - el.offset().left - parseFloat(el.css('paddingLeft'))) / 16), 5), 17035 item = el.closest('.' + settings.cardClass).data(J.OBJ), 17036 extDir = false; 17037 //Math.minMax(0, Math.floor((getCoords(e).x - el.offset().left + 4) / el.width()) * 5, 5); 17038 17039 el.removeClass('r0 r1 r2 r3 r4 r5').addClass('r' + r); 17040 item[J.VISITORRATING] = r; 17041 17042 if (!baseOly) { 17043 // Propagate to base overlay 17044 el = getCardByName(item[J.NAME], true); 17045 if (el.length) { 17046 el.find('.rating') 17047 .removeClass('r0 r1 r2 r3 r4 r5') 17048 .addClass('r' + r); 17049 el.data(J.OBJ)[J.VISITORRATING] = r; 17050 saveRatings(getBaseItems()); 17051 } 17052 } else { 17053 saveRatings(items); 17054 } 17055 17056 return false; 17057 }); 17058 } else { 17059 // Unalterable rating from jAlbum 17060 s.addClass('r' + r); 17061 } 17062 } 17063 } 17064 17065 // Prevent copy 17066 if (settings.rightClickProtect) { 17067 image.on('contextmenu', function(e) { 17068 e.preventDefault(); 17069 return false; 17070 }); 17071 } 17072 17073 if (!baseOly) { 17074 setProgressbar(i / itemCnt); 17075 } 17076 17077 } 17078 17079 index = max; 17080 window.requestAnimationFrame(initImages); 17081 }, 17082 17083 // Finishing initialization 17084 17085 finishInit = function() { 17086 17087 removeProgressbar(); 17088 17089 main.toggleClass(settings.hasShopClass, shopCnt > 0); 17090 main.toggleClass(settings.noShopClass, !shopCnt); 17091 17092 overlay.on({ 17093 setactivecard: function(e, card) { 17094 // Moving the active element into view 17095 setActiveCard($(card)); 17096 }, 17097 17098 refreshRating: function(e, card) { 17099 // Refreshing the rating on the thumbnails 17100 refreshRating($(card)); 17101 }, 17102 17103 refresh: function(e) { 17104 fixLayout(); 17105 } 17106 }); 17107 17108 //console.log('renderImages took ' + Math.round((new Date() - timer) / 1000) + 'ms'); 17109 // Done function 17110 if (typeof complete === FUNCTION) { 17111 if (baseOly) { 17112 complete.call(undefined, overlay, baseOly); 17113 } else { 17114 complete.call(undefined, overlay); 17115 } 17116 } 17117 17118 // Fixing layout only on screen size change 17119 $window.on('resize.' + ns, function(e) { 17120 clearTimeout(fixTimeout); 17121 fixTimeout = setTimeout(fixLayout, 500); 17122 return true; 17123 });
17124 17125 fixLayout(); 17126 }, 17127 17128 // Justifying rows 17129 17130 justifyRows = function(doneFn) { 17131 17132 clearTimeout(fixTimeout); 17133 17134 // No thumbnails section? 17135 if (!overlay.length) { 17136 return; 17137 } 17138 17139 // Visible? 17140 if (!overlay.is(':visible')) { 17141 // Retry until imagecontainer gets visible 17142 fixTimeout = setTimeout(justifyRows, 100); 17143 return; 17144 } 17145 17146 var cw = overlay.children('.' + settings.contClass).width(), 17147 mcw = 'calc(100% - ' + (2 * settings.thumbGap + 10) + 'px)'; 17148 17149 if (overlay.innerWidth() < settings.pageWidth && overlay.hasClass('overlay')) { 17150 // Fixing fantom scrollbars 17151 cw += 32; 17152 } 17153 17154 // Is it already fixed for this width? 17155 if (overlay.data('fixedfor') === cw) { 17156 if (typeof doneFn === FUNCTION) { 17157 doneFn.call(); 17158 } 17159 return; 17160 } 17161 17162 // Justifying rows 17163 var justifyRow = function(cards, f) { 17164 17165 if (f > 1.6) { 17166 f = 1.6; 17167 } 17168 17169 var h = Math.floor(settings.thumbDims[1] * f); 17170 17171 cards.each(function() { 17172 var el = this.querySelector('.' + settings.thumbClass); 17173 if (el) { 17174 if (f === 1) { 17175 this.style.width = ''; 17176 el.style.height = h + 'px'; 17177 } else { 17178 this.style.width = $(this).data('width') * f + 'px'; 17179 el.style.height = h + 'px'; 17180 el = this.querySelector('.' + settings.captionClass); 17181 if (el) { 17182 el.style.removeProperty("max-width"); 17183 } 17184 } 17185 } 17186 }); 17187 }, 17188 17189 justifySection = function(cards) { 17190 17191 if (!cards.length) { 17192 return; 17193 } 17194 17195 var i = 0, 17196 rw = 0, 17197 w; 17198 17199 while (cards.length && i < cards.length) { 17200 17201 w = cards.eq(i).data('width') + 2 * settings.thumbGap; 17202 17203 if ((rw + w) >= cw) { 17204 // Overflown 17205 if (rw + (w * settings.justifyWrapThreshold) >= cw) { 17206 // Wrap this item 17207 justifyRow(cards.slice(0, i), (cw - 2 * i * settings.thumbGap) * .9999 / (rw - 2 * i * settings.thumbGap)); 17208 cards = cards.slice(i); 17209 17210 } else { 17211 // With this item 17212 justifyRow(cards.slice(0, i + 1), (cw - 2 * (i + 1) * settings.thumbGap) * .9999 / (rw + w - 2 * (i + 1) * settings.thumbGap)); 17213 cards = cards.slice(i + 1); 17214 } 17215 17216 i = 0; 17217 rw = 0; 17218 17219 } else { 17220 17221 i++; 17222 rw += w; 17223 } 17224 } 17225 17226 if (cards.length > 1) { 17227 // Remainder: the last row(s) 17228 var f = (cw - 2 * cards.length * settings.thumbGap) * .9999 / (rw - 2 * cards.length * settings.thumbGap); 17229 // Max. 20% zoom 17230 justifyRow(cards, (f > 1.2)? 1 : f); 17231 } else { 17232 // Single image 17233 justifyRow(cards, 1); 17234 } 17235 17236 // Remembering last fixed width 17237 overlay.data('fixedfor', cw); 17238 }; 17239 17240 // Starts here 17241 17242 justifySection(overlay.find('.' + settings.cardClass)); 17243 17244 if (typeof doneFn === FUNCTION) { 17245 doneFn.call(); 17246 } 17247 17248 }, 17249 17250 // Fixing layout for vertical and justified layouts and lazy loading thumbs 17251 17252 fixLayout = function() { 17253 17254 clearTimeout(fixTimeout); 17255 17256 // Fixing layout, starting lazyload on complete 17257 if (settings.thumbLayout === 'justified') { 17258 fixTimeout = setTimeout(justifyRows, 100, lazyloadThumbs); 17259 } else { 17260 fixTimeout = setTimeout(lazyloadThumbs, settings.scrollCheckInterval / 2); 17261 } 17262 }, 17263 17264 // Clicked image 17265 17266 imageClicked = function(e) { 17267 if (e.target.nodeName === 'A' && ($(e.target).closest('.' + settings.commentClass).length || $(e.target).hasClass('folder-link'))) { 17268 // Link in comment or link to folder 17269 return true; 17270 } 17271 17272 $body.hideAllTooltips(); 17273 //console.log('(SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles) = (' + SMALLSCREEN + '===' + settings.lightboxFullscreenOnlyMobiles + ') =' + (SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles)); 17274 if (!isFullscreen() && settings.lightboxFullscreen && (SMALLSCREEN === settings.lightboxFullscreenOnlyMobiles)) { 17275 requestFullscreen(); 17276 } 17277 lightboxLoad($(this).closest('.' + settings.cardClass)); 17278 17279 return false; 17280 }, 17281 17282 // Marking active card 17283 17284 setActiveCard = function(card) { 17285 17286 if (typeof card === UNDEF || card instanceof Event || !card.length) { 17287 return; 17288 } 17289 17290 var item = card.data(J.OBJ), 17291 wh = $window.height(), 17292 scrollTop = $window.scrollTop(), 17293 ct = card.offset().top, 17294 ch = card.outerHeight(); 17295 17296 getCards('.' + settings.activeClass).removeClass(settings.activeClass); 17297 card.addClass(settings.activeClass); 17298 17299 // Triggering active placemark 17300 if (settings.hasOwnProperty('map')) { 17301 if (album.hasLocation(item)) { 17302 getMapRoot().trigger('setActive', item[J.NAME]); 17303 } else { 17304 getMapRoot().trigger('resetMarkers'); 17305 } 17306 } 17307 17308 // Moving into view 17309 if (ct + ch > scrollTop + wh) { 17310 window.scrollTo({ 17311 top: Math.round(ct - wh + ch + 30), 17312 behavior: 'smooth' 17313 });
17314 } else if (ct < scrollTop) { 17315 window.scrollTo({ 17316 top: Math.round(Math.max(ct - 30, 0)), 17317 behavior: 'smooth' 17318 }); 17319 } 17320 }, 17321 17322 // Refreshing the rating 17323 17324 refreshRating = function(card, rating) { 17325 17326 if (typeof card === UNDEF || !card.length) { 17327 return; 17328 } 17329 17330 var o = card.data(J.OBJ), 17331 r = ((typeof rating !== UNDEF)? 17332 rating 17333 : 17334 (settings.visitorRating? o[J.VISITORRATING] : o[J.RATING]) 17335 ) || 0; 17336 17337 card.find('.rating') 17338 .removeClass('r0 r1 r2 r3 r4 r5') 17339 .addClass('r' + r); 17340 17341 if (!isBaseOverlay()) { 17342 getCardByName(o[J.NAME], true).find('.rating') 17343 .removeClass('r0 r1 r2 r3 r4 r5') 17344 .addClass('r' + r); 17345 } else { 17346 saveRatings(items); 17347 } 17348 }; 17349 17350 // Creating new Unique ID if not yet exists 17351 if (!ns) { 17352 ns = setOverlayNS(); 17353 } 17354 17355 if (baseOly && settings.useRating && settings.visitorRating) { 17356 loadRatings(items); 17357 } 17358 17359 window.requestAnimationFrame(initImages); 17360 17361 }, 17362 17363 /******************************************************** 17364 * 17365 * Lazy loading 17366 * 17367 ********************************************************/ 17368 17369 // Loading visible thumbs 17370 17371 lazyloadThumbs = function() { 17372 17373 clearTimeout(lazyloadTimeout); 17374 lazyloadTimeout = null; 17375 17376 if (lightboxOn()) { 17377 lazyloadTimeout = setTimeout(lazyloadThumbs, 100); 17378 return; 17379 } 17380 17381 var st = $window.scrollTop(), 17382 wh = $window.height(), 17383 oly = getOverlay(), 17384 baseOly = isBaseOverlay(), 17385 cards; 17386 17387 if (Math.abs(st - scrollTop) > 20) { 17388 // Scrolling in progress 17389 scrollTop = st; 17390 lazyloadTimeout = setTimeout(lazyloadThumbs, settings.scrollCheckInterval); 17391 return; 17392 } 17393 17394 if (oly.length) { 17395 cards = oly.find('.' + settings.cardClass).not(':empty'); 17396 } else { 17397 cards = main.find('.' + settings.foldersClass + ' .' + settings.cardClass).not(':empty'); 17398 } 17399 17400 // Filtering the images to be loaded 17401 cards = cards.filter(function() { 17402 if ($(this).hasClass(settings.lazyloadClass)) { 17403 // Not loaded yet 17404 return true; 17405 } else if ($(this).hasClass(settings.preloadClass)) { 17406 // Preloadedd thumbs 17407 var thumb = $(this).find('.' + settings.thumbClass), 17408 img = thumb.find('img').eq(0); 17409 17410 $(this).removeClass(settings.preloadClass); 17411 // Keep only those that have too small resolution 17412 return !settings.fixedShapeThumbs && img.data('hasrenditions') && 17413 (img[0].width * 1.1 < thumb.width() || img[0].height * 1.1 < thumb.height()); 17414 } 17415 // Already loaded 17416 return false; 17417 }); 17418 17419 if (cards.length) { 17420 17421 cards.each(function() { 17422 var card = $(this), 17423 ct = card.offset().top, 17424 ch = card.height(); 17425
17426 if ((ct + ch) > (scrollTop - wh)) { 17427 if (ct < (scrollTop + 1.5 * wh)) { 17428 // 2 screens ahead 17429 if (baseOly && card.hasClass(settings.folderClass)) { 17430 loadFolderThumb(card); 17431 } else { 17432 loadThumb(card); 17433 } 17434 } else { 17435 return false; 17436 } 17437 } 17438 }); 17439 17440 // perpetrating lazyload 17441 scrollTop = st; 17442 lazyloadTimeout = setTimeout(lazyloadThumbs, settings.scrollCheckInterval); 17443 17444 } else { 17445 // No more left 17446 $window.off('scroll.' + ns); 17447 } 17448 }, 17449 17450 // Loading one folder 17451 17452 loadFolderThumb = function(card) { 17453 17454 if (typeof card === UNDEF || !card.length) { 17455 return; 17456 } 17457 17458 var onloadFn = function() { 17459 var img = $(this); 17460 17461 img.css({ 17462 display: 'block', 17463 opacity: 1 17464 }); 17465 }; 17466 17467 card.find('.thumb[data-src]').each(function() { 17468 var thumb = $(this), 17469 img = thumb.find('img:not(.mosimg)').eq(0); 17470 17471 if (!img.length) { 17472 img = $('<img>', { 17473 alt: thumb.data('alt') || '' 17474 }).css({ 17475 opacity: 0 17476 }).appendTo(thumb); 17477 } 17478 17479 if (typeof img.attr('src') !== UNDEF) { 17480 onloadFn.call(img[0]); 17481 } else { 17482 img.one('load', onloadFn); 17483 img.attr('src', thumb.data('src')); 17484 thumb.removeData('src'); 17485 } 17486 17487 }); 17488 17489 card.find('.mosaic [data-src]').each(function() { 17490 var m = $(this), 17491 mi = m.children('img').eq(0); 17492 17493 if (!mi.length) { 17494 mi = $('<img>').css({ 17495 opacity: 0 17496 }).appendTo(m); 17497 } 17498 17499 mi.one('load', onloadFn); 17500 mi.attr('src', m.data('src')); 17501 m.removeData('src'); 17502 }); 17503 17504 card.removeClass(settings.lazyloadClass); 17505 }, 17506 17507 // Loading one thumb 17508 17509 loadThumb = function(card) { 17510 17511 if (typeof card === UNDEF || !card.length) { 17512 return; 17513 } 17514 17515 var item = card.data(J.OBJ); 17516 17517 if (!item) { 17518 return; 17519 } 17520 17521 var thumb = card.children('.' + settings.thumbClass).eq(0), 17522 img = thumb.find('img'), 17523 onloadFn = function() { 17524 var img = $(this), 17525 card = img.parents('.' + settings.cardClass).eq(0), 17526 ph = card.find('.' + settings.placeholderClass); 17527 17528 //window.requestAnimationFrame(function() { 17529 if (ph.length) { 17530 ph.remove(); 17531 } 17532 17533 img.css({ 17534 display: 'block', 17535 opacity: 1 17536 }); 17537 17538 // }); 17539 17540 card.removeClass(settings.lazyloadClass + ' ' + settings.preloadClass); 17541 }; 17542 17543 if (!img.length) { 17544 // Create IMG element if missing 17545 img = $('<img>', { 17546 alt: album.getAlt(item) 17547 }).css({ 17548 aspectRatio: item[J.THUMB][J.WIDTH] + '/' + item[J.THUMB][J.HEIGHT], 17549 display: 'none', 17550 opacity: 0 17551 }).appendTo(thumb); 17552 } 17553 17554 if (card.hasClass(settings.lazyloadClass) && typeof img.attr('src') !== UNDEF && img.attr('src') !== false) { 17555 // IMG already has source and lazyloaded 17556 onloadFn.call(img[0]); 17557 } else { 17558 // Missing or low res. 17559 img.one('load', onloadFn); 17560 // Set source: optimal thumbnail if fixedShape, can fall back to images otherwise 17561 img.attr('src', img.data('hasrenditions')? 17562 album.getOptimalThumbPath(card.data(J.OBJ), [ thumb.outerWidth(), thumb.outerHeight() ], !settings.fixedShapeThumbs) 17563 : 17564 album.getThumbPath(item)); 17565 } 17566 }, 17567 17568 /******************************************************** 17569 * 17570 * Preparing images 17571 * 17572 ********************************************************/ 17573 17574 prepareImages = function(overlay, baseOly) { 17575 17576 if (!album) { 17577 return; 17578 } 17579
17580 if (typeof overlay === UNDEF) { 17581 overlay = getBaseOverlay(); 17582 baseOly = true; 17583 } else if (typeof baseOly === UNDEF) { 17584 baseOly = isBaseOverlay(overlay); 17585 } 17586 17587 var cards = getCards(), 17588 imgs = cards.filter('.' + settings.lbableClass); 17589 17590 // Start lazy loading and layout corrections 17591 overlay.trigger('refresh'); 17592 17593 if (imgs.length > 1 || settings.showEmptyFilters) { 17594 // Filtering and sort only if there are at least 2 items 17595 if ($('.filter-cont').length) { 17596 // Initializing Filters / Sort 17597 prepareFilterBox(baseOly); 17598 } 17599 } 17600 17601 if (imgs.length) { 17602 // Has lighboxable images 17603 17604 // Selection boxes 17605 if (album.hasShop() || settings.feedback) { 17606 var ci = getSelectableCards(); 17607 17608 toggleSelectButtons(0, ci.length); 17609 toggleBoxes(ci.length); 17610 17611 if (ci.length) { 17612 // Make items selectable 17613 overlay.selectable({ 17614 cardClass: settings.selectableClass, 17615 selectedClass: settings.selectedClass, 17616 selectionChange: function(n) { 17617 toggleSelectButtons(n, ci.length); 17618 shopRoot.add(feedbackRoot).trigger('itemsSelected', n); 17619 } 17620 }); 17621 17622 // Keep selected items button wiring 17623 shopBox.add(feedbackBox) 17624 .find('.' + settings.keepSelectedClass).on('click.' + ns, function() { 17625 keepSelectedItems(text.selectedItems, function() { 17626 setHash(); 17627 }); 17628 17629 return false; 17630 }); 17631 17632 // Initialized 17633 content.find('[data-sticky-rel]').trigger('stickyRefresh'); 17634 // Uninited 17635 content.find('[data-sticky]').not('[data-sticky-rel]').sticky(); 17636 17637 } 17638 } 17639 17640 // Initializing map 17641 if (settings.hasOwnProperty('map')) { 17642 // Pass settings to lightbox defaults 17643 if (settings.map.hasOwnProperty('apiKey')) { 17644 $.fn.lightbox.defaults['mapApiKey'] = settings.map.apiKey; 17645 } 17646 if (settings.map.hasOwnProperty('type')) { 17647 $.fn.lightbox.defaults['mapType'] = settings.map.type; 17648 } 17649 if (settings.map.hasOwnProperty('zoom')) { 17650 $.fn.lightbox.defaults['mapZoom'] = settings.map.zoom; 17651 } 17652 if (settings.map['index']) { 17653 prepareMap(getAllItems(), settings.map); 17654 } 17655 } 17656 17657 /* Waiting for Derek's answer 17658 if (!LOCAL && typeof FOTOMOTO !== UNDEF && settings.fotomoto) { 17659 FOTOMOTO.API.checkinImages(getFotomotoImages(cards), function(images) { 17660 17661 }); 17662 } 17663 */ 17664 17665 // Removing the lightbox, athough this should not happen 17666 overlay.trigger('lightboxRemove'); 17667 17668 overlay.lightbox(album, { 17669 17670 baseSet: baseOly, 17671 17672 onLoadStart: function(card) { 17673 var item = card.data(J.OBJ); 17674 17675 if (item) { 17676 addParam(settings.pageName || settings.indexName, { 17677 img: album.getItemName(item) 17678 }, item[J.TITLE]); 17679 } 17680 }, 17681 17682 onLoadEnd: function(card) { 17683 if (typeof card !== UNDEF) { 17684 overlay.trigger('setactivecard', card); 17685 } 17686 setTimeout(updateShares, 100); 17687 }, 17688 17689 onClose: function(card) { 17690 if (isFullscreen() && settings.slideshowFullscreen && (SMALLSCREEN === settings.slideshowFullscreenOnlyMobiles)) { 17691 // Exit fullscreen only on desktops 17692 exitFullscreen(); 17693 } 17694 if (typeof card !== UNDEF) { 17695 overlay.trigger('setactivecard', card); 17696 } 17697 updateShares(); 17698 overlay.trigger('refresh'); 17699 setHash(); 17700 }, 17701 17702 onVolumeChange: function(v) { 17703 settings.audioClipVolume = v; 17704 }, 17705 17706 onReady: function() { 17707 if (initByHash && window.location.hash) { 17708 initByHash = false; 17709 var param = readParam(); 17710 17711 hashChangedByJs = true; 17712 17713 if (param.hasOwnProperty('label') && param.label !== null) { 17714 // Label filter 17715 getItemsByLabel(param.label, function() { 17716 addParam(settings.indexName, { 17717 label: param.label + '', 17718 search: null, 17719 newimages: null, 17720 date: null 17721 }, text.label + ': ' + param.label); 17722 });
17723 } else if (param.hasOwnProperty('search') && param.search !== null) { 17724 // Search 17725 getItemsBySearch(param.search, function() { 17726 addParam(settings.indexName, { 17727 search: param.search + '', 17728 label: null, 17729 newimages: null, 17730 date: null 17731 }, text.search + ': ' + param.search); 17732 }); 17733 } else if (param.hasOwnProperty('newimages')) { 17734 // Search new images 17735 getItemsByDate(getSearchNewOptions(param.newimages), function() { 17736 addParam(settings.indexName, { 17737 newimages: param.newimages, 17738 search: null, 17739 label: null, 17740 date: null 17741 }, text.searchNew + ': ' + param.newimages); 17742 }); 17743 } else if (param.hasOwnProperty('date')) { 17744 var options = getDateRangeOptions(param.date); 17745 // Search images in date range 17746 getItemsByDate(options, function() { 17747 addParam(settings.indexName, { 17748 date: param.date, 17749 newimages: null, 17750 search: null, 17751 label: null 17752 }, getDateReference(options.reference) + ': ' + param.date); 17753 }); 17754 } else if (param.hasOwnProperty('img') && param.img !== null) { 17755 var card = getCardByName(param.img); 17756 17757 if (card.length) { 17758 if (param.hasOwnProperty('slideshow')) { 17759 lightboxLoad(card, 2000); 17760 } else { 17761 lightboxLoad(card); 17762 } 17763 } else { 17764 removeParam(settings.pageName || settings.indexName, 'img'); 17765 } 17766 } else if (param.hasOwnProperty('slideshow')) { 17767 lightboxLoad(0, 2000); 17768 } 17769 } 17770 17771 $('.' + settings.startshowClass).fadeIn().on('click', function() { 17772 $(this).trigger('removeTooltip'); 17773 startSlideshow(); 17774 return false; 17775 }); 17776 } 17777 });
17778 17779 } else { 17780 // No images: remove the start slideshow button 17781 $('.' + settings.startshowClass).fadeOut(); 17782 } 17783 }, 17784 17785 // Sets the URL by JS 17786 17787 setHash = function(params, title) { 17788 var hash = (typeof params === UNDEF)? '' : '#' + paramize(params); 17789 17790 if (hash !== window.location.hash) { 17791 hashChangedByJs = true; 17792 if (params) { 17793 setParam(settings.pageName || settings.indexName, params, title || ''); 17794 } else { 17795 removeParam(settings.pageName || settings.indexName); 17796 } 17797 setTimeout(function() { 17798 // reseting hashByJs 17799 hashChangedByJs = false; 17800 }, 1000); 17801 } 17802 }, 17803 17804 // Hash change listener :: when the browser's back / forward buttons were pressed 17805 17806 stateChange = function() { 17807 if (window.location.hash === lastHash || hashChangedByJs) { 17808 // Same hash or changed by JS (not on external trigger) 17809 return; 17810 } 17811 17812 lastHash = window.location.hash; 17813 hashChangedByJs = false; 17814 initByHash = false; 17815 17816 var param = readParam(); 17817 17818 if (param.hasOwnProperty('label') && param.label !== null) { 17819 // Label filter 17820 getItemsByLabel(param.label, function() { 17821 updateShares(); 17822 });
17823 } else if (param.hasOwnProperty('search') && param.search !== null) { 17824 // Search 17825 getItemsBySearch(param.search, function() { 17826 updateShares(); 17827 }); 17828 } else if (param.hasOwnProperty('newimages') && param.newimages !== null) { 17829 // New images 17830 getItemsByDate(getSearchNewOptions(param.newimages), function() { 17831 updateShares(); 17832 }); 17833 } else if (param.hasOwnProperty('date') && param.date !== null) { 17834 // Date range 17835 var options = getDateRangeOptions(param.date); 17836 getItemsByDate(options, function() { 17837 updateShares(); 17838 }); 17839 } else if (param.hasOwnProperty('img') && param.img !== null) { 17840 // Image (no search or label) 17841 var card = getCardByName(param.img); 17842 if (card.length) { 17843 if (param.hasOwnProperty('slideshow')) { 17844 lightboxLoad(card, 2000); 17845 } else { 17846 lightboxLoad(card); 17847 } 17848 } else { 17849 removeParam(settings.indexName, 'img'); 17850 } 17851 } else if (param.hasOwnProperty('slideshow')) { 17852 lightboxLoad(0, 2000); 17853 } else { 17854 // Plain index page 17855 lightboxQuit(); 17856 removeOverlay(); 17857 } 17858 17859 }, 17860 17861 // Starts slideshow 17862 17863 startSlideshow = function(e) { 17864 var card = $(); 17865 17866 if (!settings.restartSlideshow) { 17867 card = getCards('.' + settings.activeClass); 17868 } 17869 17870 if (!card.length) { 17871 // Fall back to first 17872 card = getCards().eq(0); 17873 } 17874 17875 if (!isFullscreen() && settings.slideshowFullscreen && (SMALLSCREEN === settings.slideshowFullscreenOnlyMobiles)) { 17876 requestFullscreen(); 17877 } 17878 17879 lightboxLoad(card, 2000); 17880 17881 return false; 17882 }, 17883 17884 /******************************************************** 17885 * 17886 * Functionalities that results in new overlay 17887 * 17888 ********************************************************/ 17889 17890 // Preparing the overlay before the items arrive 17891 17892 prepareOverlay = function(attr) { 17893 var oly = addOverlay(attr); 17894 17895 appendProgressbar(DEBUG? text.loading : ''); 17896 focusOverlay(); 17897 17898 return oly; 17899 }, 17900 17901 // Finishing the overlay when the items are ready 17902 17903 renderOverlay = function(oly, items, ready) { 17904 17905 removeProgressbar(); 17906 renderImages(oly, items, prepareImages); 17907 17908 toggleSelectButtons(0, items.length); 17909 toggleBoxes(items.length); 17910 toggleHasImages(items.length); 17911 17912 if (typeof ready === FUNCTION) { 17913 ready.call(); 17914 } 17915 }, 17916 17917 /******************************************************** 17918 * 17919 * Keeping only selected items 17920 * 17921 ********************************************************/ 17922 17923 keepSelectedItems = function(tx, ready) { 17924 var items = getSelectedItems(); 17925 17926 if (!items.length || items.length === getCards('.' + settings.lbableClass).length) { 17927 return; 17928 } 17929 17930 var oly = prepareOverlay({ 17931 className: 'selected-items', 17932 icon: 'icon-checkbox-checked', 17933 title: '“' + tx + '”' 17934 });
17935 17936 setTimeout(function() { 17937 17938 appendToOverlayTitle($('<small>', { 17939 text: text.foundNTimes.replace('{0}', items.length) 17940 })); 17941 17942 renderOverlay(oly, items, ready); 17943 17944 }, 50); 17945 }, 17946 17947 /******************************************************** 17948 * 17949 * Search and tag search 17950 * 17951 ********************************************************/ 17952 17953 // Executing Search 17954 17955 getItemsBySearch = function(tx, ready) { 17956 var oly = prepareOverlay({ 17957 className: 'search-results', 17958 icon: 'icon-search', 17959 title: '“' + tx + '”' 17960 }); 17961 17962 album.collectItems($.extend({}, settings.search, { 17963 terms: tx, 17964 depth: 'tree', 17965 ready: function() { 17966 var found = this.length; 17967 17968 if (this.length > settings.maxResults) { 17969 this.splice(settings.maxResults); 17970 } 17971 17972 appendToOverlayTitle($('<small>', { 17973 text: (found > settings.maxResults)? 17974 text.foundTooMany.replace('{0}', found).replace('{1}', this.length) 17975 : 17976 found? 17977 text.foundNTimes.replace('{0}', found) 17978 : 17979 text.notFound 17980 })); 17981 17982 renderOverlay(oly, this, ready); 17983 } 17984 })); 17985 }, 17986 17987 // Preparing Search functionality 17988 17989 prepareSearch = function(f) { 17990 17991 if (!f.length || !album) { 17992 return; 17993 } 17994 17995 // Adding defaults if missing 17996 17997 settings.search = $.extend({ 17998 depth: 'tree', 17999 fields: 'creator,keywords,title,comment' 18000 }, settings.search); 18001 18002 var startSearch = function(f) { 18003 18004 var tx = f.find('input').eq(0).val(); 18005 18006 if (tx && tx.length > 1) { 18007 getItemsBySearch(tx, function() { 18008 setHash({ 18009 search: tx 18010 }, text.search + ': ' + tx); 18011 updateShares(); 18012 }); 18013 } 18014 return false; 18015 }; 18016 18017 f.find('.search-btn').on('click', function() { 18018 var btn = $(this), 18019 form = btn.closest('form'); 18020 18021 if (form.hasClass('toggling')) { 18022 if (form.hasClass(settings.activeClass)) { 18023 form.removeClass(settings.activeClass); 18024 startSearch(form); 18025 } else { 18026 form.find('input').eq(0).focus(); 18027 form.addClass(settings.activeClass); 18028 } 18029 } else { 18030 startSearch(form); 18031 } 18032 18033 return false; 18034 }); 18035 18036 f.on('submit', function(e) { 18037 e.preventDefault(); 18038 startSearch(f); 18039 f.removeClass(settings.activeClass); 18040 return false; 18041 }); 18042 18043 f.find('input').on('focus', function() { 18044 f.addClass(settings.activeClass); 18045 }); 18046 }, 18047 18048 /******************************************************** 18049 * 18050 * Tag cloud 18051 * 18052 ********************************************************/ 18053 18054 // Preparing Tag Search functionality 18055 18056 prepareTagSearch = function(target) { 18057 var f = target.find('.' + settings.tagSearchClass), 18058 // Start tag search 18059 startSearch = function(f) { 18060 var tx = f.find('input').eq(0).val(); 18061 18062 if (tx) { 18063 getItemsByLabel(tx, function() { 18064 setHash({ 18065 label: tx 18066 }, text.label + ': ' + tx); 18067 updateShares(); 18068 }); 18069 } 18070 return false; 18071 }; 18072 18073 if (!f.length) { 18074 f = $('<form>', { 18075 'class': settings.tagSearchClass 18076 }).appendTo(target); 18077 18078 f.append($('<div>', { 18079 'class': 'search-input' 18080 }) 18081 .append($('<input>', { 18082 'type': 'text', 18083 placeholder: text.searchPlaceholder, 18084 ariaLabel: text.search 18085 }))); 18086 18087 f.append($('<a>', { 18088 'class': 'search-btn icon-search' 18089 })); 18090 } 18091 18092 f.find('.search-btn').on('click', function() { 18093 var f = $(this).closest('form'); 18094 f.find('input').eq(0).focus(); 18095 startSearch(f); 18096 return false; 18097 });
18098 18099 f.on('submit', function(e) { 18100 e.preventDefault(); 18101 startSearch($(this)); 18102 return false; 18103 }); 18104 18105 }, 18106 18107 // Search labels 18108 18109 getItemsByLabel = function(tx, caseSensitive, ready) { 18110 var oly = prepareOverlay({ 18111 className: 'tags-found', 18112 icon: 'icon-tag', 18113 title: '“' + tx + '”' 18114 }); 18115 18116 if (typeof caseSensitive === UNDEF) { 18117 caseSensitive = false; 18118 } else if (typeof caseSensitive === FUNCTION) { 18119 ready = caseSensitive; 18120 caseSensitive = false; 18121 } 18122 18123 album.collectItems($.extend({}, settings.tagCloud, { 18124 caseSensitive: caseSensitive, 18125 exact: true, 18126 terms: tx, 18127 ready: function() { 18128 var found = this.length; 18129 18130 if (this.length > settings.maxResults) { 18131 this.splice(settings.maxResults); 18132 } 18133 18134 appendToOverlayTitle($('<small>', { 18135 text: (found > settings.maxResults)? 18136 text.foundTooMany.replace('{0}', found).replace('{1}', this.length) 18137 : 18138 found? 18139 text.foundNTimes.replace('{0}', found) 18140 : 18141 text.notFound 18142 })); 18143 18144 renderOverlay(oly, this, ready); 18145 } 18146 })); 18147 }, 18148 18149 // Collect items from a path array 18150 18151 getItemsByPaths = function(tx, paths, ready) { 18152 var oly = prepareOverlay({ 18153 className: 'tags-found', 18154 icon: 'icon-tag', 18155 title: '“' + tx + '”' 18156 });
18157 18158 album.collectByPath({ 18159 paths: paths, 18160 ready: function() { 18161 var found = this.length; 18162 18163 if (this.length > settings.maxResults) { 18164 this.splice(settings.maxResults); 18165 } 18166 18167 appendToOverlayTitle($('<small>', { 18168 text: (found > settings.maxResults)? 18169 text.foundTooMany.replace('{0}', found).replace('{1}', this.length) 18170 : 18171 found? 18172 text.foundNTimes.replace('{0}', found) 18173 : 18174 text.notFound 18175 })); 18176 18177 renderOverlay(oly, this, ready); 18178 } 18179 }); 18180 }, 18181 18182 // Add toggle button 18183 18184 addToggleButton = function(t) { 18185 var btn; 18186 18187 t.css({ 18188 maxHeight: 150, 18189 overflow: 'hidden' 18190 }); 18191 18192 btn = $('<div>', { 18193 'class': 'toggle-height' 18194 }).insertAfter(t); 18195 18196 btn.append($('<a>', { 18197 'class': 'icon-caret-down', 18198 text: text.more 18199 }) 18200 .on('click', function() { 18201 var b = t.parent(), 18202 toggleHeight = function() { 18203 var h = t.prop('scrollHeight'); 18204 18205 b.css({ 18206 height: 'auto', 18207 overflow: 'visible' 18208 }); 18209 t.css({ 18210 maxHeight: 150, 18211 }); 18212 setTimeout(function() { 18213 t.css({ 18214 maxHeight: h + 20 18215 }); 18216 }, 25); 18217 btn.addClass(settings.activeClass); 18218 }; 18219 18220 b.css({ 18221 height: b.height(), 18222 overflow: 'hidden' 18223 }); 18224 18225 t.css({ 18226 maxHeight: 'none' 18227 }); 18228 18229 setTimeout(toggleHeight, 50); 18230 18231 } 18232 )) 18233 .append($('<a>', { 18234 'class': 'icon-caret-up', 18235 text: text.less 18236 }).on('click', function() { 18237 t.css({ 18238 maxHeight: 150 18239 }); 18240 btn.removeClass(settings.activeClass); 18241 })); 18242 18243 }, 18244 18245 // Collecting and displaying tags 18246 18247 prepareTagCloud = function(tags) { 18248 var tc = $(settings.tagCloud['hook'] || '.tag-cloud-cont'); 18249 18250 if (!tc.length) { 18251 return; 18252 } 18253 18254 if (tags.length) { 18255 var t = tc.children('.' + settings.tagCloudClass), 18256 // Click function 18257 tagClicked = function() { 18258 var tx = $(this).children('span').eq(0).text(); 18259 18260 if (tx) { 18261 getItemsByPaths(tx, $(this).data('paths'), function() { 18262 setHash({ 18263 label: tx 18264 }, text.label + ': ' + tx); 18265 updateShares(); 18266 }); 18267 } 18268 18269 return false; 18270 }; 18271 18272 // Creating wrap el if not exists 18273 if (!t.length) { 18274 t = $('<div>', { 18275 'class': settings.tagCloudClass 18276 }).appendTo(tc); 18277 } 18278 18279 // Adding tags 18280 for (var a, r, cnt, i = 0, l = tags.length; i < l; i++) { 18281 18282 // Creating link element 18283 a = $('<a>', { 18284 'class': 'tag' 18285 }) 18286 .on('click', tagClicked) 18287 .data('paths', tags[i][1]) 18288 .append($('<span>', { 18289 html: tags[i][0] 18290 })); 18291 18292 // Adding count 18293 if (tags[i][1].length > 1) { 18294 18295 if (settings.tagCloud.fontVaries) { 18296 r = 0; 18297 cnt = Math.min(64, tags[i][1].length); 18298 // calculate weight 18299 while (cnt > 1) { 18300 r++; 18301 cnt = cnt >> 1; 18302 } 18303 a.addClass('size-' + r); 18304 } 18305 18306 a.append(' ').append($('<sup>', { 18307 text: tags[i][1].length 18308 })); 18309 } 18310 18311 t.append(a); 18312 } 18313 18314 // Adding toggle button if list is too high 18315 if (t.outerHeight() > 175) { 18316 addToggleButton(t); 18317 } 18318 } 18319 18320 // Adding search box 18321 if (settings.tagCloud['useSearch']) { 18322 prepareTagSearch(tc.parent()); 18323 } else if (!tags.length) { 18324 // Nothing found 18325 tc.parents('.box').eq(0).hide();//remove(); 18326 } 18327 18328 }, 18329 18330 /******************************************************** 18331 * 18332 * Search new images 18333 * 18334 ********************************************************/ 18335 18336 getSearchNewOptions = function(days) { 18337 18338 return settings.hasOwnProperty('searchNew')? 18339 { 18340 reference: settings.searchNew['reference'] || 'dateTaken', 18341 depth: settings.searchNew['depth'] || 'subfolders', 18342 start: nows / ONEDAY_S - days 18343 } 18344 : 18345 { 18346 reference: 'dateTaken', 18347 depth: 'subfolders', 18348 start: nows / ONEDAY_S - days 18349 }; 18350 18351 }, 18352 18353 // Search for new images 18354 18355 getDateRangeOptions = function(range) { 18356 var m = range.match(/^([amt])?([cts])?\:?(\d+)\-?(\d+)?/i), 18357 o = { 18358 range: 0, 18359 reference: 'dateTaken', 18360 depth: 'current' 18361 }; 18362 18363 if (m) { 18364 if (m[1]) { 18365 // Reference 18366 if (m[1] === 'a') { 18367 o.reference = 'added'; 18368 } else if (m[1] === 'm') { 18369 o.reference = 'fileModified'; 18370 } else if (m[1] === 't') { 18371 o.reference = 'dateTaken'; 18372 } 18373 } 18374 18375 if (m[2]) { 18376 // Depth 18377 if (m[2] === 'c') { 18378 o.depth = 'current'; 18379 } else if (m[2] === 't') { 18380 o.depth = 'tree'; 18381 } else if (m[2] === 's') { 18382 o.depth = 'subfolders'; 18383 } 18384 } 18385 18386 if (m[4]) { 18387 // Range 18388 o.start = parseInt(m[3]) || 0; 18389 o.end = parseInt(m[4]); 18390 } else if (m[3]) { 18391 // Single day 18392 o.start = parseInt(m[3]);
18393 o.range = 1; 18394 } 18395 } 18396 18397 return o; 18398 }, 18399 18400 getDateReference = function(r) { 18401 18402 if (r === 'dateTaken') { 18403 return text.imagesTaken; 18404 } else if (r === 'fileModified') { 18405 return text.imagesModified; 18406 } 18407 18408 return text.imagesAdded; 18409 }, 18410 18411 getDateRangeTitle = function(o) { 18412 var t1 = getDateReference(o['reference']), 18413 t2, 18414 date = function(d) { 18415 return new Date(d * ONEDAY_MS).toLocaleDateString(LOCALE); 18416 }; 18417 18418 if (!o.hasOwnProperty('end') && !o.hasOwnProperty('start')) { 18419 t2 = getTimespan(o.range); 18420 } else if (!o.hasOwnProperty('end')) { 18421 if (o.hasOwnProperty('start')) { 18422 if (o.hasOwnProperty('range')) { 18423 if (o['range'] > 1) { 18424 t2 = text.betweenDays.template(date(o.start), date(o.start + o.range)); 18425 } else { 18426 t2 = text.onDay.template(date(o.start)); 18427 } 18428 } else { 18429 t2 = getTimespan(nows / ONEDAY_S - o.start); 18430 } 18431 } 18432 } else { 18433 t2 = text.beforeDay.template(date(o.end)); 18434 } 18435 18436 return t1 + ' ' + t2; 18437 }, 18438 18439 getItemsByDate = function(options, ready) { 18440 var oly = prepareOverlay({ 18441 className: 'search-results', 18442 icon: 'icon-calendar', 18443 title: getDateRangeTitle(options) 18444 });
18445 18446 album.collectByDate($.extend({}, options, { 18447 ready: function() { 18448 var found = this.length; 18449 18450 if (this.length > settings.maxResults) { 18451 this.splice(settings.maxResults); 18452 } 18453 18454 appendToOverlayTitle($('<small>', { 18455 text: (found > settings.maxResults)? 18456 text.foundTooMany.replace('{0}', found).replace('{1}', this.length) 18457 : 18458 found? 18459 text.foundNTimes.replace('{0}', found) 18460 : 18461 text.notFound 18462 })); 18463 18464 renderOverlay(oly, this, ready); 18465 } 18466 })); 18467 }, 18468 18469 // Search date range 18470 18471 searchDateRange = function(range) { 18472 18473 if (range) { 18474 var options = getDateRangeOptions(range); 18475 18476 getItemsByDate(options, function() { 18477 setHash({ 18478 date: range 18479 }, getDateRangeTitle(options)); 18480 updateShares(); 18481 });
18482 } 18483 18484 return false; 18485 }, 18486 18487 // Search new images 18488 18489 searchNew = function(days) { 18490 18491 if (days) { 18492 18493 getItemsByDate($.extend({}, settings.searchNew, { 18494 start: nows / ONEDAY_S - days 18495 }), function() { 18496 setHash({ 18497 newimages: days 18498 }, getDateReference(settings.searchNew['reference'])); 18499 updateShares(); 18500 }); 18501 } 18502 18503 return false; 18504 }, 18505 18506 // Prepare Search new functionality 18507 18508 prepareSearchNew = function(f) { 18509 18510 if (!f.length || !album) { 18511 return; 18512 } 18513 18514 // Setting up defaults if something is missing 18515 18516 settings.searchNew = $.extend({ 18517 days: '3,30,90', 18518 depth: 'folder', 18519 reference: 'dateTaken', 18520 sinceLastVisit: true 18521 }, settings.searchNew); 18522 18523 var days = settings.searchNew.days.split(','); 18524 18525 f.append($('<label>', { 18526 text: getDateReference(settings.searchNew.reference) 18527 })); 18528 18529 f = $('<div>', { 18530 'class': 'buttons' 18531 }).appendTo(f); 18532 18533 for (var i = 0; i < days.length; i++) { 18534 f.append($('<a>', { 18535 'class': 'small button', 18536 text: getTimespan(days[i]) 18537 }) 18538 .data('days', parseInt(days[i], 10)) 18539 .on('click', function() { 18540 searchNew($(this).data('days')); 18541 })); 18542 } 18543 18544 if (settings.searchNew.sinceLastVisit) { 18545 18546 var start = $.cookie('lastVisit'), 18547 now = Math.round(new Date() / 1000); 18548 18549 if (start && start < (now - ONEDAY_S)) { 18550 f.append($('<a>', { 18551 'class': 'small button', 18552 text: text.sinceMyLastVisit 18553 }) 18554 .on('click', function() { 18555 searchNew((now - start) / ONEDAY_S); 18556 })); 18557 } 18558 18559 // Storing last visit date for 10 years 18560 $.cookie('lastVisit', now, 315360000); 18561 } 18562 }, 18563 18564 /******************************************************** 18565 * 18566 * Filtering & Sort 18567 * 18568 ********************************************************/ 18569 18570 // Dates are objects 18571 18572 getDate = function(item, name) { 18573 if (item.hasOwnProperty(J.DATES)) { 18574 if (name === 'addedDate') { 18575 return item[J.DATES]['added']; 18576 } else if (name === 'originalDate') { 18577 return item[J.DATES]['dateTaken']; 18578 } else { 18579 return item[J.DATES]['fileModified']; 18580 } 18581 } 18582 18583 return null; 18584 }, 18585 18586 // Some variables get remapped by the skin 18587 18588 getMappedVal = function(item, name) { 18589 18590 if (item.hasOwnProperty(name)) { 18591 // Generic case 18592 return item[name]; 18593 } 18594 18595 return null; 18596 }, 18597 18598 // Do filtering on current folder 18599 18600 doFilter = function(e, ready) { 18601 var form = $(e.target).closest('form'), 18602 items = getAllItems(), 18603 cnt = items.length, 18604 sbd = {}, 18605 tx = []; 18606 18607 form.find('select:visible:not(:disabled),input[type=range],input[type=date]').each(function() { 18608 var self = $(this), 18609 name = this.name, 18610 label = form.find('label[for=' + name + ']').text(), 18611 type = self.data('type'), 18612 value = self.val(), 18613 display = function(val) { 18614 if (name === 'rating') { 18615 return Math.round(val); 18616 } else if (name === 'fileSize') { 18617 return niceByte(val); 18618 } else if (type === 'date') { 18619 return (new Date(val * 1000)).toLocaleDateString(LOCALE); 18620 } else if (type === 'numeric') { 18621 if (self.data('unit') === 'seconds') { 18622 return niceTime(val); 18623 } else if (self.data('unit') === 'byte') { 18624 return niceByte(val); 18625 } else if (val >= 10) { 18626 return Math.round(val); 18627 } else if (val >= 1) { 18628 return val.toFixed(1); 18629 } else if (val >= 0.1) { 18630 return val.toFixed(2); 18631 } 18632 return val; 18633 } 18634 return toCurrency(val, currency); 18635 }; 18636 18637 if (type === 'numeric' || type === 'date' || type === 'financial') { 18638 18639 var low = self.data('low'), 18640 high = self.data('high'); 18641 18642 if (low > this.min || high < this.max) { 18643 // Only if the range is narrower than the whole range 18644 18645 if (name.endsWith('Date')) { 18646 // Date 18647 for (var i = items.length - 1, val; i >= 0; i--) { 18648 if ((val = getDate(items[i], name)) !== null && 18649 (val >= low && val <= high)) { 18650 continue; 18651 } 18652 items.splice(i, 1); 18653 } 18654 } else if (JCAMERAFIELDS.indexOf(name) !== -1) { 18655 // Camera data fields 18656 if (name === 'exposureTime') { 18657 for (var i = items.length - 1, val; i >= 0; i--) { 18658 if (items[i].hasOwnProperty(J.CAMERA) && 18659 items[i][J.CAMERA].hasOwnProperty(name) && 18660 (val = getExposure(items[i][J.CAMERA][name])) && 18661 (val >= low && val <= high)) { 18662 continue; 18663 } 18664 items.splice(i, 1); 18665 } 18666 } else if (name.startsWith('focalLength')) { 18667 for (var i = items.length - 1, val; i >= 0; i--) { 18668 if (items[i].hasOwnProperty(J.CAMERA) && 18669 items[i][J.CAMERA].hasOwnProperty(name) && 18670 !isNaN(val = parseFloat(items[i][J.CAMERA][name])) && 18671 (val >= low && val <= high)) { 18672 continue; 18673 } 18674 items.splice(i, 1); 18675 } 18676 } else { 18677 for (var i = items.length - 1, val; i >= 0; i--) { 18678 if (items[i].hasOwnProperty(J.CAMERA) && 18679 items[i][J.CAMERA].hasOwnProperty(name) && 18680 !isNaN(val = parseFloat(items[i][J.CAMERA][name])) && 18681 (val >= low && val <= high)) { 18682 continue; 18683 } 18684 items.splice(i, 1); 18685 } 18686 } 18687 } else if (name === 'rating') { 18688 // Rating 18689 for (var i = items.length - 1, val, r = settings.visitorRating? J.VISITORRATING : J.RATING; i >= 0; i--) { 18690 val = items[i][r] || 0; 18691 if (val >= low && val <= high) { 18692 continue; 18693 } 18694 items.splice(i, 1); 18695 } 18696 } else { 18697 // Generic 18698 for (var i = items.length - 1, val; i >= 0; i--) { 18699 if (!isNaN(val = parseFloat(items[i][name])) && 18700 (val >= low && val <= high)) { 18701 continue; 18702 } 18703 items.splice(i, 1); 18704 } 18705 } 18706
18707 tx.push(label + '=' + display(low) + '–' + display(high)); 18708 } 18709 18710 } else if (this.nodeName === 'SELECT' && value !== null && value.length) { 18711 18712 // Only if at least one option is selected 18713 18714 if (!Array.isArray(value)) { 18715 value = [ value ]; 18716 } 18717 18718 if (JCAMERAFIELDS.indexOf(name) !== -1) { 18719 // Camera data :: no arrays expected 18720 for (var i = items.length - 1, val; i >= 0; i--) { 18721 if (items[i].hasOwnProperty(J.CAMERA) && 18722 items[i][J.CAMERA].hasOwnProperty(name) && 18723 (val = items[i][J.CAMERA][name]) !== null && 18724 value.includes(val)) { 18725 continue; 18726 } 18727 items.splice(i, 1); 18728 } 18729 18730 } else { 18731 // Generic case 18732 for (var i = items.length - 1, found, val; i >= 0; i--) { 18733 if (items[i].hasOwnProperty(name)) { 18734 val = items[i][name]; 18735 if (Array.isArray(val)) { 18736 // Array e.g. keywords 18737 found = false; 18738 for (var j = 0; j < value.length; j++) { 18739 if (val.includes(value[j])) { 18740 //console.log(items[i]['name'] + ': ' + val); 18741 found = true; 18742 break; 18743 } 18744 } 18745 if (found) { 18746 continue; 18747 } 18748 } else if (value.includes(val)) { 18749 continue; 18750 } 18751 } 18752 items.splice(i, 1); 18753 } 18754 } 18755 18756 sbd[name] = value; 18757 18758 // Header text addition 18759 if (this['multiple']) { 18760 tx.push(name + '=[' + value.join(',') + ']'); 18761 } else { 18762 tx.push(label + '=' + value); 18763 } 18764 } 18765 }); 18766 18767 if (items.length < cnt) { 18768 18769 // Only show overlay if at least one thumb was filtered out 18770 18771 var oly = prepareOverlay({ 18772 className: 'filtered-items', 18773 icon: 'icon-filter', 18774 title: text.results 18775 }); 18776 18777 if (!isEmpty(sbd)) { 18778 oly.data('sbd', sbd); 18779 } 18780 18781 setTimeout(function() { 18782 18783 appendToOverlayTitle($('<small>', { 18784 html: tx.join(', ') + ': ' + text.foundNTimes.replace('{0}', items.length) 18785 })); 18786 18787 oly.on('overlayRemoved', resetBaseFilters); 18788 18789 renderOverlay(oly, items, ready); 18790 18791 }, 50); 18792 } 18793 18794 return false; 18795 }, 18796 18797 // Do sorting on current set of thumbs 18798 18799 doSort = function(e, ready) { 18800 var el = this.selectedOptions[0], 18801 name = this.value; 18802 18803 if (!el || !name) { 18804 return true; 18805 } 18806 18807 var items = getAllItems(), 18808 data = $(el).data('sort'), 18809 type = data['type'], 18810 asc = data['ascending'], 18811 v1, 18812 v2; 18813 18814 if (type === 'numeric' || type === 'financial' || type === 'date') { 18815 // Number 18816 18817 if (name.endsWith('Date')) { 18818 // Date 18819 items = items.sort(function(i1, i2) { 18820 v1 = getDate(i1, name); 18821 v2 = getDate(i2, name); 18822 return asc? (v1 || 0) - (v2 || 0) 18823 : 18824 (v2 || 0) - (v1 || 0); 18825 }); 18826 18827 } else if (JCAMERAFIELDS.indexOf(name) !== -1) { 18828 // Camera data 18829 if (name === 'exposureTime') { 18830 items = items.sort(function(i1, i2) { 18831 v1 = (i1.hasOwnProperty(J.CAMERA) && i1[J.CAMERA].hasOwnProperty(name))? 18832 i1[J.CAMERA][name] : 0; 18833 v2 = (i2.hasOwnProperty(J.CAMERA) && i2[J.CAMERA].hasOwnProperty(name))? 18834 i2[J.CAMERA][name] : 0; 18835 return asc? (getExposure(v1 || 0) - getExposure(v2 || 0)) 18836 : 18837 (getExposure(v2 || 0) - getExposure(v1 || 0)); 18838 }); 18839 } else { 18840 items = items.sort(function(i1, i2) { 18841 v1 = (i1.hasOwnProperty(J.CAMERA) && i1[J.CAMERA].hasOwnProperty(name))? 18842 parseFloat(i1[J.CAMERA][name]) : 0; 18843 v2 = (i2.hasOwnProperty(J.CAMERA) && i2[J.CAMERA].hasOwnProperty(name))? 18844 parseFloat(i2[J.CAMERA][name]) : 0; 18845 return asc? ((v1 || 0) - (v2 || 0)) 18846 : 18847 ((v2 || 0) - (v1 || 0)); 18848 });
18849 } 18850 18851 } else if (name === 'rating') { 18852 // Rating 18853 var r = settings.visitorRating? J.VISITORRATING : J.RATING; 18854 items = items.sort(function(i1, i2) { 18855 return asc? (i1[r] || 0) - (i2[r] || 0) 18856 : 18857 (i2[r] || 0) - (i1[r] || 0); 18858 }); 18859 18860 } else { 18861 // Every other numeric 18862 items = items.sort(function(i1, i2) { 18863 if (i1.hasOwnProperty(name) && i2.hasOwnProperty(name)) { 18864 return asc? parseFloat(i1[name]) - parseFloat(i2[name]) 18865 : 18866 parseFloat(i2[name]) - parseFloat(i1[name]); 18867 } 18868 return asc? (i1.hasOwnProperty(name)? 1 : 0) 18869 : 18870 (i2.hasOwnProperty(name)? -1 : 0); 18871 }); 18872 } 18873 18874 } else { 18875 // String 18876 18877 items = items.sort(function(i1, i2) { 18878 if (i1.hasOwnProperty(name) && i2.hasOwnProperty(name)) { 18879 return asc? (i1[name] + '').localeCompare(i2[name] + '') 18880 : 18881 (i2[name] + '').localeCompare(i1[name] + ''); 18882 } 18883 return asc? (i1.hasOwnProperty(name)? 1 : 0) 18884 : 18885 (i2.hasOwnProperty(name)? -1 : 0); 18886 }); 18887 18888 } 18889 18890 var oly = prepareOverlay({ 18891 className: 'sorted-items', 18892 icon: 'icon-ordering', 18893 title: text.sortedBy + '<small>' + data.label + ' (' + (data.ascending? text.ascending : text.descending) + ')</small>' 18894 });
18895 18896 setTimeout(function() { 18897 18898 oly.on('overlayRemoved', resetBaseSort); 18899 18900 renderOverlay(oly, items, ready); 18901 18902 }, 50); 18903 18904 return false; 18905 }, 18906 18907 // Read filter ranges from current overlay 18908 18909 getFilterRanges = function(items, filter) { 18910 18911 if (!filter || !items.length) { 18912 return; 18913 } 18914 18915 var setMinMax = function(f, val) { 18916 if (f.hasOwnProperty('min') && f.hasOwnProperty('max')) { 18917 if (f.min > val) { 18918 f.min = val; 18919 } else if (f.max < val) { 18920 f.max = val; 18921 } 18922 } else { 18923 f.min = f.max = val; 18924 } 18925 }; 18926 18927 for (var name in filter) { 18928 18929 var f = filter[name], 18930 type = f['type']; 18931 18932 if (type === 'numeric' || type === 'date' || type === 'financial') { 18933 18934 if (name.endsWith('Date')) { 18935 // Date 18936 for (var i = items.length - 1, val; i >= 0; i--) { 18937 if ((val = getDate(items[i], name)) !== null) { 18938 setMinMax(f, val); 18939 } 18940 } 18941 } else if (JCAMERAFIELDS.indexOf(name) !== -1) { 18942 // Camera data fields 18943 if (name === 'exposureTime') { 18944 for (var i = items.length - 1, val; i >= 0; i--) { 18945 if (items[i].hasOwnProperty(J.CAMERA) && 18946 items[i][J.CAMERA].hasOwnProperty(name) && 18947 (val = getExposure(items[i][J.CAMERA][name]))) { 18948 setMinMax(f, val); 18949 } 18950 } 18951 } else if (name.startsWith('focalLength')) { 18952 for (var i = items.length - 1, val; i >= 0; i--) { 18953 if (items[i].hasOwnProperty(J.CAMERA) && 18954 items[i][J.CAMERA].hasOwnProperty(name) && 18955 !isNaN(val = parseFloat(items[i][J.CAMERA][name]))) { 18956 setMinMax(f, val); 18957 } 18958 } 18959 } else { 18960 for (var i = items.length - 1, val; i >= 0; i--) { 18961 if (items[i].hasOwnProperty(J.CAMERA) && 18962 items[i][J.CAMERA].hasOwnProperty(name) && 18963 !isNaN(val = parseFloat(items[i][J.CAMERA][name]))) { 18964 setMinMax(f, val); 18965 } 18966 } 18967 } 18968 } else if (name === 'rating') { 18969 // Rating 18970 for (var i = items.length - 1, val, r = settings.visitorRating? J.VISITORRATING : J.RATING; i >= 0; i--) { 18971 val = items[i][r] || 0; 18972 setMinMax(f, val); 18973 } 18974 } else { 18975 // Generic 18976 for (var i = items.length - 1, val; i >= 0; i--) { 18977 if (!isNaN(val = parseFloat(items[i][name]))) { 18978 setMinMax(f, val); 18979 } 18980 } 18981 } 18982 18983 } else { 18984 // String 18985 if (!f.hasOwnProperty('values')) { 18986 f.values = []; 18987 } 18988 18989 if (JCAMERAFIELDS.indexOf(name) !== -1) { 18990 // Camera data :: no arrays expected 18991 for (var i = 0, val; i < items.length; i++) { 18992 if (items[i].hasOwnProperty(J.CAMERA) && 18993 items[i][J.CAMERA].hasOwnProperty(name)) { 18994 pushNew(f.values, (items[i][J.CAMERA][name] + '').trim()); 18995 } 18996 } 18997 } else { 18998 for (var i = 0, val; i < items.length; i++) { 18999 if (items[i].hasOwnProperty(name)) { 19000 pushNew(f.values, items[i][name]); 19001 } 19002 } 19003 if (f.values.length > 1) { 19004 f.values = f.values.sort(function(a, b) { return a.localeCompare(b); }); 19005 } 19006 } 19007 } 19008 } 19009 19010 return filter; 19011 }, 19012 19013 // Enabling / disabling the reset button 19014 19015 enableResetBtn = function(e) { 19016 var form = (typeof e === UNDEF || e.hasOwnProperty('target'))? $(this).closest('form') : e; 19017 form.find('button[type=reset]').removeClass('disabled'); 19018 }, 19019 19020 disableResetBtn = function(e) { 19021 var form = (typeof e === UNDEF || e.hasOwnProperty('target'))? $(this).closest('form') : e; 19022 form.find('button[type=reset]').addClass('disabled'); 19023 }, 19024 19025 // Reseting filters to defaults 19026 19027 _resetFilters = function(form) { 19028 19029 form.find('select').each(function() { 19030 $(this).prop('selectedIndex', this['multiple']? -1 : 0); 19031 });
19032 19033 form.find('input[type=range]').each(function() { 19034 $(this).trigger('resetRange'); 19035 }); 19036 19037 disableResetBtn(form); 19038 19039 }, 19040 19041 // Reset filters after a form elemnt changed 19042 19043 resetFilters = function(e) { 19044 _resetFilters($(e.target).closest('form')); 19045 return false; 19046 }, 19047 19048 // Reseting base filters to defaults 19049 19050 resetBaseFilters = function() { 19051 resetBaseSort(); 19052 _resetFilters($('#filters form.filter')); 19053 }, 19054 19055 // Reseting base filters to defaults 19056 19057 resetBaseSort = function() { 19058 $('#filters form.sort select').prop('selectedIndex', 0); 19059 }, 19060 19061 // Preparing filters / sort 19062 19063 prepareFilterBox = function(baseOly) { 19064 19065 var items = getAllItems(), 19066 sbd = getOverlay().data('sbd'), 19067 filters = album.getPropertyObject(album.getCurrentFolder(), J.FILTERS, true), 19068 sort = album.getPropertyObject(album.getCurrentFolder(), J.SORT, true), 19069 filterSection = $('#filters'); 19070 19071 if ((!filters && !sort) || !items.length) { 19072 if (baseOly) { 19073 filterSection.hide(); 19074 } 19075 19076 return; 19077 } 19078 19079 var box = filterSection.find('.' + settings.boxClass), 19080 filterForm = box.find('form.filter'), 19081 sortForm = box.find('form.sort'), 19082 group, 19083 inp, 19084 lbl, 19085 type, 19086 buttons, 19087 btn; 19088 19089 if (!baseOly) { 19090 // Preparing filtering on overlay 19091 19092 box = $('<div>', { 19093 'class': settings.contClass + ' ' + settings.boxClass + ' small-column', 19094 id: 'filters_' + getOverlayNS() 19095 }).appendTo(filterSection); 19096 19097 box.append($('<h4>', { 19098 'class': 'icon-' + (filters? 'filter' : 'ordering'), 19099 text: filterSection.find('h4').text() 19100 })); 19101 19102 if (filters) { 19103 filterForm = $('<form>', { 19104 'class': 'filter' 19105 }).appendTo(box); 19106 } 19107 19108 if (sort) { 19109 sortForm = $('<form>', { 19110 'class': 'sort' 19111 }).appendTo(box); 19112 } 19113 19114 // Attaching box to current overlay 19115 attachToOverlay(box); 19116 19117 } else { 19118 19119 box.addClass(settings.hideOnOverlayClass); 19120 } 19121 19122 buttons = $('<div>', { 19123 'class': 'buttons' 19124 }).appendTo(filterForm); 19125 19126 if (filters) { 19127 19128 // Getting filter range 19129 filters = getFilterRanges(items, filters); 19130 19131 var m; 19132 19133 for (var f in filters) { 19134 19135 type = filters[f]['type']; 19136 19137 group = $('<div>', { 19138 'class': 'group ' + ((type === 'multiple_string' || type === 'single_string')? 'select' : 'range') 19139 }).insertBefore(buttons); 19140 19141 if (filters[f]['label']) { 19142 lbl = $('<label>', { 19143 'for': f, 19144 text: filters[f]['label'] 19145 }).appendTo(group); 19146 } 19147 19148 if (type === 'multiple_string' || type === 'single_string') { 19149 19150 if (f === 'rating') { 19151 filters[f].values = [ text.noRating, 1, 2, 3, 4, 5 ]; 19152 } 19153 19154 inp = $('<select>', { 19155 id: f, 19156 name: f 19157 }).appendTo(group); 19158 19159 if (type === 'multiple_string') { 19160 inp.attr('multiple', true); 19161 inp.addTooltip(text.multipleSelectHint, { delay: 600, stay: 3000 }); 19162 } 19163 19164 if (!filters[f].hasOwnProperty('values') || !filters[f].values.length) { // || filters[f].values.length < 2) { 19165 19166 group.remove(); 19167 19168 } else { 19169 19170 // Select hint if more than one option 19171 if (baseOly || type === 'single_string' && filters[f].values.length > 1) { 19172 inp.prepend($('<option>', { 19173 text: text.select, // + ' ' + filters[f]['label'], 19174 disabled: '', 19175 selected: '' 19176 })); 19177 } 19178 19179 // Adding options 19180 if (filters[f].values) { 19181 for (var i = 0; i < filters[f].values.length; i++) { 19182 inp.append($('<option>', { 19183 text: filters[f].values[i] 19184 })); 19185 } 19186 } 19187 19188 if (!baseOly) { 19189 19190 if (filters[f].values.length === 1) { 19191 // Disabling box if there's only one option 19192 inp.prop('disabled', true); 19193 inp.children().first().prop('selected', true); 19194 } else if (sbd && sbd.hasOwnProperty(f)) { 19195 // Preselect the previous filtering value(s) 19196 inp.children('option:enabled').each(function(i) { 19197 if (sbd[f].includes(filters[f].values[i])) { 19198 $(this).prop('selected', true); 19199 } 19200 });
19201 } 19202 } 19203 19204 inp.on('change', enableResetBtn); 19205 } 19206 19207 } else { 19208 19209 inp = $('<input>', { 19210 id: f, 19211 name: f, 19212 type: 'range' 19213 }) 19214 .data('type', type) 19215 .prependTo(group); 19216 19217 if (f === 'rating') { 19218 filters[f]['step'] = 1; 19219 filters[f]['min'] = 0; 19220 filters[f]['max'] = 5; 19221 } 19222 19223 if (!filters[f].hasOwnProperty('min') || !filters[f].hasOwnProperty('max') || filters[f]['min'] === filters[f]['max']) { 19224 19225 group.remove(); 19226 19227 } else { 19228 19229 if (type === 'financial' && (m = filters[f]['label'].match(/\S+\s+[\(\[](\S+)[\)\]]/))) { 19230 19231 filters[f]['currency'] = m[1]; 19232 19233 } else if (type === 'numeric') { 19234 19235 if (f === 'exposureTime' || f === 'shutterSpeed') { 19236 filters[f]['unit'] = 'seconds'; 19237 } else if (f === 'fileSize') { 19238 filters[f]['unit'] = 'byte'; 19239 filters[f]['step'] = 1; 19240 } 19241 } 19242 filters[f].onChanged = enableResetBtn; 19243 filters[f].onReseted = disableResetBtn; 19244 19245 inp.rangeSlider(filters[f]); 19246 } 19247 } 19248 19249 inp.attr('data-type', type || 'single_string'); 19250 } 19251 19252 // Adding buttons 19253 19254 if ((inp = filterForm.find('input,select')).length) { 19255 19256 filterForm.data('single', inp.length === 1 && inp[0].nodeName === 'SELECT' && !inp.attr('multiple')); 19257 19258 if (!(baseOly && filterForm.data('single'))) { 19259 // Reset button 19260 btn = $('<button>', { 19261 type: 'reset', 19262 'class': (baseOly? 'disabled secondary' : 'alert') + ' button', 19263 text: ' ' + text.reset 19264 }).appendTo(buttons); 19265 19266 if (baseOly) { 19267 btn.on('click.' + ns, resetFilters); 19268 } else { 19269 btn.on('click.' + ns, function() { 19270 resetBaseFilters(); 19271 removeOverlay(); 19272 }); 19273 } 19274 } 19275 19276 if (filterForm.data('single')) { 19277 // Single combo box -> Update instantly on change 19278 inp.on('change.' + ns, doFilter); 19279 19280 } else { 19281 // Multiple inputs or not single combo box -> Use 'Search' button 19282 btn = filterForm.find('button[type=submit]'); 19283 19284 if (!btn.length) { 19285 btn = $('<button>', { 19286 type: 'submit', 19287 'class': 'button', 19288 text: ' ' + text.search 19289 }).appendTo(buttons); 19290 } 19291 19292 btn.on('click.' + ns, doFilter); 19293 } 19294 19295 filterForm.on('submit.' + ns, doFilter); 19296 } 19297 } 19298 19299 if (buttons.is(':empty')) { 19300 buttons.remove(); 19301 } 19302 19303 if (sort) { 19304 19305 lbl = $('<label>', { 19306 'class': 'icon-ordering', 19307 'for': 'sortby', 19308 text: ' ' 19309 }).appendTo(sortForm); 19310 19311 inp = $('<select>', { 19312 'class': 'small', 19313 id: 'sortby', 19314 name: 'sortby' 19315 }).appendTo(sortForm); 19316 19317 inp.prepend($('<option>', { 19318 text: text.sortBy, 19319 disabled: '', 19320 selected: '' 19321 })); 19322 19323 for (var s in sort) { 19324 19325 inp.append($('<option>', { 19326 html: '↥ ' + sort[s].label, 19327 value: s 19328 }).data('sort', { 19329 type: sort[s].type, 19330 label: sort[s].label, 19331 ascending: true 19332 })); 19333 19334 inp.append($('<option>', { 19335 html: '↧ ' + sort[s].label, 19336 value: s 19337 }).data('sort', { 19338 type: sort[s].type, 19339 label: sort[s].label, 19340 ascending: false 19341 })); 19342 } 19343 19344 inp.on('change.' + ns, doSort); 19345 } 19346 19347 if ((!sortForm || !sortForm.length || sortForm.is(':empty')) && 19348 (!filterForm || !filterForm.length || filterForm.is(':empty'))) { 19349 filterSection.hide(); 19350 } 19351 }, 19352 19353 /******************************************************** 19354 * 19355 * Controls 19356 * 19357 ********************************************************/ 19358 19359 // Initializing ZIP download button 19360 19361 initZip = function() { 19362 19363 panels.find('.zip-cont a.button').each(function() { 19364 var btn = $(this); 19365 19366 getFileSize(btn.attr('href'), function(size) { 19367 if (size) { 19368 btn.append('<span class="badge">' + niceByte(size) + '</span>'); 19369 } 19370 });
19371 }); 19372 }, 19373 19374 /******************************************************** 19375 * 19376 * Initialization of misc functions 19377 * 19378 ********************************************************/ 19379 19380 // Right click blocking 19381 19382 avoidRightClick = function() { 19383 getImages().add(getFolders()).on('contextmenu', 'img', function(e) { 19384 e.preventDefault(); 19385 return false; 19386 }); 19387 }, 19388 19389 // Initializing background music 19390 19391 prepareBackgroundMusic = function(player) { 19392 19393 if (player.length) { 19394 player.audioPlayer({ 19395 rootPath: settings.rootPath 19396 }); 19397 } 19398 }, 19399 19400 // Updating the share buttons after a state change 19401 19402 updateShares = function() { 19403 19404 if (settings.hasOwnProperty('share')) { 19405 19406 if (lightboxOn()) { 19407 // Lightbox: sharing actual image 19408 var item = getCurrentItem(); 19409 19410 if (item) { 19411 $(settings.share.hook).trigger('updateLinks', { 19412 sites: settings.share, 19413 title: item[J.TITLE] || item[J.NAME].stripExt().replace(/[-_]/g, ' ').capitalize(), 19414 description: item[J.COMMENT] || '', 19415 image: album.getOptimalImagePath(item, [ 1200, 800 ]), 19416 href: album.getAbsolutePath(item) 19417 }); 19418 } 19419 } else { 19420 // Index page: reset to page sharing 19421 $(settings.share.hook).trigger('updateLinks', { href: window.location.href }); 19422 } 19423 } 19424 }, 19425 19426 // Initializing Social sharing 19427 19428 prepareShares = function(opt) { 19429 19430 if (typeof opt === UNDEF) return; 19431 19432 $.fn.renderShares.defaults.buttonTheme = opt['buttonTheme'] || 'dark'; 19433 $.fn.renderShares.defaults.indexName = settings.indexName; 19434 $.fn.renderShares.defaults.facebookAppId = opt['facebookAppId'] || ''; 19435 $.fn.renderShares.defaults.albumTitle = album.getAlbumTitle(); 19436 19437 if (opt.hasOwnProperty('emailSubject')) { 19438 $.fn.renderShares.defaults.emailSubject = opt.emailSubject; 19439 } 19440 19441 if (opt.hasOwnProperty('emailBody')) { 19442 $.fn.renderShares.defaults.emailBody = opt.emailBody; 19443 } 19444 19445 $(opt['hook']).renderShares(opt); 19446 19447 if (opt.hasOwnProperty('sites')) { 19448 $.fn.lightbox.defaults.share = opt.sites; 19449 $.fn.lightbox.defaults.shareHook = opt['hook']; 19450 } 19451 }, 19452 19453 // Reading out map locations 19454 19455 getMarkers = function(images) { 19456 // Reading out map locations 19457 var markers = []; 19458 19459 for (var i = 0, l; i < images.length; i++) { 19460 if (images[i] && (l = album.getLocation(images[i]))) { 19461 markers.push({ 19462 title: [((i + 1) + '.'), images[i]['title'], images[i]['comment']].filter(Boolean).join(' '), 19463 pos: l, 19464 link: images[i].path 19465 });
19466 } 19467 } 19468 19469 return markers; 19470 }, 19471 19472 // Preparing map 19473 19474 prepareMap = function(images, opt) { 19475 19476 if (images && images.length) { 19477 opt.markers = getMarkers(images); 19478 } 19479 19480 if (opt.hasOwnProperty('markers') && opt.markers.length || opt.hasOwnProperty('overlay')) { 19481 var mapRoot = getMapRoot(true); 19482 19483 if (mapRoot.length) { 19484 if (!mapRoot.data('inited')) { 19485 // Add map only once 19486 opt.resPath = settings.resPath; 19487 19488 opt.onMarkerClick = function() { 19489 if (this.hasOwnProperty('link')) { 19490 var el = getCardByName(this.link); 19491 19492 if (el.length) { 19493 if (isFullscreen()) { 19494 exitFullscreen(); 19495 } 19496 getOverlay().trigger('setactivecard', el); 19497 lightboxLoad(el); 19498 } 19499 } 19500 }; 19501 19502 if (opt.hasOwnProperty('apiKey')) { 19503 // Google Maps 19504 opt.streetViewControl = opt.typeControl = (window.innerWidth >= 640) && (window.innerHeight >= 640); 19505 } 19506 19507 // Unhide Map section 19508 mapRoot.closest('section').removeClass('hidden'); 19509 // Calling Map library 19510 mapRoot.addMap(opt); 19511 // Set inited flag 19512 mapRoot.data('inited', true); 19513 } 19514 19515 main.removeClass(settings.noMapClass).addClass(settings.hasMapClass); 19516 return; 19517 } 19518 } 19519 19520 main.removeClass(settings.hasMapClass).addClass(settings.noMapClass); 19521 }, 19522 19523 // Preparing shopping cart 19524 19525 prepareShoppingCart = function() { 19526 19527 // Initializing the cart 19528 if (shopRoot.length) { 19529 // Using the paypal cart's currency as global currency 19530 currency = album.getCurrency(); 19531 19532 // Set rangeslider's default currency 19533 if ($.fn.hasOwnProperty('rangeSlider') && currency) { 19534 $.fn.rangeSlider.defaults.currency = currency; 19535 } 19536 19537 // Calling Paypal module 19538 shopRoot.paypal(album, { 19539 resPath: settings.resPath, 19540 relPath: settings.relPath, 19541 rootPath: settings.rootPath, 19542 currency: currency, 19543 getSelected: getSelectedItems, 19544 selectNone: function() { 19545 getBaseOverlay().trigger('selectNone'); 19546 }, 19547 getCurrent: getCurrentItem, 19548 useFloatButton: !(shopRoot.parents('.shop-cont[data-sticky]').length || document.body.clientWidth >= 640) 19549 });
19550 19551 // "Add selected" button 19552 shopRoot.find('.' + settings.addSelectedClass).on('click', function() { 19553 shopRoot.trigger('addItems', getSelectedItems()); 19554 getBaseOverlay().trigger('selectNone'); 19555 return false; 19556 }); 19557 19558 // Ensuring lightbox has 'shop' property 19559 if (!$.fn.lightbox.defaults.hasOwnProperty('shop')) { 19560 $.fn.lightbox.defaults.shop = {}; 19561 } 19562 19563 // Setting lightbox's shop root variable 19564 $.fn.lightbox.defaults.shop.root = shopRoot; 19565 } 19566 19567 }, 19568 19569 // Preparing feedback 19570 19571 prepareFeedback = function() { 19572 19573 // Initializing the feedback cart 19574 if (feedbackRoot.length) { 19575 19576 // Calling Feedback module 19577 feedbackRoot.feedback(album, $.extend({}, settings.feedback, { 19578 resPath: settings.resPath, 19579 relPath: settings.relPath, 19580 rootPath: settings.rootPath, 19581 getSelected: getSelectedItems, 19582 selectNone: function() { 19583 getBaseOverlay().trigger('selectNone'); 19584 }, 19585 getCurrent: getCurrentItem, 19586 useFloatButton: !(feedbackRoot.parents('.feedback-cont[data-sticky]').length || document.body.clientWidth >= 640) 19587 })); 19588 19589 // "Add selected" button 19590 feedbackRoot.find('.' + settings.addSelectedClass).on('click', function() { 19591 feedbackRoot.trigger('addItems', getSelectedItems()); 19592 getBaseOverlay().trigger('selectNone'); 19593 return false; 19594 });
19595 19596 // Ensuring lightbox has 'feedback' property 19597 if (!$.fn.lightbox.defaults.hasOwnProperty('feedback')) { 19598 $.fn.lightbox.defaults.feedback = {}; 19599 } 19600 // Setting lightbox's feedback root variable 19601 $.fn.lightbox.defaults.feedback.root = feedbackRoot; 19602 $.fn.lightbox.defaults.feedbackBtnLabel = settings.feedback['viewBtnLabel']; 19603 $.fn.lightbox.defaults.feedbackAddBtnLabel = settings.feedback['addBtnLabel']; 19604 $.fn.lightbox.defaults.feedbackBtnTooltip = settings.feedback['addBtnTooltip']; 19605 } 19606 }, 19607 19608 /******************************************************** 19609 * 19610 * Index page initialization 19611 * 19612 ********************************************************/ 19613 19614 prepareIndex = function() { 19615 19616 if (!album) { 19617 return; 19618 } 19619 19620 var images = album.getImages(), 19621 folders = getFolders(), 19622 hasImages = images.length > 0, 19623 hasFolders = folders.length > 0, 19624 hasShop = $.fn.hasOwnProperty('paypal') && album.hasShop(), 19625 hasFeedback = settings.hasOwnProperty('feedback'); 19626 19627 // Setting has-images class and data flag 19628 main.data('baseHasImages', hasImages); 19629 toggleHasImages(hasImages); 19630 19631 if (hasImages) { 19632 // Removing thumbnail links right away 19633 getBaseImages().each(function() { 19634 this.dataset.href = this.href; 19635 this.href = ''; 19636 });
19637 } 19638 19639 // Shopping cart initialization 19640 if (hasShop) { 19641 shopRoot = $('#shop-root'); 19642 shopBox = $('#shop-cont,#shop-box').eq(0); 19643 prepareShoppingCart(); 19644 } else { 19645 shopRoot = $(); 19646 shopBox = $(); 19647 } 19648 19649 // Feedback initialization 19650 if (hasFeedback) { 19651 feedbackRoot = $('#feedback-root'); 19652 feedbackBox = $('#feedback'); 19653 prepareFeedback(); 19654 } else { 19655 feedbackRoot = $(); 19656 feedbackBox = $(); 19657 } 19658 19659 // Selection box initialization 19660 if (hasShop || hasFeedback) { 19661 // "Select all" button 19662 shopBox.add(feedbackBox).find('.' + settings.selectAllClass).on('click', function() { 19663 getOverlay().trigger('selectAll'); 19664 return false; 19665 }); 19666 19667 // "Select none" button 19668 shopBox.add(feedbackBox).find('.' + settings.selectNoneClass).on('click', function() { 19669 getOverlay().trigger('selectNone'); 19670 return false; 19671 }); 19672 } 19673 19674 // Initializing folders 19675 if (hasFolders) { 19676 prepareFolders(folders, lazyloadFolders); 19677 } 19678 19679 // Rendering thumbnails 19680 if (hasImages) { 19681 // Preparing images on the base overlay 19682 renderImages(getBaseOverlay(), images, prepareImages); 19683 // Start slideshow wiring 19684 $(settings.startSlideshowHook).on('click', startSlideshow); 19685 19686 } else { 19687 // no images, check if inited by hash 19688 if (window.location.hash) { 19689 // we should check the search / tags / newimages filter 19690 stateChange(); 19691 } 19692 // Hide Start sliodeshow button 19693 $(settings.startSlideshowHook).hide(); 19694 } 19695 19696 // Collecting tags if gathered only from this folder 19697 if (settings.hasOwnProperty('tagCloud') && settings.tagCloud['depth'] === 'current') { 19698 album.collectTags($.extend(settings.tagCloud, { 19699 ready: function() { 19700 prepareTagCloud(this); 19701 } 19702 })); 19703 } 19704 19705 // Search 19706 if (settings.hasOwnProperty('search')) { 19707 prepareSearch($(settings.search.hook)); 19708 } 19709 19710 // Search new 19711 if (settings.hasOwnProperty('searchNew')) { 19712 prepareSearchNew($(settings.searchNew.hook)); 19713 } 19714 19715 // Preparing share buttons 19716 if (settings.hasOwnProperty('share')) { 19717 prepareShares(settings.share); 19718 } 19719 19720 var slider = $('[data-slider]'); 19721 19722 if (slider.length) { 19723 var sls = slider.data('slider') || {}, 19724 dim = [ slider.innerWidth(), slider.innerHeight() ]; 19725 19726 if (sls.hasOwnProperty('names')) { 19727 19728 for (var i = 0; i < sls.names.length; i++) { 19729 sls.names[i] = [ settings.relPath, sls.names[i] ].filter(Boolean).join('/'); 19730 } 19731 19732 // Selected images 19733 album.collectByPath({ 19734 folder: settings.relPath, 19735 paths: sls.names, 19736 levels: 0, 19737 ready: function() { 19738 sls.src = []; 19739 for (var i = 0; i < this.length; i++) { 19740 sls.src.push(album.getOptimalImagePath(this[i], dim, true, true)); 19741 } 19742 slider.easySlider(sls); 19743 } 19744 }); 19745 19746 } else { 19747 19748 album.collectNItem({ 19749 folder: settings.relPath, 19750 max: 50, 19751 quick: true, 19752 levels: 2, 19753 include: 'images', 19754 sortBy: 'random', 19755 arRange: [ Math.min(1, 0.5 * dim[0] / dim[1]), Math.max(1, 2 * dim[0] / dim[1]) ], 19756 ready: function() { 19757 sls.src = []; 19758 for (var i = 0; i < this.length; i++) { 19759 sls.src.push(album.getOptimalImagePath(this[i], dim, true, true)); 19760 } 19761 slider.easySlider(sls); 19762 } 19763 }); 19764 } 19765 } 19766 19767 // Adding map on embedded custom pages 19768 var customMap = $('.pages-cont [data-map]'); 19769 19770 if (customMap.length && settings.hasOwnProperty('map')) { 19771 settings.map.resPath = settings.resPath; 19772 customMap.addMap($.extend({}, settings.map, customMap.data('map'))); 19773 } 19774 19775 // Custom query (eg. new images sample box) 19776 var queryTarget = $('[data-jalbum-query]'); 19777 19778 if (queryTarget.length) { 19779 // Reding settings from data attribute 19780 var qsettings = queryTarget.data('jalbum-query'); 19781 19782 if (qsettings.hasOwnProperty('fn')) { 19783 // Calling the jalbum.album function 19784 album[qsettings.fn]($.extend(qsettings, { 19785 ready: function(opt) { 19786 if (this.length) { 19787 19788 for (var i = 0, a, item, mx = Math.min(this.length, 10); i < mx; i++) { 19789 19790 item = this[i]; 19791 19792 a = $('<a>', { 19793 href: album.getLink(item) 19794 }).appendTo(queryTarget); 19795 19796 a.append($('<img>', { 19797 src: album.getThumbPath(item),/* 19798 width: item[J.THUMB][J.WIDTH], 19799 height: item[J.THUMB][J.HEIGHT],*/ 19800 alt: item[J.TITLE] || item[J.NAME] 19801 })); 19802 } 19803 19804 if (this.length > 10) { 19805 queryTarget.append($('<a>', { 19806 'class': 'more', 19807 href: qsettings.linkMore, 19808 text: '+' + (this.length - 10) 19809 })); 19810 } 19811 19812 if (settings.rightClickProtect) { 19813 queryTarget.on('contextmenu', 'img', function(e) { 19814 e.preventDefault(); 19815 return false; 19816 });
19817 } 19818 } else { 19819 queryTarget.closest('.newimages-page').hide(); 19820 } 19821 } 19822 19823 })); 19824 } 19825 } 19826 }, 19827 19828 /******************************************************** 19829 * 19830 * Custom page initialization 19831 * 19832 ********************************************************/ 19833 19834 preparePage = function() { 19835 // Page :: initializing functions 19836 // Search 19837 if (settings.hasOwnProperty('search')) { 19838 prepareSearch($(settings.search.hook)); 19839 } 19840 19841 // Preparing share buttons 19842 if (settings.hasOwnProperty('share')) { 19843 prepareShares(settings.share); 19844 } 19845 19846 settings.pageName = window.location.pathname.getFile(); 19847 19848 var paths = []; 19849 19850 getCards().each(function() { 19851 var a = $(this).find('.' + settings.thumbClass); 19852 a.data('href', a.attr('href')); 19853 a.attr('href', ''); 19854 // Collecting paths from thumbnails 19855 paths.push($(this).data(J.PATH)); 19856 }); 19857 19858 if (paths.length) { 19859 19860 setOverlayNS(); 19861 19862 album.collectByPath({ 19863 paths: paths, 19864 ready: function() { 19865 // Rendering thumbnails 19866 if (this.length) { 19867 19868 // Setting has-images class 19869 main.data('baseHasImages', true); 19870 toggleHasImages(true); 19871 19872 // Preparing images on the base overlay 19873 renderImages(getBaseOverlay(), this, prepareImages); 19874 19875 } else { 19876 // Setting has-images class 19877 toggleHasImages(false); 19878 } 19879 } 19880 });
19881 } else { 19882 var map = $('[data-map]'); 19883 19884 if (map.length) { 19885 prepareMap(null, $.extend({}, settings.map, map.data('map'))); 19886 } 19887 // Setting has-images class 19888 main.data('baseHasImages', false); 19889 toggleHasImages(false); 19890 } 19891 }, 19892 19893 /******************************************************** 19894 * 19895 * Preparing functions that need the full album tree 19896 * 19897 ********************************************************/ 19898 19899 prepareDeepFunctions = function() { 19900 19901 // Collecting tags if gathered from deep tree 19902 if (settings.hasOwnProperty('tagCloud') && settings.tagCloud['depth'] !== 'current') { 19903 album.collectTags($.extend({}, settings.tagCloud, { 19904 ready: function() { 19905 prepareTagCloud(this); 19906 } 19907 })); 19908 } 19909 }, 19910 19911 /******************************************************** 19912 * 19913 * Preparing folders 19914 * 19915 ********************************************************/ 19916 19917 // Marking new folders 19918 19919 markFoldersNew = function() { 19920 19921 if (!album) { 19922 return; 19923 } 19924 19925 var folders = album.getFoldersAndWeblocs(), 19926 secs = settings.markNew['days'] * ONEDAY_S, 19927 s, 19928 d; 19929 19930 getFolders().each(function(i) { 19931 19932 if (i < folders.length) { 19933 d = folders[i][J.DATES]; 19934 19935 if (d) { 19936 d = d[settings.markNew['reference']] || (d[J.DATERANGE] && (d[J.DATERANGE][1] || d[J.DATERANGE][0])); 19937 19938 if (d && (nows - d) < secs) { 19939 s = settings.markNew.text? 19940 $('<span>', { 19941 'class': 'new-image', 19942 text: settings.markNew.text 19943 }) 19944 : 19945 $('<span>', { 19946 'class': 'icon-new-fill new-image' 19947 }); 19948 19949 if (!TOUCHENABLED) { 19950 s.data('tooltip', (new Date(d * 1000)).toLocaleDateString(LOCALE)).addTooltip({ delay: 100 }); 19951 } 19952 19953 $(this).find('.' + settings.thumbClass).eq(0).append(s); 19954 } 19955 } 19956 } 19957 }); 19958 }, 19959 19960 // Loading visible folder thumbs :: only on base overlay 19961 19962 lazyloadFoldersTimeout = null, // Lazyload tracking 19963 lazyloadFoldersInProgress = false, // lock to avoid multiple calls 19964 19965 lazyloadFolders = function() { 19966 19967 if (lazyloadFoldersInProgress || getLightbox().is(':visible') || hasOverlay()) { 19968 clearTimeout(lazyloadFoldersTimeout); 19969 return; 19970 } 19971 19972 if (lazyloadFoldersTimeout) { 19973 clearTimeout(lazyloadFoldersTimeout); 19974 lazyloadFoldersTimeout = null; 19975 } 19976 19977 var st = $window.scrollTop(); 19978 19979 if (Math.abs(st - scrollTop) > 20) { 19980 // Scrolling in progress: wait until finishes 19981 scrollTop = st; 19982 lazyloadFoldersTimeout = setTimeout(lazyloadFolders, settings.scrollCheckInterval); 19983 return; 19984 } 19985 19986 // Getting folders to load 19987 var folders = getFolders('.' + settings.lazyloadClass); 19988 19989 if (folders.length) { 19990 var wh = $window.height(); 19991 19992 lazyloadFoldersInProgress = true; 19993 19994 folders.each(function() { 19995 var card = $(this), 19996 tt = card.offset().top, 19997 th = card.height(); 19998 19999 if ((tt + th) > (scrollTop - wh)) { 20000 // Below 1 screens above scroll pos 20001 if (tt < (scrollTop + wh)) { 20002 loadFolderThumb(card); 20003 } else { 20004 // Far below: stop loading more 20005 return false; 20006 } 20007 } 20008 });
20009 20010 // perpetrating lazyload 20011 clearTimeout(lazyloadFoldersTimeout); 20012 lazyloadFoldersTimeout = setTimeout(lazyloadFolders, settings.scrollCheckInterval); 20013 20014 } else { 20015 20016 $window.off('.lazy'); 20017 } 20018 20019 lazyloadFoldersInProgress = false; 20020 scrollTop = st; 20021 }, 20022 20023 // Reading counts from a <span class="image">N images</span><span class="audio">N audios</span><span class="video">N videos</span> 20024 20025 readCounts = function(el) { 20026 var cnt = { 20027 images: 0, 20028 audios: 0, 20029 videos: 0, 20030 others: 0 20031 }; 20032 20033 el.children().each(function(i) { 20034 var c = this.innerText.split(/\W/), 20035 t = this.classList[0]; 20036 20037 if (!t && c.length > 1) { 20038 switch (c[1]) { 20039 case text.image: 20040 case text.images: 20041 t = 'images'; 20042 break; 20043 case text.audio: 20044 case text.audios: 20045 t = 'audios'; 20046 break; 20047 case text.video: 20048 case text.videos: 20049 t = 'videos'; 20050 break; 20051 case text.other: 20052 case text.others: 20053 t = 'others'; 20054 break; 20055 default: 20056 t = 'images'; 20057 } 20058 } 20059 20060 cnt[t] += parseInt(c[0]) || 0; 20061 }); 20062 20063 return cnt; 20064 }, 20065 20066 // Calculates the difference between two count objects 20067 20068 getCountDiff = function(c1, c2) { 20069 var diff = { 20070 images: 0, 20071 audios: 0, 20072 videos: 0, 20073 others: 0 20074 }; 20075 20076 for (var c in c1) { 20077 if (c2.hasOwnProperty(c) && c1[c] !== c2[c]) { 20078 diff[c] = c1[c] - c2[c]; 20079 } 20080 } 20081 20082 return diff; 20083 }, 20084 20085 // CHeck if any difference was found 20086 20087 allZero = function(diff) { 20088 20089 for (var c in diff) { 20090 if (diff[c] !== 0) { 20091 return false; 20092 } 20093 } 20094 20095 return true; 20096 }, 20097 20098 // Creates HTML formatted counts: <span class="image">N images</span><span class="audio">N audios</span><span class="video">N videos</span> 20099 20100 getFormattedCounts = function(cnt) { 20101 var c = ''; 20102 20103 if (cnt['images']) { 20104 c += '<span class="image">' + cnt['images'] + ' ' + translate(cnt['images'] > 1? 'images':'image') + '</span>'; 20105 } 20106 if (cnt['audios']) { 20107 c += '<span class="audio">' + cnt['audios'] + ' ' + translate(cnt['audios'] > 1? 'audios':'audio') + '</span>'; 20108 } 20109 if (cnt['videos']) { 20110 c += '<span class="video">' + cnt['videos'] + ' ' + translate(cnt['videos'] > 1? 'videos':'video') + '</span>'; 20111 } 20112 if (cnt['others']) { 20113 c += '<span class="other">' + cnt['others'] + ' ' + translate(cnt['others'] > 1? 'others':'other') + '</span>'; 20114 } 20115 20116 return c; 20117 }, 20118 20119 // Modifying the total 20120 20121 fixTotalImages = function(diff) { 20122 var el = $('.footer .counts'), 20123 cnt = readCounts(el); 20124 20125 for (var c in cnt) { 20126 if (diff[c] !== 0) { 20127 cnt[c] += diff[c]; 20128 } 20129 } 20130 20131 //console.log('Diff: ' + JSON.stringify(diff) + ' cnt: ' + JSON.stringify(cnt)); 20132 20133 el.empty().append(getFormattedCounts(cnt)); 20134 }, 20135 20136 // Preparing folders 20137 20138 prepareFolders = function(folders, doneFn) { 20139 20140 if (typeof folders === UNDEF || !folders.length) { 20141 return; 20142 } 20143 20144 if (settings.hasOwnProperty('markNew')) { 20145 // Marking new folders 20146 setTimeout(markFoldersNew, 300); 20147 } 20148 20149 setTimeout(function() { 20150 // Start lazy load 20151 //lazyloadFolders(); 20152 20153 /* 20154 // Removing prelaod class on folders already loaded 20155 folders.filter('.' + settings.preloadClass + ':first-child img') 20156 .one('load', function() { 20157 $(this).parents('.' + settings.preloadClass).removeClass(settings.preloadClass); 20158 });
20159 */ 20160 if ((!LOCAL || DEBUG) && settings.hasOwnProperty('weblocationCounts') && settings.weblocationCounts) { 20161 // Adding counts to weblocations 20162 folders.filter('.webloc.has-count').each(function() { 20163 var caption = $(this).children('.caption'), // Tha caption 20164 counts = readCounts(caption.find('.counts')), // Saving original counts 20165 diff; 20166 20167 loadExternal($(this).children('.thumb').attr('href'), function() { 20168 20169 if (typeof this === OBJECT && this.hasOwnProperty(J.DEEPCOUNTERS)) { 20170 diff = getCountDiff(this[J.DEEPCOUNTERS], counts); 20171 20172 if (!allZero(diff)) { 20173 20174 caption.find('.counts') 20175 .empty() 20176 .append(getFormattedCounts(this[J.DEEPCOUNTERS])); 20177 20178 fixTotalImages(diff); 20179 } 20180 } 20181 }); 20182 }); 20183 } 20184 20185 folders.find('.mosaic').each(function() { 20186 var th = $(this).siblings('.thumb'); 20187 20188 $(this).children().each(function(i) { 20189 var src = $(this).data('imgsrc') || $(this).children('img').eq(0).attr('src') || $(this).data('src'); 20190 20191 if (src) { 20192 var mi = $('<img>', { 20193 'class': 'mosimg hide-image', 20194 src: src.replace('/thumbs/', '/slides/'), 20195 id: 'mi' + i 20196 }).appendTo(th); 20197 20198 $(this).on('mouseenter', function(e) { 20199 setTimeout(function() { 20200 mi.removeClass('hide-image').addClass('show-image'); 20201 }, 100); 20202 }).on('mouseleave', function(e) { 20203 setTimeout(function() { 20204 mi.addClass('hide-image').removeClass('show-image'); 20205 }, 200); 20206 }); 20207 } 20208 }); 20209 }); 20210 20211 }, 500); 20212 20213 if (typeof doneFn === FUNCTION) { 20214 doneFn.call(self[0]); 20215 } 20216 }, 20217 20218 /******************************************************** 20219 * 20220 * Initializing album 20221 * 20222 ********************************************************/ 20223 20224 initAlbum = function() { 20225 20226 if (typeof Album === UNDEF) { 20227 console.log('Critical Error: Missing jalbum.album.js library!'); 20228 return; 20229 } 20230 20231 var needsDeep = settings.hasOwnProperty('tagCloud') && settings.tagCloud['depth'] !== 'current' || 20232 settings.pageType === 'page' && settings.originalPage === 'newimages', 20233 params = { 20234 makeDate: settings['makeDate'], 20235 rootPath: settings['rootPath'], 20236 relPath: settings['relPath'], 20237 hiDpiImages: settings['hiDpiImages'] || false, 20238 hiDpiThumbs: settings['hiDpiThumbs'] || false, 20239 folderImageFile: settings.folderImageFileName, 20240 folderThumbFile: settings.folderThumbFileName, 20241 thumbDims: settings['thumbDims'] || [ 418, 209 ], 20242 thumbsFillFrame: settings['thumbsFill'] || false, 20243 ready: (settings.pageType === 'index')? prepareIndex : preparePage, 20244 loadDeep: needsDeep, 20245 fatalError: function(txt, attr) { 20246 $body.modal((typeof attr === UNDEF)? translate(txt) : translate(txt).replace(/\{0\}/g, attr), { 20247 title: text.fatalError, 20248 icon: 'error', 20249 'class': 'small alert' 20250 }); 20251 }, 20252 locale: settings['locale'] || 'en-US' 20253 }; 20254 20255 if (settings.indexName !== $.fn.skin.defaults.indexName) { 20256 params.indexName = settings['indexName']; 20257 } 20258 20259 if (settings.audioPoster !== $.fn.skin.defaults.audioPoster) { 20260 params.audioPoster = settings['audioPoster']; 20261 } 20262 20263 if (settings.videoPoster !== $.fn.skin.defaults.videoPoster) { 20264 params.videoPoster = settings['videoPoster']; 20265 } 20266 20267 if (settings.useOriginals) { 20268 params.useOriginals = true; 20269 } 20270 20271 if (needsDeep) { 20272 params.deepReady = prepareDeepFunctions; 20273 } 20274 20275 album = new Album($, params); 20276 }, 20277 20278 // Main initializing function 20279 20280 init = function() { 20281 20282 // Removing search string, e.g. FB data 20283 if (window.location.search) { 20284 removeSearch(); 20285 } 20286 20287 // Restoring scroll position, saving on unload 20288 restoreScrollPosition(); 20289 $(window).on('beforeunload.' + ns, saveScrollPosition); 20290 20291 loadPrefs(settings, [ 'audioClipVolume' ], 'lb_pref'); 20292 20293 // Passing along lightbox defaults 20294 passDefaults(settings, $.fn.lightbox.defaults, 'indexName,level,resPath,useOriginals,previousFoldersLast,previousFolderPath,nextFoldersFirst,nextFolderPath,extraSizes,hiDpiThumbs,hiDpiImages,audioClipVolume,lightboxFullscreen,lightboxFullscreenOnlyMobiles,slideshowFullscreen,rightClickProtect,enableKeyboard,mouseWheelAction,fotomoto,useRating,visitorRating,lbableClass'); 20295 passDefaults(settings.lightbox, $.fn.lightbox.defaults); 20296 20297 // Creating new Album instance 20298 initAlbum(); 20299 20300 // Right click protection on folders, slider 20301 if (settings.rightClickProtect) { 20302 avoidRightClick(); 20303 } 20304
20305 // Background music 20306 prepareBackgroundMusic($('[data-audioPlayer]')); 20307 20308 // Hash change listener 20309 $window.on('hashchange.' + ns, stateChange); 20310 20311 // Focusing overlay 20312 main.on('overlayReady.' + ns, function(e, o) { 20313 moveInView(o); 20314 }); 20315 20316 // Menu button wiring (small screens) 20317 $('[data-topnav-toggle]').on({ 20318 'click': function() { 20319 $body.toggleClass('menu-on'); 20320 return false; 20321 }, 20322 'selectstart': function(e) { 20323 e.preventDefault(); 20324 return false; 20325 } 20326 }); 20327 20328 // Scroll to top btn 20329 if (settings.scrollToTopButton) { 20330 $body.scrollToTopBtn(); 20331 } 20332 20333 if (hasFullscreen()) { 20334 20335 // Fullscreen change listener 20336 onFullscreenChange(function(on) { 20337 $body.toggleClass(settings.fullscreenClass, on); 20338 }); 20339 20340 $('.hero .controls .gofullscreen').on('click', requestFullscreen); 20341 $('.hero .controls .exitfullscreen').on('click', exitFullscreen); 20342 20343 if (window.innerWidth < 640) { 20344 // Change to full screen on mobiles on orientation change 20345 goFullscreenOnLandscapeChange(); 20346 } 20347 } 20348 }; 20349 20350 20351 /******************************************************** 20352 * 20353 * Execution starts here 20354 * 20355 ********************************************************/ 20356 20357 init(); 20358 20359 // Utility function for debugging 20360 this.getAlbum = function() { 20361 return album; 20362 }; 20363 20364 return this; 20365 }; 20366 20367 /******************************************************** 20368 * 20369 * Defaults 20370 * 20371 ********************************************************/ 20372 20373 $.fn.skin.defaults = { 20374 locale: 'en-US', 20375 contentClass: 'content', 20376 contClass: 'cont', 20377 startSlideshowHook: '.hero .start-show', 20378 hasOverlayClass: 'has-overlay', 20379 baseOverlayClass: 'base-overlay', 20380 hideOnOverlayClass: 'hide-on-overlay', 20381 overlayClass: 'overlay', 20382 overlayTitleClass: 'title', 20383 overlayHeadLevel: 3, 20384 hasImagesClass: 'has-images', 20385 noImagesClass: 'no-images', 20386 hasMapClass: 'has-map', 20387 noMapClass: 'no-map', 20388 hasShopClass: 'has-shop', 20389 noShopClass: 'no-shop', 20390 shoppableClass: 'shoppable', 20391 progressClass: 'progress', 20392 progressbarClass: 'progressbar', 20393 maxResults: 1000, 20394 lightboxClass: 'lightbox', 20395 sliderClass: 'slider', 20396 belowfoldClass: 'below-fold', 20397 foldersClass: 'folders', 20398 folderClass: 'folder', 20399 thumbnailsClass: 'thumbnails', 20400 thumbClass: 'thumb', 20401 boxClass: 'box', 20402 mapBoxHook: 'section.map,.map-cont', 20403 mapRootClass: 'map-root', 20404 tagCloudClass: 'tag-cloud', 20405 tagSearchClass: 'tag-search', 20406 thumbLayout: 'fixgrid', 20407 thumbsFill: true, 20408 useAudioClipButton: false, 20409 columns: 5, 20410 thumbsPerBatch: 20, 20411 thumbDims: [ 418, 209 ], 20412 thumbGap: 6, 20413 fixedShapeThumbs: false, 20414 justifyWrapThreshold: 0.333333, 20415 imageClass: 'image', 20416 captionClasses: 'over bottom hover', 20417 cardClass: 'card', 20418 iconThumbClass: 'iconthumb', 20419 placeholderClass: 'placeholder', 20420 folderCardClass: 'foldercard', 20421 mosaicClass: 'mosaic', 20422 captionPlacement: 'below', 20423 lbableClass: 'lbable', 20424 captionClass: 'caption', 20425 commentClass: 'comment', 20426 hascaptionClass: 'hascaption', 20427 selectableClass: 'selectable', 20428 selectedClass: 'checked', 20429 activeClass: 'active', 20430 preloadClass: 'preload', 20431 lazyloadClass: 'lazyload', 20432 startshowClass: 'start-btn', 20433 fullscreenClass: 'fullscreen-on', 20434 panoClass: 'pano', 20435 selectBoxClass: 'select-box', 20436 selectAllClass: 'select-all', 20437 selectNoneClass: 'select-none', 20438 keepSelectedClass: 'keep-selected', 20439 addSelectedClass: 'add-selected', 20440 scrollCheckInterval: 120, 20441 indexName: 'index.html',
20442 folderImageFileName: 'folderimage.jpg', 20443 folderThumbFileName: 'folderthumb.jpg', 20444 audioPoster: 'audio.poster.png', 20445 videoPoster: 'video.poster.png', 20446 dateFormat: 'M/d/yyyy', 20447 audioClipVolume: 0.75, 20448 lightboxFullscreen: false, 20449 lightboxFullscreenOnlyMobiles: false, 20450 slideshowFullscreen: false, 20451 rightClickProtect: false, 20452 useRating: false, 20453 jalbumRating: false, 20454 visitorRating: false, 20455 showEmptyFilters: false, 20456 enableKeyboard: true, 20457 videoAuto: true, 20458 linkToFolder: true, 20459 useOriginals: false, 20460 scrollToTopButton: true 20461 }; 20462 20463})(jQuery, jQuery(window), jQuery(document));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.