vendor: 5,748 bytes, lines 1-142
1/*! 2 * Lightbox v2.11.5 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 propagation of the Esc keypress when 110 // Lightbox is open. This prevents it from interfering 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" role="button" tabindex="0" aria-label="Previous image" href="" ></a><a class="lb-next" role="button" tabindex="0" aria-label="Next image" href="" ></a></div><div class="lb-loader"><a class="lb-cancel" role="button" tabindex="0"></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" role="button" tabindex="0"></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 keyup', function(e) { 203 // If mouse click OR 'enter' or 'space' keypress, close LB 204 if ( 205 e.type === 'click' || (e.type === 'keyup' && (e.which === 13 || e.which === 32))) { 206 self.end(); 207 return false; 208 } 209 }); 210 }; 211 212 // Show overlay and lightbox. If the image is part of a set, add siblings to album array. 213 Lightbox.prototype.start = function($link) { 214 var self = this; 215 var $window = $(window); 216 217 $window.on('resize', $.proxy(this.sizeOverlay, this)); 218 219 this.sizeOverlay(); 220 221 this.album = []; 222 var imageNumber = 0; 223 224 function addToAlbum($link) { 225 self.album.push({ 226 alt: $link.attr('data-alt'), 227 link: $link.attr('href'), 228 title: $link.attr('data-title') || $link.attr('title') 229 }); 230 } 231 232 // Support both data-lightbox attribute and rel attribute implementations 233 var dataLightboxValue = $link.attr('data-lightbox'); 234 var $links; 235 236 if (dataLightboxValue) { 237 $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]'); 238 for (var i = 0; i < $links.length; i = ++i) { 239 addToAlbum($($links[i])); 240 if ($links[i] === $link[0]) { 241 imageNumber = i; 242 } 243 } 244 } else { 245 if ($link.attr('rel') === 'lightbox') { 246 // If image is not part of a set 247 addToAlbum($link); 248 } else { 249 // If image is part of a set 250 $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]'); 251 for (var j = 0; j < $links.length; j = ++j) { 252 addToAlbum($($links[j])); 253 if ($links[j] === $link[0]) { 254 imageNumber = j; 255 } 256 } 257 } 258 } 259 260 // Position Lightbox 261 var top = $window.scrollTop() + this.options.positionFromTop; 262 var left = $window.scrollLeft(); 263 this.$lightbox.css({ 264 top: top + 'px', 265 left: left + 'px' 266 }).fadeIn(this.options.fadeDuration); 267 268 // Disable scrolling of the page while open 269 if (this.options.disableScrolling) { 270 $('body').addClass('lb-disable-scrolling'); 271 } 272 273 this.changeImage(imageNumber); 274 }; 275 276 // Hide most UI elements in preparation for the animated resizing of the lightbox. 277 Lightbox.prototype.changeImage = function(imageNumber) { 278 var self = this; 279 var filename = this.album[imageNumber].link; 280 var filetype = filename.split('.').slice(-1)[0]; 281 var $image = this.$lightbox.find('.lb-image'); 282 283 // Disable keyboard nav during transitions 284 this.disableKeyboardNav(); 285 286 // Show loading state 287 this.$overlay.fadeIn(this.options.fadeDuration); 288 $('.lb-loader').fadeIn('slow'); 289 this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide(); 290 this.$outerContainer.addClass('animating'); 291 292 // When image to show is preloaded, we send the width and height to sizeContainer() 293 var preloader = new Image(); 294 preloader.onload = function() { 295 var $preloader; 296 var imageHeight; 297 var imageWidth; 298 var maxImageHeight; 299 var maxImageWidth; 300 var windowHeight; 301 var windowWidth; 302 303 $image.attr({ 304 'alt': self.album[imageNumber].alt, 305 'src': filename 306 }); 307 308 $preloader = $(preloader); 309 310 $image.width(preloader.width); 311 $image.height(preloader.height); 312 313 var aspectRatio = preloader.width / preloader.height; 314 315 windowWidth = $(window).width(); 316 windowHeight = $(window).height(); 317 318 // Calculate the max image dimensions for the current viewport. 319 // Take into account the border around the image and an additional 10px gutter on each side. 320 maxImageWidth = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20; 321 maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - self.options.positionFromTop - 70; 322 323 /* 324 Since many SVGs have small intrinsic dimensions, but they support scaling 325 up without quality loss because of their vector format, max out their 326 size inside the viewport. 327 */ 328 if (filetype === 'svg') { 329 if (aspectRatio >= 1) { 330 imageWidth = maxImageWidth; 331 imageHeight = parseInt(maxImageWidth / aspectRatio, 10); 332 } else {
333 imageWidth = parseInt(maxImageHeight / aspectRatio, 10); 334 imageHeight = maxImageHeight; 335 } 336 $image.width(imageWidth); 337 $image.height(imageHeight); 338 339 } else { 340 341 // Fit image inside the viewport. 342 if (self.options.fitImagesInViewport) { 343 344 // Check if image size is larger then maxWidth|maxHeight in settings 345 if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) { 346 maxImageWidth = self.options.maxWidth; 347 } 348 if (self.options.maxHeight && self.options.maxHeight < maxImageHeight) { 349 maxImageHeight = self.options.maxHeight; 350 } 351 352 } else { 353 maxImageWidth = self.options.maxWidth || preloader.width || maxImageWidth; 354 maxImageHeight = self.options.maxHeight || preloader.height || maxImageHeight; 355 } 356 357 // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight 358 // option than we need to size down while maintaining the aspect ratio. 359 if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) { 360 if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) { 361 imageWidth = maxImageWidth; 362 imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10); 363 $image.width(imageWidth); 364 $image.height(imageHeight); 365 } else { 366 imageHeight = maxImageHeight; 367 imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10); 368 $image.width(imageWidth); 369 $image.height(imageHeight); 370 } 371 } 372 } 373 374 self.sizeContainer($image.width(), $image.height()); 375 }; 376 377 // Preload image before showing 378 preloader.src = this.album[imageNumber].link; 379 this.currentImageIndex = imageNumber; 380 }; 381 382 // Stretch overlay to fit the viewport 383 Lightbox.prototype.sizeOverlay = function() { 384 var self = this; 385 /* 386 We use a setTimeout 0 to pause JS execution and let the rendering catch-up. 387 Why do this? If the `disableScrolling` option is set to true, a class is added to the body 388 tag that disables scrolling and hides the scrollbar. We want to make sure the scrollbar is 389 hidden before we measure the document width, as the presence of the scrollbar will affect the 390 number. 391 */ 392 setTimeout(function() { 393 self.$overlay 394 .width($(document).width()) 395 .height($(document).height()); 396 397 }, 0); 398 }; 399 400 // Animate the size of the lightbox to fit the image we are showing 401 // This method also shows the the image. 402 Lightbox.prototype.sizeContainer = function(imageWidth, imageHeight) { 403 var self = this; 404 405 var oldWidth = this.$outerContainer.outerWidth(); 406 var oldHeight = this.$outerContainer.outerHeight(); 407 var newWidth = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right; 408 var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom; 409 410 function postResize() { 411 self.$lightbox.find('.lb-dataContainer').width(newWidth); 412 self.$lightbox.find('.lb-prevLink').height(newHeight); 413 self.$lightbox.find('.lb-nextLink').height(newHeight); 414 415 // Set focus on one of the two root nodes so keyboard events are captured. 416 self.$overlay.trigger('focus'); 417 418 self.showImage(); 419 } 420 421 if (oldWidth !== newWidth || oldHeight !== newHeight) { 422 this.$outerContainer.animate({ 423 width: newWidth, 424 height: newHeight 425 }, this.options.resizeDuration, 'swing', function() { 426 postResize(); 427 }); 428 } else { 429 postResize(); 430 } 431 }; 432 433 // Display the image and its details and begin preload neighboring images. 434 Lightbox.prototype.showImage = function() { 435 this.$lightbox.find('.lb-loader').stop(true).hide(); 436 this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration); 437 438 this.updateNav(); 439 this.updateDetails(); 440 this.preloadNeighboringImages(); 441 this.enableKeyboardNav(); 442 }; 443 444 // Display previous and next navigation if appropriate. 445 Lightbox.prototype.updateNav = function() { 446 // Check to see if the browser supports touch events. If so, we take the conservative approach 447 // and assume that mouse hover events are not supported and always show prev/next navigation 448 // arrows in image sets. 449 var alwaysShowNav = false;
450 try { 451 document.createEvent('TouchEvent'); 452 alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false; 453 } catch (e) {} 454 455 this.$lightbox.find('.lb-nav').show(); 456 457 if (this.album.length > 1) { 458 if (this.options.wrapAround) { 459 if (alwaysShowNav) { 460 this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1'); 461 } 462 this.$lightbox.find('.lb-prev, .lb-next').show(); 463 } else { 464 if (this.currentImageIndex > 0) { 465 this.$lightbox.find('.lb-prev').show(); 466 if (alwaysShowNav) { 467 this.$lightbox.find('.lb-prev').css('opacity', '1'); 468 } 469 } 470 if (this.currentImageIndex < this.album.length - 1) { 471 this.$lightbox.find('.lb-next').show(); 472 if (alwaysShowNav) { 473 this.$lightbox.find('.lb-next').css('opacity', '1'); 474 } 475 } 476 } 477 } 478 }; 479 480 // Display caption, image number, and closing button. 481 Lightbox.prototype.updateDetails = function() { 482 var self = this; 483 484 // Enable anchor clicks in the injected caption html. 485 // Thanks Nate Wright for the fix. @https://github.com/NateWr 486 if (typeof this.album[this.currentImageIndex].title !== 'undefined' && 487 this.album[this.currentImageIndex].title !== '') { 488 var $caption = this.$lightbox.find('.lb-caption'); 489 if (this.options.sanitizeTitle) { 490 $caption.text(this.album[this.currentImageIndex].title); 491 } else { 492 $caption.html(this.album[this.currentImageIndex].title); 493 } 494 $caption.fadeIn('fast'); 495 } 496 497 if (this.album.length > 1 && this.options.showImageNumberLabel) { 498 var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length); 499 this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast'); 500 } else { 501 this.$lightbox.find('.lb-number').hide(); 502 } 503 504 this.$outerContainer.removeClass('animating'); 505 506 this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function() { 507 return self.sizeOverlay(); 508 }); 509 }; 510 511 // Preload previous and next images in set. 512 Lightbox.prototype.preloadNeighboringImages = function() { 513 if (this.album.length > this.currentImageIndex + 1) { 514 var preloadNext = new Image(); 515 preloadNext.src = this.album[this.currentImageIndex + 1].link; 516 } 517 if (this.currentImageIndex > 0) { 518 var preloadPrev = new Image(); 519 preloadPrev.src = this.album[this.currentImageIndex - 1].link; 520 } 521 }; 522 523 Lightbox.prototype.enableKeyboardNav = function() { 524 this.$lightbox.on('keyup.keyboard', $.proxy(this.keyboardAction, this)); 525 this.$overlay.on('keyup.keyboard', $.proxy(this.keyboardAction, this)); 526 }; 527 528 Lightbox.prototype.disableKeyboardNav = function() { 529 this.$lightbox.off('.keyboard'); 530 this.$overlay.off('.keyboard'); 531 }; 532 533 Lightbox.prototype.keyboardAction = function(event) { 534 var KEYCODE_ESC = 27; 535 var KEYCODE_LEFTARROW = 37; 536 var KEYCODE_RIGHTARROW = 39; 537 538 var keycode = event.keyCode; 539 if (keycode === KEYCODE_ESC) { 540 // Prevent bubbling so as to not affect other components on the page. 541 event.stopPropagation(); 542 this.end(); 543 } else if (keycode === KEYCODE_LEFTARROW) { 544 if (this.currentImageIndex !== 0) { 545 this.changeImage(this.currentImageIndex - 1); 546 } else if (this.options.wrapAround && this.album.length > 1) { 547 this.changeImage(this.album.length - 1); 548 } 549 } else if (keycode === KEYCODE_RIGHTARROW) { 550 if (this.currentImageIndex !== this.album.length - 1) { 551 this.changeImage(this.currentImageIndex + 1); 552 } else if (this.options.wrapAround && this.album.length > 1) { 553 this.changeImage(0); 554 } 555 } 556 }; 557 558 // Closing time. :-( 559 Lightbox.prototype.end = function() { 560 this.disableKeyboardNav(); 561 $(window).off('resize', this.sizeOverlay); 562 this.$lightbox.fadeOut(this.options.fadeDuration); 563 this.$overlay.fadeOut(this.options.fadeDuration); 564 565 if (this.options.disableScrolling) { 566 $('body').removeClass('lb-disable-scrolling'); 567 } 568 }; 569 570 return new Lightbox(); 571}));
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.