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