vendor: 6,071 bytes, lines 1-143
1/*! 2 * Lightbox v2.11.4 3 * by Lokesh Dhakar 4 * 5 * More info: 6 * http://lokeshdhakar.com/projects/lightbox2/ 7 * 8 * Copyright Lokesh Dhakar 9 * Released under the MIT license 10 * https://github.com/lokesh/lightbox2/blob/master/LICENSE 11 * 12 * @preserve 13 */ 14 15// Uses Node, AMD or browser globals to create a module. 16(function (root, factory) { 17 if (typeof define === 'function' && define.amd) { 18 // AMD. Register as an anonymous module. 19 define(['jquery'], factory); 20 } else if (typeof exports === 'object') { 21 // Node. Does not work with strict CommonJS, but 22 // only CommonJS-like environments that support module.exports, 23 // like Node. 24 module.exports = factory(require('jquery')); 25 } else { 26 // Browser globals (root is window) 27 root.lightbox = factory(root.jQuery); 28 } 29}(this, function ($) { 30 31 function Lightbox(options) { 32 this.album = []; 33 this.currentImageIndex = void 0; 34 this.openingElement = null; 35 this.init(); 36 37 // options 38 this.options = $.extend({}, this.constructor.defaults); 39 this.option(options); 40 } 41 42 // Descriptions of all options available on the demo site: 43 // http://lokeshdhakar.com/projects/lightbox2/index.html#options 44 Lightbox.defaults = { 45 albumLabel: 'Image %1 of %2', 46 alwaysShowNavOnTouchDevices: false, 47 fadeDuration: 600, 48 fitImagesInViewport: true, 49 imageFadeDuration: 600, 50 // maxWidth: 800, 51 // maxHeight: 600, 52 positionFromTop: 50, 53 resizeDuration: 700, 54 showImageNumberLabel: true, 55 wrapAround: false, 56 disableScrolling: false, 57 /* 58 Sanitize Title 59 If the caption data is trusted, for example you are hardcoding it in, then leave this to false. 60 This will free you to add html tags, such as links, in the caption. 61 62 If the caption data is user submitted or from some other untrusted source, then set this to true 63 to prevent xss and other injection attacks. 64 */ 65 sanitizeTitle: false 66 }; 67 68 Lightbox.prototype.option = function(options) { 69 $.extend(this.options, options); 70 }; 71 72 Lightbox.prototype.imageCountLabel = function(currentImageNum, totalImages) { 73 return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages); 74 }; 75 76 Lightbox.prototype.init = function() { 77 var self = this; 78 // Both enable and build methods require the body tag to be in the DOM. 79 $(document).ready(function() { 80 self.enable(); 81 self.build(); 82 }); 83 }; 84 85 // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes 86 // that contain 'lightbox'. When these are clicked, start lightbox. 87 Lightbox.prototype.enable = function() { 88 var self = this; 89 $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function(event) { 90 self.start($(event.currentTarget)); 91 return false; 92 }); 93 }; 94 95 // Build html for the lightbox and the overlay. 96 // Attach event handlers to the new DOM elements. click click click 97 Lightbox.prototype.build = function() { 98 if ($('#lightbox').length > 0) { 99 return; 100 } 101 102 var self = this; 103 104 // The two root notes generated, #lightboxOverlay and #lightbox are given 105 // tabindex attrs so they are focusable. We attach our keyboard event 106 // listeners to these two elements, and not the document. Clicking anywhere 107 // while Lightbox is opened will keep the focus on or inside one of these 108 // two elements. 109 // 110 // We do this so we can prevent propogation of the Esc keypress when 111 // Lightbox is open. This prevents it from intefering with other components 112 // on the page below. 113 // 114 // Github issue: https://github.com/lokesh/lightbox2/issues/663 115 $('<div id="lightboxOverlay" tabindex="-1" class="lightboxOverlay"></div><div id="lightbox" tabindex="-1" class="lightbox"><div class="lb-instContainer"><span class="lb-inst-hilite">Esc</span> to exit; <span class="lb-inst-hilite">Left</span> and <span class="lb-inst-hilite">Right</span> arrows to navigate.</div><div class="lb-outerContainer"><div class="lb-container"><img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt=""/><div class="lb-nav"><a class="lb-prev" aria-label="Previous image" href="javascript:" ></a><a class="lb-next" aria-label="Next image" href="javascript:" ></a></div><div class="lb-loader"><a class="lb-cancel" rel="nofollow" href="javascript:void(0)" role="button" aria-label="Cancel"></a></div></div></div><div class="lb-dataContainer"><div class="lb-data"><div class="lb-details"><span class="lb-caption"></span><span class="lb-description"></span><span class="lb-number"></span></div><div class="lb-closeContainer"><a class="lb-close" rel="nofollow" href="javascript:void(0)" role="button" aria-label="Close"></a></div></div></div></div>').appendTo($('body')); 116 117 // Cache jQuery objects 118 this.$lightbox = $('#lightbox'); 119 this.$overlay = $('#lightboxOverlay'); 120 this.$outerContainer = this.$lightbox.find('.lb-outerContainer'); 121 this.$container = this.$lightbox.find('.lb-container'); 122 this.$image = this.$lightbox.find('.lb-image'); 123 this.$nav = this.$lightbox.find('.lb-nav'); 124 125 // Store css values for future lookup 126 this.containerPadding = { 127 top: parseInt(this.$container.css('padding-top'), 10), 128 right: parseInt(this.$container.css('padding-right'), 10), 129 bottom: parseInt(this.$container.css('padding-bottom'), 10), 130 left: parseInt(this.$container.css('padding-left'), 10) 131 }; 132 133 this.imageBorderWidth = { 134 top: parseInt(this.$image.css('border-top-width'), 10), 135 right: parseInt(this.$image.css('border-right-width'), 10), 136 bottom: parseInt(this.$image.css('border-bottom-width'), 10), 137 left: parseInt(this.$image.css('border-left-width'), 10) 138 }; 139 140 // Attach event handlers to the newly minted DOM elements 141 this.$overlay.hide().on('click', function() { 142 self.end(); 143 return false;
144 }); 145 146 this.$lightbox.hide().on('click', function(event) { 147 if ($(event.target).attr('id') === 'lightbox') { 148 self.end(); 149 } 150 }); 151 152 this.$outerContainer.on('click', function(event) { 153 if ($(event.target).attr('id') === 'lightbox') { 154 self.end(); 155 } 156 return false; 157 }); 158 159 this.$lightbox.find('.lb-prev').on('click', function() { 160 if (self.currentImageIndex === 0) { 161 self.changeImage(self.album.length - 1); 162 } else { 163 self.changeImage(self.currentImageIndex - 1); 164 } 165 return false; 166 }); 167 168 this.$lightbox.find('.lb-next').on('click', function() { 169 if (self.currentImageIndex === self.album.length - 1) { 170 self.changeImage(0); 171 } else { 172 self.changeImage(self.currentImageIndex + 1); 173 } 174 return false; 175 }); 176 177 /* 178 Show context menu for image on right-click 179 180 There is a div containing the navigation that spans the entire image and lives above of it. If 181 you right-click, you are right clicking this div and not the image. This prevents users from 182 saving the image or using other context menu actions with the image. 183 184 To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we 185 set pointer-events to none on the nav div. This is so that the upcoming right-click event on 186 the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs 187 we set the pointer events back to auto for the nav div so it can capture hover and left-click 188 events as usual. 189 */ 190 this.$nav.on('mousedown', function(event) { 191 if (event.which === 3) { 192 self.$nav.css('pointer-events', 'none'); 193 194 self.$lightbox.one('contextmenu', function() { 195 setTimeout(function() { 196 this.$nav.css('pointer-events', 'auto'); 197 }.bind(self), 0); 198 }); 199 } 200 }); 201 202 203 this.$lightbox.find('.lb-loader, .lb-close').on('click keyup', function(e) { 204 // If mouse click OR 'enter' or 'space' keypress, close LB 205 if ( 206 e.type === 'click' || (e.type === 'keyup' && (e.which === 13 || e.which === 32))) { 207 self.end(); 208 return false; 209 } 210 }); 211 }; 212 213 // Show overlay and lightbox. If the image is part of a set, add siblings to album array. 214 Lightbox.prototype.start = function($link) { 215 var self = this; 216 var $window = $(window); 217 218 self.openingElement = document.activeElement; 219 220 $window.on('resize', $.proxy(this.sizeOverlay, this)); 221 222 this.sizeOverlay(); 223 224 this.album = []; 225 var imageNumber = 0; 226 227 function addToAlbum($link) { 228 self.album.push({ 229 alt: $link.attr('data-alt'), 230 link: $link.attr('href'), 231 title: $link.attr('data-title') || $link.attr('title'), 232 description: $link.attr('data-description') 233 }); 234 } 235 236 // Support both data-lightbox attribute and rel attribute implementations 237 var dataLightboxValue = $link.attr('data-lightbox'); 238 var $links; 239 240 if (dataLightboxValue) { 241 $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]'); 242 for (var i = 0; i < $links.length; i = ++i) { 243 addToAlbum($($links[i])); 244 if ($links[i] === $link[0]) { 245 imageNumber = i; 246 } 247 } 248 } else { 249 if ($link.attr('rel') === 'lightbox') { 250 // If image is not part of a set 251 addToAlbum($link); 252 } else { 253 // If image is part of a set 254 $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]'); 255 for (var j = 0; j < $links.length; j = ++j) { 256 addToAlbum($($links[j])); 257 if ($links[j] === $link[0]) { 258 imageNumber = j; 259 } 260 } 261 } 262 } 263 264 // Position Lightbox 265 var top = $window.scrollTop() + this.options.positionFromTop; 266 var left = $window.scrollLeft(); 267 this.$lightbox.css({ 268 top: top + 'px', 269 left: left + 'px' 270 }).fadeIn(this.options.fadeDuration); 271 272 // Disable scrolling of the page while open 273 if (this.options.disableScrolling) { 274 $('body').addClass('lb-disable-scrolling'); 275 } 276 277 this.changeImage(imageNumber); 278 }; 279 280 // Hide most UI elements in preparation for the animated resizing of the lightbox. 281 Lightbox.prototype.changeImage = function(imageNumber) { 282 var self = this; 283 var filename = this.album[imageNumber].link; 284 var filetype = filename.split('.').slice(-1)[0]; 285 var $image = this.$lightbox.find('.lb-image'); 286 287 // Disable keyboard nav during transitions 288 this.disableKeyboardNav(); 289 290 // Show loading state 291 this.$overlay.fadeIn(this.options.fadeDuration); 292 $('.lb-loader').fadeIn('slow'); 293 this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption, .lb-instContainer').hide(); 294 this.$outerContainer.addClass('animating'); 295 296 // When image to show is preloaded, we send the width and height to sizeContainer() 297 var preloader = new Image(); 298 preloader.onload = function() { 299 var $preloader; 300 var imageHeight; 301 var imageWidth; 302 var maxImageHeight; 303 var maxImageWidth; 304 var windowHeight; 305 var windowWidth; 306 307 $image.attr({ 308 'alt': self.album[imageNumber].alt, 309 'src': filename 310 }); 311 312 $preloader = $(preloader); 313 314 $image.width(preloader.width); 315 $image.height(preloader.height); 316 317 var aspectRatio = preloader.width / preloader.height; 318 319 windowWidth = $(window).width(); 320 windowHeight = $(window).height(); 321 322 // Calculate the max image dimensions for the current viewport. 323 // Take into account the border around the image and an additional 10px gutter on each side. 324 maxImageWidth = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20; 325 maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - self.options.positionFromTop - 70; 326 327 /* 328 Since many SVGs have small intrinsic dimensions, but they support scaling 329 up without quality loss because of their vector format, max out their 330 size inside the viewport. 331 */ 332 if (filetype === 'svg') { 333 if (aspectRatio >= 1) { 334 imageWidth = maxImageWidth; 335 imageHeight = parseInt(maxImageWidth / aspectRatio, 10); 336 } else {
337 imageWidth = parseInt(maxImageHeight / aspectRatio, 10); 338 imageHeight = maxImageHeight; 339 } 340 $image.width(imageWidth); 341 $image.height(imageHeight); 342 343 } else { 344 345 // Fit image inside the viewport. 346 if (self.options.fitImagesInViewport) { 347 348 // Check if image size is larger then maxWidth|maxHeight in settings 349 if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) { 350 maxImageWidth = self.options.maxWidth; 351 } 352 if (self.options.maxHeight && self.options.maxHeight < maxImageHeight) { 353 maxImageHeight = self.options.maxHeight; 354 } 355 356 } else { 357 maxImageWidth = self.options.maxWidth || preloader.width || maxImageWidth; 358 maxImageHeight = self.options.maxHeight || preloader.height || maxImageHeight; 359 } 360 361 // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight 362 // option than we need to size down while maintaining the aspect ratio. 363 if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) { 364 if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) { 365 imageWidth = maxImageWidth; 366 imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10); 367 $image.width(imageWidth); 368 $image.height(imageHeight); 369 } else { 370 imageHeight = maxImageHeight; 371 imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10); 372 $image.width(imageWidth); 373 $image.height(imageHeight); 374 } 375 } 376 } 377 378 self.sizeContainer($image.width(), $image.height()); 379 }; 380 381 // Preload image before showing 382 preloader.src = this.album[imageNumber].link; 383 this.currentImageIndex = imageNumber; 384 }; 385 386 // Stretch overlay to fit the viewport 387 Lightbox.prototype.sizeOverlay = function() { 388 var self = this; 389 /* 390 We use a setTimeout 0 to pause JS execution and let the rendering catch-up. 391 Why do this? If the `disableScrolling` option is set to true, a class is added to the body 392 tag that disables scrolling and hides the scrollbar. We want to make sure the scrollbar is 393 hidden before we measure the document width, as the presence of the scrollbar will affect the 394 number. 395 */ 396 setTimeout(function() { 397 self.$overlay 398 .width($(document).width()) 399 .height($(document).height()); 400 401 }, 0); 402 }; 403 404 // Animate the size of the lightbox to fit the image we are showing 405 // This method also shows the the image. 406 Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) { 407 var self = this; 408 409 var oldWidth = this.$outerContainer.outerWidth(); 410 var oldHeight = this.$outerContainer.outerHeight(); 411 var newWidth = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right; 412 var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom; 413 414 function postResize() { 415 self.$lightbox.find('.lb-instContainer').width(newWidth); 416 self.$lightbox.find('.lb-dataContainer').width(newWidth); 417 self.$lightbox.find('.lb-prevLink').height(newHeight); 418 self.$lightbox.find('.lb-nextLink').height(newHeight); 419 420 // Set focus on one of the two root nodes so keyboard events are captured. 421 self.$overlay.trigger('focus'); 422 423 self.showImage(); 424 } 425 426 if (oldWidth !== newWidth || oldHeight !== newHeight) { 427 this.$outerContainer.animate({ 428 width: newWidth, 429 height: newHeight 430 }, this.options.resizeDuration, 'swing', function() { 431 postResize(); 432 }); 433 } else { 434 postResize(); 435 } 436 }; 437 438 // Display the image and its details and begin preload neighboring images. 439 Lightbox.prototype.showImage = function() { 440 this.$lightbox.find('.lb-loader').stop(true).hide(); 441 this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration); 442 443 this.updateNav(); 444 this.updateDetails(); 445 this.preloadNeighboringImages(); 446 this.enableKeyboardNav(); 447 }; 448 449 // Display previous and next navigation if appropriate. 450 Lightbox.prototype.updateNav = function() { 451 // Check to see if the browser supports touch events. If so, we take the conservative approach 452 // and assume that mouse hover events are not supported and always show prev/next navigation 453 // arrows in image sets. 454 var alwaysShowNav = false;
455 try { 456 document.createEvent('TouchEvent'); 457 alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false; 458 } catch (e) {} 459 460 this.$lightbox.find('.lb-nav').show(); 461 462 if (this.album.length > 1) { 463 if (this.options.wrapAround) { 464 if (alwaysShowNav) { 465 this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1'); 466 } 467 this.$lightbox.find('.lb-prev, .lb-next').show(); 468 } else { 469 if (this.currentImageIndex > 0) { 470 this.$lightbox.find('.lb-prev').show(); 471 if (alwaysShowNav) { 472 this.$lightbox.find('.lb-prev').css('opacity', '1'); 473 } 474 } 475 if (this.currentImageIndex < this.album.length - 1) { 476 this.$lightbox.find('.lb-next').show(); 477 if (alwaysShowNav) { 478 this.$lightbox.find('.lb-next').css('opacity', '1'); 479 } 480 } 481 } 482 } 483 }; 484 485 // Display caption, description, image number, and closing button. 486 Lightbox.prototype.updateDetails = function() { 487 var self = this; 488 489 // Enable anchor clicks in the injected caption html. 490 // Thanks Nate Wright for the fix. @https://github.com/NateWr 491 if ((typeof this.album[this.currentImageIndex].title !== 'undefined' 492 && this.album[this.currentImageIndex].title !== '') 493 || (typeof this.album[this.currentImageIndex].description !== 'undefined' 494 && this.album[this.currentImageIndex].description !== '')) 495 { 496 var $caption = this.$lightbox.find('.lb-caption'); 497 var $description = this.$lightbox.find('.lb-description'); 498 if (this.options.sanitizeTitle) { 499 $caption.text(this.album[this.currentImageIndex].title); 500 $description.text(this.album[this.currentImageIndex].description); 501 } else { 502 $caption.html(this.album[this.currentImageIndex].title); 503 $description.html(this.album[this.currentImageIndex].description); 504 } 505 $caption.fadeIn('fast'); 506 $description.fadeIn('fast'); 507 } 508 509 if (this.album.length > 1 && this.options.showImageNumberLabel) { 510 var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length); 511 this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast'); 512 } else { 513 this.$lightbox.find('.lb-number').hide(); 514 } 515 516 this.$outerContainer.removeClass('animating'); 517 518 this.$lightbox.find('.lb-dataContainer,.lb-instContainer').fadeIn(this.options.resizeDuration, function() { 519 return self.sizeOverlay(); 520 }); 521 }; 522 523 // Preload previous and next images in set. 524 Lightbox.prototype.preloadNeighboringImages = function() { 525 if (this.album.length > this.currentImageIndex + 1) { 526 var preloadNext = new Image(); 527 preloadNext.src = this.album[this.currentImageIndex + 1].link; 528 } 529 if (this.currentImageIndex > 0) { 530 var preloadPrev = new Image(); 531 preloadPrev.src = this.album[this.currentImageIndex - 1].link; 532 } 533 }; 534 535 Lightbox.prototype.enableKeyboardNav = function() { 536 this.$lightbox.on('keydown.keyboard', $.proxy(this.keyboardAction, this)); 537 this.$overlay.on('keydown.keyboard', $.proxy(this.keyboardAction, this)); 538 }; 539 540 Lightbox.prototype.disableKeyboardNav = function() { 541 this.$lightbox.off('.keyboard'); 542 this.$overlay.off('.keyboard'); 543 }; 544 545 Lightbox.prototype.keyboardAction = function(event) { 546 var KEYCODE_ESC = 27; 547 var KEYCODE_LEFTARROW = 37; 548 var KEYCODE_RIGHTARROW = 39; 549 var KEYCODE_TAB = 9; 550 551 var keycode = event.keyCode; 552 if (keycode === KEYCODE_ESC) { 553 // Prevent bubbling so as to not affect other components on the page. 554 event.stopPropagation(); 555 this.end(); 556 } else if (keycode === KEYCODE_LEFTARROW) { 557 if (this.currentImageIndex !== 0) { 558 this.changeImage(this.currentImageIndex - 1); 559 } else if (this.options.wrapAround && this.album.length > 1) { 560 this.changeImage(this.album.length - 1); 561 } 562 } else if (keycode === KEYCODE_RIGHTARROW) { 563 if (this.currentImageIndex !== this.album.length - 1) { 564 this.changeImage(this.currentImageIndex + 1); 565 } else if (this.options.wrapAround && this.album.length > 1) { 566 this.changeImage(0); 567 } 568 } else if (keycode === KEYCODE_TAB) { 569 // Prevent bubbling so as to not affect other components on the page. 570 event.stopPropagation(); 571 event.preventDefault(); 572 return false;
573 } 574 }; 575 576 // Closing time. :-( 577 Lightbox.prototype.end = function() { 578 this.disableKeyboardNav(); 579 $(window).off('resize', this.sizeOverlay); 580 this.$lightbox.fadeOut(this.options.fadeDuration); 581 this.$overlay.fadeOut(this.options.fadeDuration); 582 583 if (this.options.disableScrolling) { 584 $('body').removeClass('lb-disable-scrolling'); 585 } 586 587 if (this.openingElement !== null) 588 { 589 this.openingElement.focus(); 590 this.openingElement = null; 591 } 592 }; 593 594 return new Lightbox(); 595}));
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.