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