1(function (Drupal) { 2 3 /** 4 * Command to attach data using jQuery's data API. 5 * 6 * @param {Drupal.Ajax} [ajax] 7 * {@link Drupal.Ajax} object created by {@link Drupal.ajax}. 8 * @param {object} response 9 * The response from the Ajax request. 10 * @param {string} response.event_name 11 * The event name 12 * @param {object} response.data 13 * The value of the event. 14 */ 15 Drupal.AjaxCommands.prototype.gtagEvent = function (ajax, response) { 16 gtag('event', response.event_name, response.data) 17 } 18})(Drupal); 19; 20function ctSetCookie(cookies) 21{ 22 23 let useCookies, useAltCookies 24 25 if ('undefined' !== typeof ct_use_cookies) { 26 useCookies = ct_use_cookies; 27 } else { 28 useCookies = false; 29 } 30 31 if ('undefined' !== typeof ct_use_alt_cookies) { 32 useAltCookies = ct_use_alt_cookies; 33 } else { 34 useAltCookies = false 35 } 36 37 if (useCookies) { 38 if (useAltCookies && useAltCookies === 1) { 39 let xhr = new XMLHttpRequest(); 40 41 let json = JSON.stringify(cookies); 42 43 xhr.open("POST", '/admin/config/cleantalk/set_alt_cookies') 44 xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); 45 46 xhr.send(json); 47 } else { 48 cookies.forEach( 49 function (e) { 50 let ctSecure = location.protocol === "https:" ? "; secure" : ""; 51 document.cookie = e.name + "=" + encodeURIComponent(e.value) + "; path=/; expires=0; samesite=lax" + ctSecure; 52 } 53 ) 54 } 55 } 56} 57; 58var d = new Date(), 59 ctTimeMs = new Date().getTime(), 60 ctMouseEventTimerFlag = true, //Reading interval flag 61 ctMouseData = "[", 62 ctMouseDataCounter = 0, 63 ctScrollCollected = false, 64 ctEventTokenInterval = null; 65 66ctSetCookie( 67 [ 68 { 69 'name' : 'ct_check_js', 70 'value' : ct_check_js_val 71 }, 72 { 73 'name' : 'ct_ps_timestamp', 74 'value' : Math.floor(new Date().getTime()/1000) 75 }, 76 { 77 'name' : 'ct_fkp_timestamp', 78 'value' : '0' 79 }, 80 { 81 'name' : 'ct_pointer_data', 82 'value' : '0' 83 }, 84 { 85 'name' : 'ct_timezone', 86 'value' : d.getTimezoneOffset()/60*(-1) 87 }, 88 { 89 'name' : 'apbct_antibot', 90 'value' : drupal_ac_antibot_cookie_value 91 }, 92 { 93 'name' : 'ct_has_scrolled', 94 'value' : false 95 } 96 ] 97); 98 99//Reading interval 100 101var ctMouseReadInterval = setInterval( 102 function () { 103 ctMouseEventTimerFlag = true; 104 }, 150 105); 106 107//Writting interval 108 109var ctMouseWriteDataInterval = setInterval( 110 function () { 111 if(typeof ct_use_alt_cookies !== "undefined" && ct_use_alt_cookies === 1) { 112 return; 113 } 114 var ctMouseDataToSend = ctMouseData.slice(0,-1).concat("]"); 115 ctSetCookie( 116 [ 117 { 118 'name' : 'ct_pointer_data', 119 'value' : ctMouseDataToSend 120 } 121 ] 122 ); 123 }, 1200 124); 125 126//Stop observing function 127 128function ctMouseStopData() 129{ 130 if(typeof window.addEventListener == "function") { 131 window.removeEventListener("mousemove", ctFunctionMouseMove); 132 } else { 133 window.detachEvent("onmousemove", ctFunctionMouseMove); 134 } 135 clearInterval(ctMouseReadInterval); 136 clearInterval(ctMouseWriteDataInterval); 137} 138 139//Logging mouse position each 300 ms 140 141var ctFunctionMouseMove = function output(event) 142{ 143 if (ctMouseEventTimerFlag == true) { 144 var mouseDate = new Date(); 145 ctMouseData += "[" + Math.round(event.pageY) + "," + Math.round(event.pageX) + "," + Math.round(mouseDate.getTime() - ctTimeMs) + "],"; 146 ctMouseDataCounter++; 147 ctMouseEventTimerFlag = false; 148 if(ctMouseDataCounter >= 100) { 149 ctMouseStopData(); 150 } 151 } 152}; 153 154/** 155 * Set scrolling cookie 156 */ 157function ctSetHasScrolled() 158{ 159 if(! ctScrollCollected ) { 160 ctSetCookie( 161 [ 162 { 163 'name' : 'ct_has_scrolled', 164 'value' : true 165 } 166 ] 167 ); 168 ctScrollCollected = true; 169 } 170} 171 172//Stop key listening function 173 174function ctKeyStopStopListening() 175{ 176 if (typeof window.addEventListener == "function") { 177 window.removeEventListener("mousedown", ctFunctionFirstKey); 178 window.removeEventListener("keydown", ctFunctionFirstKey); 179 } 180 else { 181 window.detachEvent("mousedown", ctFunctionFirstKey); 182 window.detachEvent("keydown", ctFunctionFirstKey); 183 } 184} 185 186//Writing first key press timestamp 187 188var ctFunctionFirstKey = function output(event) 189{ 190 var KeyTimestamp = Math.floor(new Date().getTime()/1000);
191 ctSetCookie( 192 [ 193 { 194 'name' : 'ct_fkp_timestamp', 195 'value' : KeyTimestamp 196 } 197 ] 198 ); 199 ctKeyStopStopListening(); 200}; 201 202if (typeof window.addEventListener == "function") { 203 window.addEventListener("mousemove", ctFunctionMouseMove); 204 window.addEventListener("mousedown", ctFunctionFirstKey); 205 window.addEventListener("keydown", ctFunctionFirstKey); 206 window.addEventListener("scroll", ctSetHasScrolled); 207} 208else { 209 window.attachEvent("onmousemove", ctFunctionMouseMove); 210 window.attachEvent("mousedown", ctFunctionFirstKey); 211 window.attachEvent("keydown", ctFunctionFirstKey); 212 window.attachEvent("scroll", ctSetHasScrolled); 213} 214 215function apbct_collect_visible_fields( form ) 216{ 217 218 // Get only fields 219 var inputs = [], 220 inputs_visible = '', 221 inputs_visible_count = 0, 222 inputs_invisible = '', 223 inputs_invisible_count = 0, 224 inputs_with_duplicate_names = []; 225 226 for(var key in form.elements){ 227 if(!isNaN(+key)) { 228 inputs[key] = form.elements[key]; 229 } 230 } 231 232 // Filter fields 233 inputs = inputs.filter( 234 function (elem) { 235 236 // Filter already added fields 237 if(inputs_with_duplicate_names.indexOf(elem.getAttribute('name')) !== -1 ) { 238 return false; 239 } 240 // Filter inputs with same names for type == radio 241 if(-1 !== ['radio', 'checkbox'].indexOf(elem.getAttribute("type"))) { 242 inputs_with_duplicate_names.push(elem.getAttribute('name')); 243 return false; 244 } 245 return true; 246 } 247 ); 248 249 // Visible fields 250 inputs.forEach( 251 function (elem, i, elements) { 252 // Unnecessary fields 253 if(elem.getAttribute("type") === "submit" // type == submit 254 || elem.getAttribute('name') === null 255 || elem.getAttribute('name') === 'ct_checkjs' 256 ) { 257 return; 258 } 259 // Invisible fields 260 if(getComputedStyle(elem).display === "none" // hidden 261 || getComputedStyle(elem).visibility === "hidden" // hidden 262 || getComputedStyle(elem).opacity === "0" // hidden 263 || elem.getAttribute("type") === "hidden" // type == hidden 264 ) { 265 if(elem.classList.contains("wp-editor-area") ) { 266 inputs_visible += " " + elem.getAttribute("name"); 267 inputs_visible_count++; 268 } else { 269 inputs_invisible += " " + elem.getAttribute("name"); 270 inputs_invisible_count++; 271 } 272 } 273 // Visible fields 274 else { 275 inputs_visible += " " + elem.getAttribute("name"); 276 inputs_visible_count++; 277 } 278 279 } 280 ); 281 282 inputs_invisible = inputs_invisible.trim(); 283 inputs_visible = inputs_visible.trim(); 284 285 return { 286 visible_fields : inputs_visible, 287 visible_fields_count : inputs_visible_count, 288 invisible_fields : inputs_invisible, 289 invisible_fields_count : inputs_invisible_count, 290 } 291 292} 293 294function apbct_visible_fields_set_cookie( visible_fields_collection ) 295{ 296 var collection = typeof visible_fields_collection === 'object' && visible_fields_collection !== null ? visible_fields_collection : {}; 297 298 ctSetCookie( 299 [ 300 { 301 'name' : 'apbct_visible_fields', 302 'value' : JSON.stringify(collection) 303 } 304 ] 305 ) 306} 307 308// Event token handler class. Get token and save it to the alternative sessions. 309 310class EventTokenHandler { 311 312 constructor(use_alt_cookies_option) { 313 this.token = null; 314 this.lsSavedFlagName = 'event_token_saved_to_alt_sessions'; 315 this.lsEVentTokenName = 'bot_detector_event_token'; 316 this.intervalValue = 1500; 317 this.init(use_alt_cookies_option); 318 } 319 320 init(use_alt_cookies_option){ 321 if ('undefined' !== typeof use_alt_cookies_option && use_alt_cookies_option) { 322 this.setNotSavedFlag(); 323 this.startListenTokenLS(); 324 } 325 } 326 327 328 setSavedFlag() 329 { 330 localStorage.setItem(this.lsSavedFlagName, '1'); 331 } 332 333 setNotSavedFlag() 334 { 335 localStorage.setItem(this.lsSavedFlagName, '0'); 336 } 337 338 isSaved() 339 { 340 let event_token_saved = localStorage.getItem(this.lsSavedFlagName); 341 return event_token_saved !== null && event_token_saved !== undefined && event_token_saved === '1'; 342 } 343 344 getEventToken() 345 { 346 let event_token = localStorage.getItem(this.lsEVentTokenName); 347 try { 348 event_token = event_token !== null ? JSON.parse(event_token) : false;
349 } catch (e) { 350 return false; 351 } 352 event_token = event_token.hasOwnProperty('value') ? event_token.value : false; 353 if (typeof event_token === 'string' && event_token.length === 64) { 354 return event_token; 355 } 356 return false; 357 } 358 359 setEventTokenCookie() 360 { 361 if (typeof this.token === 'string' && this.token.length === 64) { 362 ctSetCookie( 363 [ 364 { 365 'name': 'apbct_event_token', 366 'value': this.token 367 } 368 ] 369 ) 370 } 371 } 372 373 stopListenTokenLS() 374 { 375 clearInterval(this.intervalID); 376 } 377 378 startListenTokenLS() 379 { 380 this.intervalID = setInterval( this.intervalRun, this.intervalValue, this) 381 } 382 383 intervalRun(handler) 384 { 385 if ( !handler.isSaved() ) { 386 handler.token = handler.getEventToken(); 387 if ( handler.token ) { 388 handler.setEventTokenCookie(); 389 handler.setSavedFlag(); 390 handler.stopListenTokenLS(); 391 } 392 } 393 } 394} 395 396//Collect event token 397new EventTokenHandler(ct_use_alt_cookies); 398; 399/** 400 * @file 401 * Bootstrap Popovers. 402 */ 403 404var Drupal = Drupal || {}; 405 406(function ($, Drupal, Bootstrap) { 407 "use strict"; 408 409 var $document = $(document); 410 411 /** 412 * Extend the Bootstrap Popover plugin constructor class. 413 */ 414 Bootstrap.extendPlugin('popover', function (settings) { 415 return { 416 DEFAULTS: { 417 animation: !!settings.popover_animation, 418 autoClose: !!settings.popover_auto_close, 419 enabled: settings.popover_enabled, 420 html: !!settings.popover_html, 421 placement: settings.popover_placement, 422 selector: settings.popover_selector, 423 trigger: settings.popover_trigger, 424 title: settings.popover_title, 425 content: settings.popover_content, 426 delay: parseInt(settings.popover_delay, 10), 427 container: settings.popover_container 428 } 429 }; 430 }); 431 432 /** 433 * Bootstrap Popovers. 434 * 435 * @todo This should really be properly delegated if selector option is set. 436 */ 437 Drupal.behaviors.bootstrapPopovers = { 438 $activePopover: null, 439 attach: function (context) { 440 // Immediately return if popovers are not available. 441 if (!$.fn.popover || !$.fn.popover.Constructor.DEFAULTS.enabled) { 442 return; 443 } 444 445 var _this = this; 446 447 $document 448 .on('show.bs.popover', '[data-toggle=popover]', function () { 449 var $trigger = $(this); 450 var popover = $trigger.data('bs.popover'); 451 452 // Only keep track of clicked triggers that we're manually handling. 453 if (popover.options.originalTrigger === 'click') { 454 if (_this.$activePopover && _this.getOption('autoClose') && !_this.$activePopover.is($trigger)) { 455 _this.$activePopover.popover('hide'); 456 } 457 _this.$activePopover = $trigger; 458 } 459 }) 460 // Unfortunately, :focusable is only made available when using jQuery 461 // UI. While this would be the most semantic pseudo selector to use 462 // here, jQuery UI may not always be loaded. Instead, just use :visible 463 // here as this just needs some sort of selector here. This activates 464 // delegate binding to elements in jQuery so it can work it's bubbling 465 // focus magic since elements don't really propagate their focus events. 466 // @see https://www.drupal.org/project/bootstrap/issues/3013236 467 .on('focus.bs.popover', ':visible', function (e) { 468 var $target = $(e.target); 469 if (_this.$activePopover && _this.getOption('autoClose') && !_this.$activePopover.is($target) && !$target.closest('.popover.in')[0]) { 470 _this.$activePopover.popover('hide'); 471 _this.$activePopover = null; 472 } 473 }) 474 .on('click.bs.popover', function (e) { 475 var $target = $(e.target); 476 if (_this.$activePopover && _this.getOption('autoClose') && !$target.is('[data-toggle=popover]') && !$target.closest('.popover.in')[0]) { 477 _this.$activePopover.popover('hide'); 478 _this.$activePopover = null; 479 } 480 }) 481 .on('keyup.bs.popover', function (e) { 482 if (_this.$activePopover && _this.getOption('autoClose') && e.which === 27) { 483 _this.$activePopover.popover('hide'); 484 _this.$activePopover = null; 485 } 486 }) 487 ; 488 489 var elements = $(context).find('[data-toggle=popover]').toArray(); 490 for (var i = 0; i < elements.length; i++) { 491 var $element = $(elements[i]); 492 var options = $.extend({}, $.fn.popover.Constructor.DEFAULTS, $element.data()); 493 494 // Store the original trigger. 495 options.originalTrigger = options.trigger; 496 497 // If the trigger is "click", then we'll handle it manually here. 498 if (options.trigger === 'click') { 499 options.trigger = 'manual'; 500 } 501 502 // Retrieve content from a target element. 503 var target = options.target || $element.is('a[href^="#"]') && $element.attr('href'); 504 var $target = $document.find(target).clone(); 505 if (!options.content && $target[0]) { 506 $target.removeClass('visually-hidden hidden').removeAttr('aria-hidden'); 507 options.content = $target.wrap('<div/>').parent()[options.html ? 'html' : 'text']() || ''; 508 } 509 510 // Initialize the popover. 511 $element.popover(options); 512 513 // Handle clicks manually. 514 if (options.originalTrigger === 'click') { 515 // To ensure the element is bound multiple times, remove any 516 // previously set event handler before adding another one. 517 $element 518 .off('click.drupal.bootstrap.popover') 519 .on('click.drupal.bootstrap.popover', function (e) { 520 $(this).popover('toggle'); 521 e.preventDefault(); 522 e.stopPropagation(); 523 }) 524 ; 525 } 526 } 527 }, 528 detach: function (context) { 529 // Immediately return if popovers are not available. 530 if (!$.fn.popover || !$.fn.popover.Constructor.DEFAULTS.enabled) { 531 return; 532 } 533 534 // Destroy all popovers. 535 $(context).find('[data-toggle="popover"]') 536 .off('click.drupal.bootstrap.popover') 537 .popover('destroy') 538 ; 539 }, 540 getOption: function(name, defaultValue, element) { 541 var $element = element ? $(element) : this.$activePopover; 542 var options = $.extend(true, {}, $.fn.popover.Constructor.DEFAULTS, ($element && $element.data('bs.popover') || {}).options); 543 if (options[name] !== void 0) { 544 return options[name]; 545 } 546 return defaultValue !== void 0 ? defaultValue : void 0; 547 } 548 }; 549 550})(window.jQuery, window.Drupal, window.Drupal.bootstrap); 551; 552/** 553 * @file 554 * Bootstrap Tooltips. 555 */ 556 557var Drupal = Drupal || {}; 558 559(function ($, Drupal, Bootstrap) { 560 "use strict"; 561 562 /** 563 * Extend the Bootstrap Tooltip plugin constructor class. 564 */ 565 Bootstrap.extendPlugin('tooltip', function (settings) { 566 return { 567 DEFAULTS: { 568 animation: !!settings.tooltip_animation, 569 enabled: settings.tooltip_enabled, 570 html: !!settings.tooltip_html, 571 placement: settings.tooltip_placement, 572 selector: settings.tooltip_selector, 573 trigger: settings.tooltip_trigger, 574 delay: parseInt(settings.tooltip_delay, 10), 575 container: settings.tooltip_container 576 } 577 }; 578 }); 579 580 /** 581 * Bootstrap Tooltips. 582 * 583 * @todo This should really be properly delegated if selector option is set. 584 */ 585 Drupal.behaviors.bootstrapTooltips = { 586 attach: function (context) { 587 // Immediately return if tooltips are not available. 588 if (!$.fn.tooltip || !$.fn.tooltip.Constructor.DEFAULTS.enabled) { 589 return; 590 } 591 592 var elements = $(context).find('[data-toggle="tooltip"]').toArray(); 593 for (var i = 0; i < elements.length; i++) { 594 var $element = $(elements[i]); 595 var options = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, $element.data()); 596 $element.tooltip(options); 597 } 598 }, 599 detach: function (context) { 600 // Immediately return if tooltips are not available. 601 if (!$.fn.tooltip || !$.fn.tooltip.Constructor.DEFAULTS.enabled) { 602 return; 603 } 604 605 // Destroy all tooltips. 606 $(context).find('[data-toggle="tooltip"]').tooltip('destroy'); 607 } 608 }; 609 610})(window.jQuery, window.Drupal, window.Drupal.bootstrap); 611;
vendor: 29,168 bytes, lines 612-1716
612/*! 613 Colorbox 1.6.4 614 license: MIT 615 http://www.jacklmoore.com/colorbox 616*/ 617(function ($, document, window) { 618 var 619 // Default settings object. 620 // See http://jacklmoore.com/colorbox for details. 621 defaults = { 622 // data sources 623 html: false, 624 photo: false, 625 iframe: false, 626 inline: false, 627 628 // behavior and appearance 629 transition: "elastic", 630 speed: 300, 631 fadeOut: 300, 632 width: false, 633 initialWidth: "600", 634 innerWidth: false, 635 maxWidth: false, 636 height: false, 637 initialHeight: "450", 638 innerHeight: false, 639 maxHeight: false, 640 scalePhotos: true, 641 scrolling: true, 642 opacity: 0.9, 643 preloading: true, 644 className: false, 645 overlayClose: true, 646 escKey: true, 647 arrowKey: true, 648 top: false, 649 bottom: false, 650 left: false, 651 right: false, 652 fixed: false, 653 data: undefined, 654 closeButton: true, 655 fastIframe: true, 656 open: false, 657 reposition: true, 658 loop: true, 659 slideshow: false, 660 slideshowAuto: true, 661 slideshowSpeed: 2500, 662 slideshowStart: "start slideshow", 663 slideshowStop: "stop slideshow", 664 photoRegex: /\.(gif|png|jp(e|g|eg)|bmp|ico|webp|jxr|svg)((#|\?).*)?$/i, 665 666 // alternate image paths for high-res displays 667 retinaImage: false, 668 retinaUrl: false, 669 retinaSuffix: '@2x.$1', 670 671 // internationalization 672 current: "image {current} of {total}", 673 previous: "previous", 674 next: "next", 675 close: "close", 676 xhrError: "This content failed to load.", 677 imgError: "This image failed to load.", 678 679 // accessbility 680 returnFocus: true, 681 trapFocus: true, 682 683 // callbacks 684 onOpen: false, 685 onLoad: false, 686 onComplete: false, 687 onCleanup: false, 688 onClosed: false, 689 690 rel: function() { 691 return this.rel; 692 }, 693 href: function() { 694 // using this.href would give the absolute url, when the href may have been inteded as a selector (e.g. '#container') 695 return $(this).attr('href'); 696 }, 697 title: function() { 698 return this.title; 699 }, 700 createImg: function() { 701 var img = new Image(); 702 var attrs = $(this).data('cbox-img-attrs'); 703 704 if (typeof attrs === 'object') { 705 $.each(attrs, function(key, val){ 706 img[key] = val; 707 }); 708 } 709 710 return img; 711 }, 712 createIframe: function() { 713 var iframe = document.createElement('iframe'); 714 var attrs = $(this).data('cbox-iframe-attrs'); 715 716 if (typeof attrs === 'object') { 717 $.each(attrs, function(key, val){ 718 iframe[key] = val; 719 }); 720 } 721 722 if ('frameBorder' in iframe) { 723 iframe.frameBorder = 0; 724 } 725 if ('allowTransparency' in iframe) { 726 iframe.allowTransparency = "true"; 727 } 728 iframe.name = (new Date()).getTime(); // give the iframe a unique name to prevent caching 729 iframe.allowFullscreen = true; 730 731 return iframe; 732 } 733 }, 734 735 // Abstracting the HTML and event identifiers for easy rebranding 736 colorbox = 'colorbox', 737 prefix = 'cbox', 738 boxElement = prefix + 'Element', 739 740 // Events 741 event_open = prefix + '_open', 742 event_load = prefix + '_load', 743 event_complete = prefix + '_complete', 744 event_cleanup = prefix + '_cleanup', 745 event_closed = prefix + '_closed', 746 event_purge = prefix + '_purge', 747 748 // Cached jQuery Object Variables 749 $overlay, 750 $box, 751 $wrap, 752 $content, 753 $topBorder, 754 $leftBorder, 755 $rightBorder, 756 $bottomBorder, 757 $related, 758 $window, 759 $loaded, 760 $loadingBay, 761 $loadingOverlay, 762 $title, 763 $current, 764 $slideshow, 765 $next, 766 $prev, 767 $close, 768 $groupControls, 769 $events = $('<a/>'), // $({}) would be prefered, but there is an issue with jQuery 1.4.2 770 771 // Variables for cached values or use across multiple functions 772 settings, 773 interfaceHeight, 774 interfaceWidth, 775 loadedHeight, 776 loadedWidth, 777 index, 778 photo, 779 open, 780 active, 781 closing, 782 loadingTimer, 783 publicMethod, 784 div = "div", 785 requests = 0, 786 previousCSS = {}, 787 init; 788 789 // **************** 790 // HELPER FUNCTIONS 791 // **************** 792 793 // Convenience function for creating new jQuery objects 794 function $tag(tag, id, css) { 795 var element = document.createElement(tag); 796 797 if (id) { 798 element.id = prefix + id; 799 } 800 801 if (css) { 802 element.style.cssText = css; 803 } 804 805 return $(element); 806 } 807 808 // Get the window height using innerHeight when available to avoid an issue with iOS 809 // http://bugs.jquery.com/ticket/6724 810 function winheight() { 811 return window.innerHeight ? window.innerHeight : $(window).height(); 812 } 813 814 function Settings(element, options) { 815 if (options !== Object(options)) { 816 options = {}; 817 } 818 819 this.cache = {}; 820 this.el = element; 821 822 this.value = function(key) { 823 var dataAttr; 824 825 if (this.cache[key] === undefined) { 826 dataAttr = $(this.el).attr('data-cbox-'+key); 827 828 if (dataAttr !== undefined) { 829 this.cache[key] = dataAttr; 830 } else if (options[key] !== undefined) { 831 this.cache[key] = options[key]; 832 } else if (defaults[key] !== undefined) { 833 this.cache[key] = defaults[key]; 834 } 835 } 836 837 return this.cache[key]; 838 }; 839 840 this.get = function(key) { 841 var value = this.value(key); 842 return $.isFunction(value) ? value.call(this.el, this) : value; 843 }; 844 } 845 846 // Determine the next and previous members in a group. 847 function getIndex(increment) { 848 var 849 max = $related.length, 850 newIndex = (index + increment) % max; 851 852 return (newIndex < 0) ? max + newIndex : newIndex; 853 } 854 855 // Convert '%' and 'px' values to integers 856 function setSize(size, dimension) { 857 return Math.round((/%/.test(size) ? ((dimension === 'x' ? $window.width() : winheight()) / 100) : 1) * parseInt(size, 10)); 858 } 859 860 // Checks an href to see if it is a photo. 861 // There is a force photo option (photo: true) for hrefs that cannot be matched by the regex. 862 function isImage(settings, url) { 863 return settings.get('photo') || settings.get('photoRegex').test(url); 864 } 865 866 function retinaUrl(settings, url) { 867 return settings.get('retinaUrl') && window.devicePixelRatio > 1 ? url.replace(settings.get('photoRegex'), settings.get('retinaSuffix')) : url; 868 } 869 870 function trapFocus(e) { 871 if ('contains' in $box[0] && !$box[0].contains(e.target) && e.target !== $overlay[0]) { 872 e.stopPropagation(); 873 $box.focus(); 874 } 875 } 876 877 function setClass(str) { 878 if (setClass.str !== str) { 879 $box.add($overlay).removeClass(setClass.str).addClass(str); 880 setClass.str = str; 881 } 882 } 883 884 function getRelated(rel) { 885 index = 0; 886 887 if (rel && rel !== false && rel !== 'nofollow') { 888 $related = $('.' + boxElement).filter(function () { 889 var options = $.data(this, colorbox); 890 var settings = new Settings(this, options); 891 return (settings.get('rel') === rel); 892 }); 893 index = $related.index(settings.el); 894 895 // Check direct calls to Colorbox. 896 if (index === -1) { 897 $related = $related.add(settings.el); 898 index = $related.length - 1; 899 } 900 } else { 901 $related = $(settings.el); 902 } 903 } 904 905 function trigger(event) { 906 // for external use 907 $(document).trigger(event); 908 // for internal use 909 $events.triggerHandler(event); 910 } 911 912 var slideshow = (function(){ 913 var active, 914 className = prefix + "Slideshow_", 915 click = "click." + prefix, 916 timeOut; 917 918 function clear () { 919 clearTimeout(timeOut); 920 } 921 922 function set() { 923 if (settings.get('loop') || $related[index + 1]) { 924 clear(); 925 timeOut = setTimeout(publicMethod.next, settings.get('slideshowSpeed')); 926 } 927 } 928 929 function start() { 930 $slideshow 931 .html(settings.get('slideshowStop')) 932 .unbind(click) 933 .one(click, stop); 934 935 $events 936 .bind(event_complete, set) 937 .bind(event_load, clear); 938 939 $box.removeClass(className + "off").addClass(className + "on"); 940 } 941 942 function stop() { 943 clear(); 944 945 $events 946 .unbind(event_complete, set) 947 .unbind(event_load, clear); 948 949 $slideshow 950 .html(settings.get('slideshowStart')) 951 .unbind(click) 952 .one(click, function () { 953 publicMethod.next(); 954 start(); 955 }); 956 957 $box.removeClass(className + "on").addClass(className + "off"); 958 } 959 960 function reset() { 961 active = false; 962 $slideshow.hide(); 963 clear(); 964 $events 965 .unbind(event_complete, set) 966 .unbind(event_load, clear); 967 $box.removeClass(className + "off " + className + "on"); 968 } 969 970 return function(){ 971 if (active) { 972 if (!settings.get('slideshow')) { 973 $events.unbind(event_cleanup, reset); 974 reset(); 975 } 976 } else { 977 if (settings.get('slideshow') && $related[1]) { 978 active = true; 979 $events.one(event_cleanup, reset); 980 if (settings.get('slideshowAuto')) { 981 start(); 982 } else { 983 stop(); 984 } 985 $slideshow.show(); 986 } 987 } 988 }; 989 990 }()); 991 992 993 function launch(element) { 994 var options; 995 996 if (!closing) { 997 998 options = $(element).data(colorbox); 999 1000 settings = new Settings(element, options); 1001 1002 getRelated(settings.get('rel')); 1003 1004 if (!open) { 1005 open = active = true; // Prevents the page-change action from queuing up if the visitor holds down the left or right keys. 1006 1007 setClass(settings.get('className')); 1008 1009 // Show colorbox so the sizes can be calculated in older versions of jQuery 1010 $box.css({visibility:'hidden', display:'block', opacity:''}); 1011 1012 $loaded = $tag(div, 'LoadedContent', 'width:0; height:0; overflow:hidden; visibility:hidden'); 1013 $content.css({width:'', height:''}).append($loaded); 1014 1015 // Cache values needed for size calculations 1016 interfaceHeight = $topBorder.height() + $bottomBorder.height() + $content.outerHeight(true) - $content.height(); 1017 interfaceWidth = $leftBorder.width() + $rightBorder.width() + $content.outerWidth(true) - $content.width(); 1018 loadedHeight = $loaded.outerHeight(true); 1019 loadedWidth = $loaded.outerWidth(true); 1020 1021 // Opens inital empty Colorbox prior to content being loaded. 1022 var initialWidth = setSize(settings.get('initialWidth'), 'x'); 1023 var initialHeight = setSize(settings.get('initialHeight'), 'y'); 1024 var maxWidth = settings.get('maxWidth'); 1025 var maxHeight = settings.get('maxHeight'); 1026 1027 settings.w = Math.max((maxWidth !== false ? Math.min(initialWidth, setSize(maxWidth, 'x')) : initialWidth) - loadedWidth - interfaceWidth, 0); 1028 settings.h = Math.max((maxHeight !== false ? Math.min(initialHeight, setSize(maxHeight, 'y')) : initialHeight) - loadedHeight - interfaceHeight, 0); 1029 1030 $loaded.css({width:'', height:settings.h}); 1031 publicMethod.position(); 1032 1033 trigger(event_open); 1034 settings.get('onOpen'); 1035 1036 $groupControls.add($title).hide(); 1037 1038 $box.focus(); 1039 1040 if (settings.get('trapFocus')) { 1041 // Confine focus to the modal 1042 // Uses event capturing that is not supported in IE8- 1043 if (document.addEventListener) { 1044 1045 document.addEventListener('focus', trapFocus, true); 1046 1047 $events.one(event_closed, function () { 1048 document.removeEventListener('focus', trapFocus, true); 1049 }); 1050 } 1051 } 1052 1053 // Return focus on closing 1054 if (settings.get('returnFocus')) { 1055 $events.one(event_closed, function () { 1056 $(settings.el).focus(); 1057 }); 1058 } 1059 } 1060 1061 var opacity = parseFloat(settings.get('opacity')); 1062 $overlay.css({ 1063 opacity: opacity === opacity ? opacity : '', 1064 cursor: settings.get('overlayClose') ? 'pointer' : '', 1065 visibility: 'visible' 1066 }).show(); 1067 1068 if (settings.get('closeButton')) { 1069 $close.html(settings.get('close')).appendTo($content); 1070 } else { 1071 $close.appendTo('<div/>'); // replace with .detach() when dropping jQuery < 1.4 1072 } 1073 1074 load(); 1075 } 1076 } 1077 1078 // Colorbox's markup needs to be added to the DOM prior to being called 1079 // so that the browser will go ahead and load the CSS background images. 1080 function appendHTML() { 1081 if (!$box) { 1082 init = false; 1083 $window = $(window); 1084 $box = $tag(div).attr({ 1085 id: colorbox, 1086 'class': $.support.opacity === false ? prefix + 'IE' : '', // class for optional IE8 & lower targeted CSS. 1087 role: 'dialog', 1088 tabindex: '-1' 1089 }).hide(); 1090 $overlay = $tag(div, "Overlay").hide(); 1091 $loadingOverlay = $([$tag(div, "LoadingOverlay")[0],$tag(div, "LoadingGraphic")[0]]); 1092 $wrap = $tag(div, "Wrapper"); 1093 $content = $tag(div, "Content").append( 1094 $title = $tag(div, "Title"), 1095 $current = $tag(div, "Current"), 1096 $prev = $('<button type="button"/>').attr({id:prefix+'Previous'}), 1097 $next = $('<button type="button"/>').attr({id:prefix+'Next'}), 1098 $slideshow = $('<button type="button"/>').attr({id:prefix+'Slideshow'}), 1099 $loadingOverlay 1100 ); 1101 1102 $close = $('<button type="button"/>').attr({id:prefix+'Close'}); 1103 1104 $wrap.append( // The 3x3 Grid that makes up Colorbox 1105 $tag(div).append( 1106 $tag(div, "TopLeft"), 1107 $topBorder = $tag(div, "TopCenter"), 1108 $tag(div, "TopRight") 1109 ), 1110 $tag(div, false, 'clear:left').append( 1111 $leftBorder = $tag(div, "MiddleLeft"), 1112 $content, 1113 $rightBorder = $tag(div, "MiddleRight") 1114 ), 1115 $tag(div, false, 'clear:left').append( 1116 $tag(div, "BottomLeft"), 1117 $bottomBorder = $tag(div, "BottomCenter"), 1118 $tag(div, "BottomRight") 1119 ) 1120 ).find('div div').css({'float': 'left'}); 1121 1122 $loadingBay = $tag(div, false, 'position:absolute; width:9999px; visibility:hidden; display:none; max-width:none;'); 1123 1124 $groupControls = $next.add($prev).add($current).add($slideshow); 1125 } 1126 if (document.body && !$box.parent().length) { 1127 $(document.body).append($overlay, $box.append($wrap, $loadingBay)); 1128 } 1129 } 1130 1131 // Add Colorbox's event bindings 1132 function addBindings() { 1133 function clickHandler(e) { 1134 // ignore non-left-mouse-clicks and clicks modified with ctrl / command, shift, or alt. 1135 // See: http://jacklmoore.com/notes/click-events/ 1136 if (!(e.which > 1 || e.shiftKey || e.altKey || e.metaKey || e.ctrlKey)) { 1137 e.preventDefault(); 1138 launch(this); 1139 } 1140 } 1141 1142 if ($box) { 1143 if (!init) { 1144 init = true; 1145 1146 // Anonymous functions here keep the public method from being cached, thereby allowing them to be redefined on the fly. 1147 $next.click(function () { 1148 publicMethod.next(); 1149 }); 1150 $prev.click(function () { 1151 publicMethod.prev(); 1152 }); 1153 $close.click(function () { 1154 publicMethod.close(); 1155 }); 1156 $overlay.click(function () { 1157 if (settings.get('overlayClose')) { 1158 publicMethod.close(); 1159 } 1160 }); 1161 1162 // Key Bindings 1163 $(document).bind('keydown.' + prefix, function (e) { 1164 var key = e.keyCode; 1165 if (open && settings.get('escKey') && key === 27) { 1166 e.preventDefault(); 1167 publicMethod.close(); 1168 } 1169 if (open && settings.get('arrowKey') && $related[1] && !e.altKey) { 1170 if (key === 37) { 1171 e.preventDefault(); 1172 $prev.click(); 1173 } else if (key === 39) { 1174 e.preventDefault(); 1175 $next.click(); 1176 } 1177 } 1178 }); 1179 1180 if ($.isFunction($.fn.on)) { 1181 // For jQuery 1.7+ 1182 $(document).on('click.'+prefix, '.'+boxElement, clickHandler); 1183 } else { 1184 // For jQuery 1.3.x -> 1.6.x 1185 // This code is never reached in jQuery 1.9, so do not contact me about 'live' being removed. 1186 // This is not here for jQuery 1.9, it's here for legacy users. 1187 $('.'+boxElement).live('click.'+prefix, clickHandler); 1188 } 1189 } 1190 return true; 1191 } 1192 return false; 1193 } 1194 1195 // Don't do anything if Colorbox already exists. 1196 if ($[colorbox]) { 1197 return; 1198 } 1199 1200 // Append the HTML when the DOM loads 1201 $(appendHTML); 1202 1203 1204 // **************** 1205 // PUBLIC FUNCTIONS 1206 // Usage format: $.colorbox.close(); 1207 // Usage from within an iframe: parent.jQuery.colorbox.close(); 1208 // **************** 1209 1210 publicMethod = $.fn[colorbox] = $[colorbox] = function (options, callback) { 1211 var settings; 1212 var $obj = this; 1213 1214 options = options || {}; 1215 1216 if ($.isFunction($obj)) { // assume a call to $.colorbox 1217 $obj = $('<a/>'); 1218 options.open = true; 1219 } 1220 1221 if (!$obj[0]) { // colorbox being applied to empty collection 1222 return $obj; 1223 } 1224 1225 appendHTML(); 1226 1227 if (addBindings()) { 1228 1229 if (callback) { 1230 options.onComplete = callback; 1231 } 1232 1233 $obj.each(function () { 1234 var old = $.data(this, colorbox) || {}; 1235 $.data(this, colorbox, $.extend(old, options)); 1236 }).addClass(boxElement); 1237 1238 settings = new Settings($obj[0], options); 1239 1240 if (settings.get('open')) { 1241 launch($obj[0]); 1242 } 1243 } 1244 1245 return $obj; 1246 }; 1247 1248 publicMethod.position = function (speed, loadedCallback) { 1249 var 1250 css, 1251 top = 0, 1252 left = 0, 1253 offset = $box.offset(), 1254 scrollTop, 1255 scrollLeft; 1256 1257 $window.unbind('resize.' + prefix); 1258 1259 // remove the modal so that it doesn't influence the document width/height 1260 $box.css({top: -9e4, left: -9e4}); 1261 1262 scrollTop = $window.scrollTop(); 1263 scrollLeft = $window.scrollLeft(); 1264 1265 if (settings.get('fixed')) { 1266 offset.top -= scrollTop; 1267 offset.left -= scrollLeft; 1268 $box.css({position: 'fixed'}); 1269 } else { 1270 top = scrollTop; 1271 left = scrollLeft; 1272 $box.css({position: 'absolute'}); 1273 } 1274 1275 // keeps the top and left positions within the browser's viewport. 1276 if (settings.get('right') !== false) { 1277 left += Math.max($window.width() - settings.w - loadedWidth - interfaceWidth - setSize(settings.get('right'), 'x'), 0); 1278 } else if (settings.get('left') !== false) { 1279 left += setSize(settings.get('left'), 'x'); 1280 } else { 1281 left += Math.round(Math.max($window.width() - settings.w - loadedWidth - interfaceWidth, 0) / 2); 1282 } 1283 1284 if (settings.get('bottom') !== false) { 1285 top += Math.max(winheight() - settings.h - loadedHeight - interfaceHeight - setSize(settings.get('bottom'), 'y'), 0); 1286 } else if (settings.get('top') !== false) { 1287 top += setSize(settings.get('top'), 'y'); 1288 } else { 1289 top += Math.round(Math.max(winheight() - settings.h - loadedHeight - interfaceHeight, 0) / 2); 1290 } 1291 1292 $box.css({top: offset.top, left: offset.left, visibility:'visible'}); 1293 1294 // this gives the wrapper plenty of breathing room so it's floated contents can move around smoothly, 1295 // but it has to be shrank down around the size of div#colorbox when it's done. If not, 1296 // it can invoke an obscure IE bug when using iframes. 1297 $wrap[0].style.width = $wrap[0].style.height = "9999px"; 1298 1299 function modalDimensions() { 1300 $topBorder[0].style.width = $bottomBorder[0].style.width = $content[0].style.width = (parseInt($box[0].style.width,10) - interfaceWidth)+'px'; 1301 $content[0].style.height = $leftBorder[0].style.height = $rightBorder[0].style.height = (parseInt($box[0].style.height,10) - interfaceHeight)+'px'; 1302 } 1303 1304 css = {width: settings.w + loadedWidth + interfaceWidth, height: settings.h + loadedHeight + interfaceHeight, top: top, left: left}; 1305 1306 // setting the speed to 0 if the content hasn't changed size or position 1307 if (speed) { 1308 var tempSpeed = 0; 1309 $.each(css, function(i){ 1310 if (css[i] !== previousCSS[i]) { 1311 tempSpeed = speed; 1312 return; 1313 } 1314 }); 1315 speed = tempSpeed; 1316 } 1317 1318 previousCSS = css; 1319 1320 if (!speed) { 1321 $box.css(css); 1322 } 1323 1324 $box.dequeue().animate(css, { 1325 duration: speed || 0, 1326 complete: function () { 1327 modalDimensions(); 1328 1329 active = false; 1330 1331 // shrink the wrapper down to exactly the size of colorbox to avoid a bug in IE's iframe implementation. 1332 $wrap[0].style.width = (settings.w + loadedWidth + interfaceWidth) + "px"; 1333 $wrap[0].style.height = (settings.h + loadedHeight + interfaceHeight) + "px"; 1334 1335 if (settings.get('reposition')) { 1336 setTimeout(function () { // small delay before binding onresize due to an IE8 bug. 1337 $window.bind('resize.' + prefix, publicMethod.position); 1338 }, 1); 1339 } 1340 1341 if ($.isFunction(loadedCallback)) { 1342 loadedCallback(); 1343 } 1344 }, 1345 step: modalDimensions 1346 }); 1347 }; 1348 1349 publicMethod.resize = function (options) { 1350 var scrolltop; 1351 1352 if (open) { 1353 options = options || {}; 1354 1355 if (options.width) { 1356 settings.w = setSize(options.width, 'x') - loadedWidth - interfaceWidth; 1357 } 1358 1359 if (options.innerWidth) { 1360 settings.w = setSize(options.innerWidth, 'x'); 1361 } 1362 1363 $loaded.css({width: settings.w}); 1364 1365 if (options.height) { 1366 settings.h = setSize(options.height, 'y') - loadedHeight - interfaceHeight; 1367 } 1368 1369 if (options.innerHeight) { 1370 settings.h = setSize(options.innerHeight, 'y'); 1371 } 1372 1373 if (!options.innerHeight && !options.height) { 1374 scrolltop = $loaded.scrollTop(); 1375 $loaded.css({height: "auto"}); 1376 settings.h = $loaded.height(); 1377 } 1378 1379 $loaded.css({height: settings.h}); 1380 1381 if(scrolltop) { 1382 $loaded.scrollTop(scrolltop); 1383 } 1384 1385 publicMethod.position(settings.get('transition') === "none" ? 0 : settings.get('speed')); 1386 } 1387 }; 1388 1389 publicMethod.prep = function (object) { 1390 if (!open) { 1391 return; 1392 } 1393 1394 var callback, speed = settings.get('transition') === "none" ? 0 : settings.get('speed'); 1395 1396 $loaded.remove(); 1397 1398 $loaded = $tag(div, 'LoadedContent').append(object); 1399 1400 function getWidth() { 1401 settings.w = settings.w || $loaded.width(); 1402 settings.w = settings.mw && settings.mw < settings.w ? settings.mw : settings.w; 1403 return settings.w; 1404 } 1405 function getHeight() { 1406 settings.h = settings.h || $loaded.height(); 1407 settings.h = settings.mh && settings.mh < settings.h ? settings.mh : settings.h; 1408 return settings.h; 1409 } 1410 1411 $loaded.hide() 1412 .appendTo($loadingBay.show())// content has to be appended to the DOM for accurate size calculations. 1413 .css({width: getWidth(), overflow: settings.get('scrolling') ? 'auto' : 'hidden'}) 1414 .css({height: getHeight()})// sets the height independently from the width in case the new width influences the value of height. 1415 .prependTo($content); 1416 1417 $loadingBay.hide(); 1418 1419 // floating the IMG removes the bottom line-height and fixed a problem where IE miscalculates the width of the parent element as 100% of the document width. 1420 1421 $(photo).css({'float': 'none'}); 1422 1423 setClass(settings.get('className')); 1424 1425 callback = function () { 1426 var total = $related.length, 1427 iframe, 1428 complete; 1429 1430 if (!open) { 1431 return; 1432 } 1433 1434 function removeFilter() { // Needed for IE8 in versions of jQuery prior to 1.7.2 1435 if ($.support.opacity === false) { 1436 $box[0].style.removeAttribute('filter'); 1437 } 1438 } 1439 1440 complete = function () { 1441 clearTimeout(loadingTimer); 1442 $loadingOverlay.hide(); 1443 trigger(event_complete); 1444 settings.get('onComplete'); 1445 }; 1446 1447 1448 $title.html(settings.get('title')).show(); 1449 $loaded.show(); 1450 1451 if (total > 1) { // handle grouping 1452 if (typeof settings.get('current') === "string") { 1453 $current.html(settings.get('current').replace('{current}', index + 1).replace('{total}', total)).show(); 1454 } 1455 1456 $next[(settings.get('loop') || index < total - 1) ? "show" : "hide"]().html(settings.get('next')); 1457 $prev[(settings.get('loop') || index) ? "show" : "hide"]().html(settings.get('previous')); 1458 1459 slideshow(); 1460 1461 // Preloads images within a rel group 1462 if (settings.get('preloading')) { 1463 $.each([getIndex(-1), getIndex(1)], function(){ 1464 var img, 1465 i = $related[this], 1466 settings = new Settings(i, $.data(i, colorbox)), 1467 src = settings.get('href'); 1468 1469 if (src && isImage(settings, src)) { 1470 src = retinaUrl(settings, src); 1471 img = document.createElement('img'); 1472 img.src = src; 1473 } 1474 }); 1475 } 1476 } else { 1477 $groupControls.hide(); 1478 } 1479 1480 if (settings.get('iframe')) { 1481 1482 iframe = settings.get('createIframe'); 1483 1484 if (!settings.get('scrolling')) { 1485 iframe.scrolling = "no"; 1486 } 1487 1488 $(iframe) 1489 .attr({ 1490 src: settings.get('href'), 1491 'class': prefix + 'Iframe' 1492 }) 1493 .one('load', complete) 1494 .appendTo($loaded); 1495 1496 $events.one(event_purge, function () { 1497 iframe.src = "//about:blank"; 1498 }); 1499 1500 if (settings.get('fastIframe')) { 1501 $(iframe).trigger('load'); 1502 } 1503 } else { 1504 complete(); 1505 } 1506 1507 if (settings.get('transition') === 'fade') { 1508 $box.fadeTo(speed, 1, removeFilter); 1509 } else { 1510 removeFilter(); 1511 } 1512 }; 1513 1514 if (settings.get('transition') === 'fade') { 1515 $box.fadeTo(speed, 0, function () { 1516 publicMethod.position(0, callback); 1517 }); 1518 } else { 1519 publicMethod.position(speed, callback); 1520 } 1521 }; 1522 1523 function load () { 1524 var href, setResize, prep = publicMethod.prep, $inline, request = ++requests; 1525 1526 active = true; 1527 1528 photo = false; 1529 1530 trigger(event_purge); 1531 trigger(event_load); 1532 settings.get('onLoad'); 1533 1534 settings.h = settings.get('height') ? 1535 setSize(settings.get('height'), 'y') - loadedHeight - interfaceHeight : 1536 settings.get('innerHeight') && setSize(settings.get('innerHeight'), 'y'); 1537 1538 settings.w = settings.get('width') ? 1539 setSize(settings.get('width'), 'x') - loadedWidth - interfaceWidth : 1540 settings.get('innerWidth') && setSize(settings.get('innerWidth'), 'x'); 1541 1542 // Sets the minimum dimensions for use in image scaling 1543 settings.mw = settings.w; 1544 settings.mh = settings.h; 1545 1546 // Re-evaluate the minimum width and height based on maxWidth and maxHeight values. 1547 // If the width or height exceed the maxWidth or maxHeight, use the maximum values instead. 1548 if (settings.get('maxWidth')) { 1549 settings.mw = setSize(settings.get('maxWidth'), 'x') - loadedWidth - interfaceWidth; 1550 settings.mw = settings.w && settings.w < settings.mw ? settings.w : settings.mw; 1551 } 1552 if (settings.get('maxHeight')) { 1553 settings.mh = setSize(settings.get('maxHeight'), 'y') - loadedHeight - interfaceHeight; 1554 settings.mh = settings.h && settings.h < settings.mh ? settings.h : settings.mh; 1555 } 1556 1557 href = settings.get('href'); 1558 1559 loadingTimer = setTimeout(function () { 1560 $loadingOverlay.show(); 1561 }, 100); 1562 1563 if (settings.get('inline')) { 1564 var $target = $(href).eq(0); 1565 // Inserts an empty placeholder where inline content is being pulled from. 1566 // An event is bound to put inline content back when Colorbox closes or loads new content. 1567 $inline = $('<div>').hide().insertBefore($target); 1568 1569 $events.one(event_purge, function () { 1570 $inline.replaceWith($target); 1571 }); 1572 1573 prep($target); 1574 } else if (settings.get('iframe')) { 1575 // IFrame element won't be added to the DOM until it is ready to be displayed, 1576 // to avoid problems with DOM-ready JS that might be trying to run in that iframe. 1577 prep(" "); 1578 } else if (settings.get('html')) { 1579 prep(settings.get('html')); 1580 } else if (isImage(settings, href)) { 1581 1582 href = retinaUrl(settings, href); 1583 1584 photo = settings.get('createImg'); 1585 1586 $(photo) 1587 .addClass(prefix + 'Photo') 1588 .bind('error.'+prefix,function () { 1589 prep($tag(div, 'Error').html(settings.get('imgError'))); 1590 }) 1591 .one('load', function () { 1592 if (request !== requests) { 1593 return; 1594 } 1595 1596 // A small pause because some browsers will occassionaly report a 1597 // img.width and img.height of zero immediately after the img.onload fires 1598 setTimeout(function(){ 1599 var percent; 1600 1601 if (settings.get('retinaImage') && window.devicePixelRatio > 1) { 1602 photo.height = photo.height / window.devicePixelRatio; 1603 photo.width = photo.width / window.devicePixelRatio; 1604 } 1605 1606 if (settings.get('scalePhotos')) { 1607 setResize = function () { 1608 photo.height -= photo.height * percent; 1609 photo.width -= photo.width * percent; 1610 }; 1611 if (settings.mw && photo.width > settings.mw) { 1612 percent = (photo.width - settings.mw) / photo.width; 1613 setResize(); 1614 } 1615 if (settings.mh && photo.height > settings.mh) { 1616 percent = (photo.height - settings.mh) / photo.height; 1617 setResize(); 1618 } 1619 } 1620 1621 if (settings.h) { 1622 photo.style.marginTop = Math.max(settings.mh - photo.height, 0) / 2 + 'px'; 1623 } 1624 1625 if ($related[1] && (settings.get('loop') || $related[index + 1])) { 1626 photo.style.cursor = 'pointer'; 1627 1628 $(photo).bind('click.'+prefix, function () { 1629 publicMethod.next(); 1630 }); 1631 } 1632 1633 photo.style.width = photo.width + 'px'; 1634 photo.style.height = photo.height + 'px'; 1635 prep(photo); 1636 }, 1); 1637 }); 1638 1639 photo.src = href; 1640 1641 } else if (href) { 1642 $loadingBay.load(href, settings.get('data'), function (data, status) { 1643 if (request === requests) { 1644 prep(status === 'error' ? $tag(div, 'Error').html(settings.get('xhrError')) : $(this).contents()); 1645 } 1646 }); 1647 } 1648 } 1649 1650 // Navigates to the next page/image in a set. 1651 publicMethod.next = function () { 1652 if (!active && $related[1] && (settings.get('loop') || $related[index + 1])) { 1653 index = getIndex(1); 1654 launch($related[index]); 1655 } 1656 }; 1657 1658 publicMethod.prev = function () { 1659 if (!active && $related[1] && (settings.get('loop') || index)) { 1660 index = getIndex(-1); 1661 launch($related[index]); 1662 } 1663 }; 1664 1665 // Note: to use this within an iframe use the following format: parent.jQuery.colorbox.close(); 1666 publicMethod.close = function () { 1667 if (open && !closing) { 1668 1669 closing = true; 1670 open = false; 1671 trigger(event_cleanup); 1672 settings.get('onCleanup'); 1673 $window.unbind('.' + prefix); 1674 $overlay.fadeTo(settings.get('fadeOut') || 0, 0); 1675 1676 $box.stop().fadeTo(settings.get('fadeOut') || 0, 0, function () { 1677 $box.hide(); 1678 $overlay.hide(); 1679 trigger(event_purge); 1680 $loaded.remove(); 1681 1682 setTimeout(function () { 1683 closing = false; 1684 trigger(event_closed); 1685 settings.get('onClosed'); 1686 }, 1); 1687 }); 1688 } 1689 }; 1690 1691 // Removes changes Colorbox made to the document, but does not remove the plugin. 1692 publicMethod.remove = function () { 1693 if (!$box) { return; } 1694 1695 $box.stop(); 1696 $[colorbox].close(); 1697 $box.stop(false, true).remove(); 1698 $overlay.remove(); 1699 closing = false; 1700 $box = null; 1701 $('.' + boxElement) 1702 .removeData(colorbox) 1703 .removeClass(boxElement); 1704 1705 $(document).unbind('click.'+prefix).unbind('keydown.'+prefix); 1706 }; 1707 1708 // A method for fetching the current element Colorbox is referencing. 1709 // returns a jQuery object. 1710 publicMethod.element = function () { 1711 return $(settings.el); 1712 }; 1713 1714 publicMethod.settings = defaults; 1715 1716}(jQuery, document, window));
1717; 1718(function ($) { 1719 Drupal.behaviors.pdf_reader_colorbox = { 1720 attach: function(context, settings) { 1721 $(".iframe").colorbox({iframe:true, width:"80%", height:"80%"}); 1722 }}})(jQuery);; 1723/** 1724 * @file 1725 * Defines Javascript behaviors for MegaMenu frontend. 1726 */ 1727 1728(function ($, Drupal, drupalSettings, once) { 1729 "use strict"; 1730 1731 Drupal.TBMegaMenu = Drupal.TBMegaMenu || {}; 1732 1733 Drupal.TBMegaMenu.oldWindowWidth = 0; 1734 Drupal.TBMegaMenu.displayedMenuMobile = false; 1735 Drupal.TBMegaMenu.supportedScreens = [980]; 1736 Drupal.TBMegaMenu.focusableElements = 'a:not([disabled]), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), details:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'; 1737 Drupal.TBMegaMenu.menuResponsive = function () { 1738 var windowWidth = window.innerWidth ? window.innerWidth : $(window).width(); 1739 var navCollapse = $('.tb-megamenu').children('.nav-collapse'); 1740 if (windowWidth < Drupal.TBMegaMenu.supportedScreens[0]) { 1741 navCollapse.addClass('collapse'); 1742 if (Drupal.TBMegaMenu.displayedMenuMobile) { 1743 navCollapse.css({height: 'auto', overflow: 'visible'}); 1744 } 1745 else { 1746 navCollapse.css({height: 0, overflow: 'hidden'}); 1747 } 1748 } 1749 else { 1750 // If width of window is greater than 980 (supported screen). 1751 navCollapse.removeClass('collapse'); 1752 if (navCollapse.height() <= 0) { 1753 navCollapse.css({height: 'auto', overflow: 'visible'}); 1754 } 1755 } 1756 }; 1757 1758 Drupal.TBMegaMenu.focusNextPrevElement = function (direction) { 1759 // Add all the elements we want to include in our selection 1760 var $current = $(document.activeElement); 1761 1762 if ($current.length) { 1763 var $focusable = $(Drupal.TBMegaMenu.focusableElements).filter(function () { 1764 var $this = $(this); 1765 return $this.closest('.tb-megamenu-subnav').length === 0 && $this.is(':visible'); 1766 }) 1767 1768 var index = $focusable.index($current); 1769 if (index > -1) { 1770 if (direction === 'next') { 1771 var nextElement = $focusable[index + 1] || $focusable[0]; 1772 } 1773 else { 1774 var nextElement = $focusable[index - 1] || $focusable[0]; 1775 } 1776 1777 nextElement.focus(); 1778 } 1779 } 1780 } 1781 1782 1783 Drupal.behaviors.tbMegaMenuAction = { 1784 attach: function (context, settings) { 1785 1786 $(once('tb-megamenu', '.tb-megamenu', context)).each(function () { 1787 1788 /* Keyboard Control Setup */ 1789 // Semi-Global Variables 1790 var navParent = document.querySelector('.tb-megamenu'), 1791 linkArray = new Array(), 1792 curPos = new Array(-1,-1,-1); 1793 1794 // Each Top-Level Link 1795 $(this).find('.level-1').children('a, span').not('.mobile-only').each(function (i, toplink) { 1796 linkArray[i] = new Array(); 1797 1798 // Add Link to Array 1799 linkArray[i][-1] = toplink; 1800 1801 // Determine Coordinates 1802 $(toplink).data({ coordinate: [i, -1] }); 1803 1804 // Each Column 1805 $(toplink).next().children().children().children('.mega-col-nav').each(function (j, column) { 1806 // Only add to the linkArray if menu items exist. 1807 // TODO - this does not allow for tabbing to links in blocks, only menu item links. 1808 if ($(column).find(Drupal.TBMegaMenu.focusableElements).length > 0) { 1809 1810 linkArray[i][j] = new Array(); 1811 1812 // Each Link 1813 $(column).find(Drupal.TBMegaMenu.focusableElements).each(function (k, sublink) { 1814 1815 // Add Link to Array 1816 linkArray[i][j][k] = sublink; 1817 1818 // Determine Coordinates 1819 $(sublink).data({ coordinate: [i, j, k] }); 1820 }); // each link 1821 } 1822 1823 }); // each column 1824 1825 }); // each top-level link 1826 1827 // Update Position on Focus 1828 $(this).find(Drupal.TBMegaMenu.focusableElements).focus(function () { 1829 curPos = $(this).data('coordinate'); 1830 }); 1831 1832 // Key Pressed 1833 function keydownEvent(k) { 1834 // Determine Key 1835 switch(k.keyCode) { 1836 1837 // TAB 1838 case 9: 1839 k.preventDefault(); 1840 nav_tab(k); 1841 break; 1842 1843 // RETURN 1844 case 13: 1845 nav_open_link(); 1846 break; 1847 1848 // ESC 1849 case 27: 1850 nav_esc(); 1851 break; 1852 1853 // LEFT 1854 case 37: 1855 k.preventDefault(); 1856 nav_left(); 1857 break; 1858 1859 // UP 1860 case 38: 1861 k.preventDefault(); 1862 nav_up(); 1863 break; 1864 1865 // RIGHT 1866 case 39: 1867 k.preventDefault(); 1868 nav_right(); 1869 break; 1870 1871 // DOWN 1872 case 40: 1873 k.preventDefault(); 1874 nav_down(); 1875 break; 1876 1877 // HOME 1878 case 36: 1879 nav_home(); 1880 break; 1881 1882 // END 1883 case 35: 1884 nav_end(); 1885 break; 1886 1887 // Else 1888 default:
1889 // Do nothing 1890 1891 } // determine key 1892 } // keydownEvent 1893 1894 /* Keypress Functions */ 1895 // Tab 1896 function nav_tab(k) { 1897 if (nav_is_toplink()) { 1898 if (k.shiftKey) { 1899 nav_prev_toplink(); 1900 } 1901 else { 1902 nav_next_toplink(); 1903 } 1904 } 1905 else { 1906 if (k.shiftKey) { 1907 nav_up(); 1908 } 1909 else { 1910 nav_down(); 1911 } 1912 } 1913 } 1914 1915 // Open Link 1916 function nav_open_link() { 1917 linkArray[curPos[0]][curPos[1]][curPos[2]].click(); 1918 } 1919 1920 // Escape 1921 function nav_esc() { 1922 nav_close_megamenu(); 1923 } 1924 1925 // Left 1926 function nav_left() { 1927 if (nav_is_toplink()) { 1928 nav_prev_toplink(); 1929 } 1930 else { 1931 nav_prev_column(); 1932 } 1933 } 1934 1935 // Right 1936 function nav_right() { 1937 if (nav_is_toplink()) { 1938 nav_next_toplink(); 1939 } 1940 else { 1941 nav_next_column(); 1942 } 1943 } 1944 1945 // Up 1946 function nav_up() { 1947 if (nav_is_toplink()) { 1948 nav_prev_toplink(); 1949 } 1950 else { 1951 if (linkArray[curPos[0]][curPos[1]][curPos[2] - 1]) { 1952 // If the previous link in the array is hidden (ie, it's in a 1953 // submenu that is not currently expanded), then skip to the next 1954 // item in the array until we find one that's visible. 1955 if ($(linkArray[curPos[0]][curPos[1]][curPos[2] - 1]).is(':visible')) { 1956 linkArray[curPos[0]][curPos[1]][curPos[2] - 1].focus(); 1957 } 1958 else { 1959 curPos = [curPos[0], curPos[1], curPos[2] - 1]; 1960 nav_up(); 1961 } 1962 } 1963 else { 1964 nav_prev_column(); 1965 } 1966 } 1967 } 1968 1969 // Down 1970 function nav_down() { 1971 if (nav_is_toplink()) { 1972 nav_next_column(); 1973 } 1974 else { 1975 if (linkArray[curPos[0]][curPos[1]][curPos[2] + 1]) { 1976 linkArray[curPos[0]][curPos[1]][curPos[2] + 1].focus(); 1977 } 1978 else { 1979 nav_next_column(); 1980 } 1981 } 1982 } 1983 1984 // Home Button 1985 function nav_home() { 1986 if (nav_is_toplink()) { 1987 linkArray[0][-1].focus(); 1988 } 1989 else { 1990 linkArray[curPos[0]][0][0].focus(); 1991 } 1992 } 1993 1994 // End Button 1995 function nav_end() { 1996 if (nav_is_toplink()) { 1997 linkArray.slice(-1)[0][-1].focus(); 1998 } 1999 else { 2000 linkArray[curPos[0]].slice(-1)[0].slice(-1)[0].focus(); 2001 } 2002 } 2003 2004 /* Helper Functions */ 2005 // Determine Link Level 2006 function nav_is_toplink() { 2007 return (curPos[1] < 0); 2008 } 2009 2010 // Close Mega Menu 2011 function nav_close_megamenu() { 2012 $('.tb-megamenu .open').removeClass('open'); 2013 ariaCheck(); 2014 } 2015 2016 // Next Toplink 2017 function nav_next_toplink() { 2018 if (linkArray[curPos[0] + 1]) { 2019 linkArray[curPos[0] + 1][-1].focus(); 2020 } 2021 else { 2022 nav_close_megamenu(); 2023 2024 // Focus on the next element. 2025 Drupal.TBMegaMenu.focusNextPrevElement('next'); 2026 } 2027 } 2028 2029 // Previous Toplink 2030 function nav_prev_toplink() { 2031 if (linkArray[curPos[0] - 1]) { 2032 linkArray[curPos[0] - 1][-1].focus(); 2033 } 2034 else { 2035 // Focus on the previous element. 2036 Drupal.TBMegaMenu.focusNextPrevElement('prev'); 2037 } 2038 } 2039 2040 // Previous Column 2041 function nav_prev_column() { 2042 if (linkArray[curPos[0]][curPos[1] - 1][0]) { 2043 linkArray[curPos[0]][curPos[1] - 1][0].focus(); 2044 } 2045 else { 2046 nav_parent_toplink(); 2047 } 2048 } 2049 2050 // Next Column 2051 function nav_next_column() { 2052 if (linkArray[curPos[0]][curPos[1] + 1]) { 2053 linkArray[curPos[0]][curPos[1] + 1][0].focus(); 2054 } 2055 else { 2056 nav_parent_toplink(); 2057 } 2058 } 2059 2060 // Go to Parent Toplink 2061 function nav_parent_toplink() { 2062 linkArray[curPos[0]][-1].focus(); 2063 } 2064 2065 var ariaCheck = function () { 2066 $("li.tb-megamenu-item", this).each(function () { 2067 if ($(this).is('.mega-group')) { 2068 // Mega menu item has mega class (it's a true mega menu) 2069 if (!$(this).parents().is('.open')) { 2070 // Mega menu item has mega class and its ancestor is closed, so apply appropriate ARIA attributes 2071 $(this).children().attr('aria-expanded', 'false'); 2072 } 2073 else if ($(this).parents().is('.open')) { 2074 // Mega menu item has mega class and its ancestor is o
2074pen, so apply appropriate ARIA attributes 2075 $(this).children().attr('aria-expanded', 'true'); 2076 } 2077 } 2078 else if ($(this).is('.dropdown') || $(this).is('.dropdown-submenu')) { 2079 // Mega menu item has dropdown (it's a flyout menu) 2080 if (!$(this).is('.open')) { 2081 // Mega menu item has dropdown class and is closed, so apply appropriate ARIA attributes 2082 $(this).children().attr('aria-expanded', 'false'); 2083 } 2084 else if ($(this).is('.open')) { 2085 // Mega menu item has dropdown class and is open, so apply appropriate ARIA attributes 2086 $(this).children().attr('aria-expanded', 'true'); 2087 } 2088 } 2089 else { 2090 // Mega menu item is neither a mega or dropdown class, so remove ARIA attributes (it doesn't have children) 2091 $(this).children().removeAttr('aria-expanded'); 2092 } 2093 }); 2094 }; 2095 2096 var showMenu = function ($subMenu, mm_timeout) { 2097 if ($subMenu.hasClass('mega')) { 2098 $subMenu.addClass('animating'); 2099 clearTimeout($subMenu.data('animatingTimeout')); 2100 $subMenu.data('animatingTimeout', setTimeout(function () { 2101 $subMenu.removeClass('animating') 2102 }, mm_timeout)); 2103 clearTimeout($subMenu.data('hoverTimeout')); 2104 $subMenu.data('hoverTimeout', setTimeout(function () { 2105 $subMenu.addClass('open'); 2106 ariaCheck(); 2107 }, 100)); 2108 } 2109 else { 2110 clearTimeout($subMenu.data('hoverTimeout')); 2111 $subMenu.data('hoverTimeout', 2112 setTimeout(function () { 2113 $subMenu.addClass('open'); 2114 ariaCheck(); 2115 }, 100)); 2116 } 2117 }; 2118 2119 var hideMenu = function ($subMenu, mm_timeout) { 2120 $subMenu.children('.dropdown-toggle').attr('aria-expanded', 'false'); 2121 if ($subMenu.hasClass('mega')) { 2122 $subMenu.addClass('animating'); 2123 clearTimeout($subMenu.data('animatingTimeout')); 2124 $subMenu.data('animatingTimeout', setTimeout(function () { 2125 $subMenu.removeClass('animating') 2126 }, mm_timeout)); 2127 clearTimeout($subMenu.data('hoverTimeout')); 2128 $subMenu.data('hoverTimeout', setTimeout(function () { 2129 $subMenu.removeClass('open'); 2130 ariaCheck(); 2131 }, 100)); 2132 } 2133 else { 2134 clearTimeout($subMenu.data('hoverTimeout')); 2135 $subMenu.data('hoverTimeout', setTimeout(function () { 2136 $subMenu.removeClass('open'); 2137 ariaCheck(); 2138 }, 100)); 2139 } 2140 }; 2141 2142 $('.tb-megamenu-button', this).click(function () { 2143 if (parseInt($(this).parent().children('.nav-collapse').height())) { 2144 $(this).parent().children('.nav-collapse').css({height: 0, overflow: 'hidden'}); 2145 Drupal.TBMegaMenu.displayedMenuMobile = false; 2146 } 2147 else { 2148 $(this).parent().children('.nav-collapse').css({height: 'auto', overflow: 'visible'}); 2149 Drupal.TBMegaMenu.displayedMenuMobile = true; 2150 } 2151 }); 2152 2153 var isTouch = window.matchMedia('(pointer: coarse)').matches; 2154 2155 if (!isTouch) { 2156 var mm_duration = 0; 2157 2158 $('.tb-megamenu', context).each(function () { 2159 if ($(this).data('duration')) { 2160 mm_duration = $(this).data('duration'); 2161 } 2162 }); 2163 2164 var mm_timeout = mm_duration ? 100 + mm_duration : 500; 2165 $('.nav > li, li.mega', context).bind('mouseenter', function (event) { 2166 showMenu($(this), mm_timeout); 2167 }); 2168 2169 $('.nav > li > .dropdown-toggle, li.mega > .dropdown-toggle', context).bind('focus', function (event) { 2170 var $this = $(this); 2171 var $subMenu = $this.closest('li'); 2172 showMenu($subMenu, mm_timeout); 2173 // If the focus moves outside of the subMenu, close it. 2174 $(document).bind('focusin', function (event) { 2175 if ($subMenu.has(event.target).length) { 2176 return; 2177 } 2178 $(document).unbind(event); 2179 hideMenu($subMenu, mm_timeout); 2180 }); 2181 }); 2182 2183 $('.nav > li, li.mega', context).bind('mouseleave', function (event) { 2184 hideMenu($(this), mm_timeout); 2185 }); 2186 2187 /** 2188 * Allow tabbing by appending the open class. 2189 * Works in tandem with CSS changes that utilize opacity rather than 2190 * display none 2191 */ 2192 // If the selected anchor is not in the TB Megamenu, remove all "open" 2193 // class occurrences 2194 $('a, span').focus(function (event) { 2195 if (!$(this).parent().hasClass('tb-megamenu-item') && !$(this).parents('.tb-megamenu-block').length) {
2196 nav_close_megamenu(); 2197 } 2198 }); 2199 2200 $('.nav > li > a, li.mega > a').focus(function (event) { 2201 // Remove all occurrences of "open" from other menu trees 2202 var siblings = $(this).parents('.tb-megamenu-item').siblings(); 2203 // var siblings = $(this).closest('.tb-megamenu-item.level-1').siblings(); 2204 $.each(siblings, function (i, v) { 2205 var cousins = $(v).find('.open'); 2206 $.each(cousins, function (index, value) { 2207 $(value).removeClass('open'); 2208 ariaCheck($(this)); 2209 }); 2210 $(v).removeClass('open'); 2211 ariaCheck(); 2212 }); 2213 // Open the submenu if the selected item has one 2214 if ($(this).next(".tb-megamenu-submenu").length > 0) { 2215 if (!$(this).parent().hasClass("open")) { 2216 $(this).parent().addClass("open"); 2217 } 2218 } 2219 // If the anchor's top-level parent is not open, open it 2220 if (!$(this).closest('.tb-megamenu-item.dropdown').hasClass('open') && $(this).closest('.tb-megamenu-item.dropdown').find('.tb-megamenu-submenu').length > 0) { 2221 $(this).closest('.tb-megamenu-item.dropdown').addClass('open'); 2222 ariaCheck(); 2223 } 2224 // If anchor's parent submenus are not open, open them 2225 var parents = $(this).parents('.tb-megamenu-item.dropdown-submenu'); 2226 $.each(parents, function (i, v) { 2227 if (!$(v).hasClass('open')) { 2228 $(v).addClass('open'); 2229 ariaCheck(); 2230 } 2231 }); 2232 }); 2233 } 2234 2235 // Define actions for touch devices. 2236 var createTouchMenu = function (items) { 2237 items.children("a, span").each(function () { 2238 var $item = $(this); 2239 var tbitem = $(this).parent(); 2240 2241 $item.click(function (event) { 2242 // If the menu link has already been clicked once... 2243 if ($item.hasClass("tb-megamenu-clicked")) { 2244 var $uri = $item.attr("href"); 2245 2246 // If the menu link has a URI, go to the link. 2247 // <nolink> menu items will not have a URI. 2248 if ($uri) { 2249 window.location.href = $uri; 2250 } 2251 else { 2252 $item.removeClass("tb-megamenu-clicked"); 2253 hideMenu(tbitem, mm_timeout); 2254 } 2255 } 2256 else { 2257 event.preventDefault(); 2258 2259 // Hide any already open menus. 2260 nav_close_megamenu(); 2261 $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked"); 2262 2263 // Open the submenu. 2264 $item.addClass("tb-megamenu-clicked"); 2265 showMenu(tbitem, mm_timeout); 2266 } 2267 }); 2268 }); 2269 2270 // Anytime there's a click outside the menu, close the menu. 2271 $(document).on('click', function (event) { 2272 if ($(event.target).closest('.tb-megamenu-nav').length === 0) { 2273 nav_close_megamenu(); 2274 $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked"); 2275 }; 2276 }) 2277 }; 2278 2279 if (isTouch) { 2280 createTouchMenu($(".tb-megamenu ul.nav li.mega", context).has(".dropdown-menu")); 2281 }; 2282 2283 $(window).on('load resize', function () { 2284 var windowWidth = window.innerWidth ? window.innerWidth : $(window).width(); 2285 if (windowWidth != Drupal.TBMegaMenu.oldWindowWidth) { 2286 Drupal.TBMegaMenu.oldWindowWidth = windowWidth; 2287 Drupal.TBMegaMenu.menuResponsive(); 2288 2289 if (windowWidth >= Drupal.TBMegaMenu.supportedScreens[0]) { 2290 navParent.addEventListener('keydown',keydownEvent); 2291 } 2292 else { 2293 navParent.removeEventListener('keydown',keydownEvent); 2294 } 2295 } 2296 }); 2297 2298 }); 2299 2300 } 2301 } 2302})(jQuery, Drupal, drupalSettings, once); 2303; 2304((Drupal) => { 2305 'use strict'; 2306 2307 /** 2308 * Factory for generating alert classes for different callback styles. 2309 * 2310 * Each alert style's ticker class is responsible for registering its type 2311 * with this factory. 2312 * 2313 * @type {{get(*): *, register(*, *): this}} 2314 */ 2315 Drupal.alertFactory = (function () { 2316 2317 /** 2318 * Object of factory callbacks for each alert type, keyed by their 2319 * respective alert style names. 2320 * 2321 * @type {{}} 2322 */
2323 let callbacks = {}; 2324 2325 /** 2326 * Return a factory object for registering and retrieving ticker classes. 2327 */ 2328 return { 2329 2330 /** 2331 * Register's an alert style's factory callback. 2332 * 2333 * @param {string} type 2334 * The name of the alert style for which to register a factory callback. 2335 * @param {function} callback 2336 * The callback to invoke for generating an alert display class for this 2337 * alert style. 2338 * @returns {Drupal.alertFactory} 2339 * Reference to self. 2340 */ 2341 register(type, callback) { 2342 if (typeof callback === 'function') { 2343 callbacks[type] = callback; 2344 } 2345 return this; 2346 }, 2347 2348 /** 2349 * Returns the factory callback for the specified alert style. 2350 * 2351 * @param {string} type 2352 * The name of the alert style for which to return a factory callback. 2353 * @returns {function} 2354 * The factory callback that will return the ticker class for the 2355 * specified ticker style. 2356 */ 2357 get(type) { 2358 return callbacks[type]; 2359 } 2360 } 2361 })(); 2362})(Drupal); 2363; 2364((Drupal, undefined) => { 2365 'use strict'; 2366 2367 /** 2368 * Represents a single, ticker-style alerts display. 2369 */ 2370 class AlertsTicker { 2371 2372 /** 2373 * If TRUE, the current ticker is actively running. 2374 * 2375 * @type {boolean} 2376 */ 2377 #running = false; 2378 2379 /** 2380 * Index of the alert that is currently displayed or will be displayed when 2381 * the ticker starts. 2382 * 2383 * @type {number} 2384 */ 2385 #current = 0; 2386 2387 /** 2388 * The ID of the currently running setTimeout() invocation. 2389 * 2390 * @type {int} 2391 */ 2392 #timeoutId; 2393 2394 /** 2395 * Reference to the promise chain that controls the ticker animations. 2396 * 2397 * @type {Promise} 2398 */ 2399 animationChain; 2400 2401 /** 2402 * True if the next/prev pagers should do nothing (e.g. if the ticker is 2403 * currently advancing to the next alert item). 2404 * 2405 * @type {boolean} 2406 */ 2407 pagerLocked = false; 2408 2409 /** 2410 * Constructs a new AlertsTicker object. 2411 * 2412 * @param {HTMLElement} ticker 2413 * Reference to the outermost element that represents this ticker display. 2414 * @param {{}} selectors 2415 * Object of selectors for identifying: 2416 * - The element that acts as the direct container for the individual 2417 * alert items. 2418 * - The counter element. 2419 * - The clickable element to display the previous alert. 2420 * - The clickable element to display the next alert. 2421 * @param {{}} alerts 2422 * An object whose keys are HTML IDs that identify individual alert items, 2423 * and whose values are each objects containing the following configuration 2424 * data per alert item: 2425 * - delay: The number of milliseconds that a given alert item should be 2426 * displayed before advancing to the next alert item. 2427 */ 2428 constructor(ticker, selectors, alerts, ) { 2429 this.ticker = ticker; 2430 this.container = document.querySelector(selectors.container); 2431 this.counter = document.querySelector(selectors.counter); 2432 this.pager = { 2433 'prev': document.querySelector(selectors.prev), 2434 'next': document.querySelector(selectors.next) 2435 }; 2436 2437 /** 2438 * Set up AlertItem objects, each with doubly-linked references. 2439 */ 2440 this.alerts = []; 2441 Object.keys(alerts).forEach(id => { 2442 const 2443 alert = new AlertItem(id, alerts[id], this), 2444 sequence = this.alerts.push(alert) 2445 ; 2446 alert.sequence = sequence; 2447 if (sequence > 1) { 2448 // Create linked references between immediate siblings. 2449 const prev = this.alerts[sequence - 2]; 2450 alert.prev = prev; 2451 prev.next = alert; 2452 } 2453 }); 2454 // Make the associations circular. 2455 this.alerts[ this.#lastIndex() ].next = this.alerts[0]; 2456 this.alerts[0].prev = this.alerts[ this.#lastIndex() ]; 2457 2458 /** 2459 * Set up prev/next event handlers. 2460 */ 2461 this.pager.next.onclick = e => { 2462 if (this.pagerLocked) { 2463 return; 2464 } 2465 this.animationChain = this.animationChain 2466 .then(() => this.cancel()) 2467 .then(() => this.#doAdvance()); 2468 } 2469 this.pager.prev.onclick = e => { 2470 if (this.pagerLocked) { 2471 return; 2472 } 2473 this.animationChain = this.animationChain 2474 .then(() => this.cancel()) 2475 .then(() => this.#doAdvance(true)); 2476 } 2477 } 2478 2479 /** 2480 * Returns a reference to the current alert item. 2481 * 2482 * @returns {AlertItem} 2483 */ 2484 current () { 2485 return this.alerts[this.#current]; 2486 } 2487 2488 /** 2489 * Advances the current marker to the next alert item in sequence and 2490 * returns a reference to that alert item. 2491 * 2492 * @returns {AlertItem} 2493 */ 2494 next () { 2495 this.#current = (this.#current < this.#lastIndex()) 2496 ? this.#current + 1 2497 : 0; 2498 return this.current(); 2499 } 2500 2501 /** 2502 * Advances the current marker to the previous alert item in sequence and 2503 * returns a reference to that alert item. 2504 * 2505 * @returns {AlertItem} 2506 */ 2507 prev () { 2508 this.#current = (this.#current > 0) 2509 ? this.#current - 1 2510 : this.#lastIndex(); 2511 return this.current(); 2512 } 2513 2514 /** 2515 * Calls .next() or .prev(), based on the value of reverse, and returns the 2516 * resultant value. 2517 * 2518 * @param reverse 2519 * TRUE if .prev() should be returned. Otherwise, .next() will be returned. 2520 * @returns {AlertItem} 2521 * Reference to the newly queued AlertItem. 2522 */ 2523 #page (reverse = false) { 2524 return reverse ? this.prev() : this.next(); 2525 } 2526 2527 /** 2528 * Begin the ticker animation sequence. 2529 * 2530 * @returns {AlertsTicker} 2531 * Reference to self. 2532 */ 2533 start () { 2534 if (this.#running) { 2535 return this; 2536 } 2537 this.animationChain = new Promise((resolve, reject) => { 2538 this.current().display() 2539 resolve(this); 2540 }).then(() => this.advance()); 2541 this.#running = true; 2542 return this; 2543 } 2544 2545 /** 2546 * Sets an animation timer for the next ticker transition and returns the 2547 * promise that will resolve when the timer expires. 2548 * 2549 * @returns {Promise<unknown>} 2550 * A promise that resolves when the timer expires. 2551 */ 2552 advance () { 2553 return new Promise((resolve, reject) => { 2554 this.#timeoutId = setTimeout(() => { 2555 this.#doAdvance(); 2556 return resolve(this.current()); 2557 }, this.current().settings.delay) 2558 }); 2559 } 2560 2561 /** 2562 * Returns a promise that resolves by hiding the current alert and advancing 2563 * the next alert into the display. 2564 * 2565 * @param reverse 2566 * True if the previous alert should be advanced. Otherwise, the next alert 2567 * will advance. 2568 * @returns {Promise<unknown>} 2569 * A promise that resolves once the transition sequence has been inited. 2570 */ 2571 #doAdvance (reverse = false) { 2572 return new Promise((resolve, reject) => { 2573 this.current().hide(reverse) 2574 .then(() => this.#page(reverse).display(reverse)) 2575 .then(() => this.advance()); 2576 resolve(this.current()); 2577 }); 2578 } 2579 2580 /** 2581 * Clears the current setTimeout() invocation and returns an immediately 2582 * resolved promise. 2583 * 2584 * @returns {Promise<unknown>} 2585 */ 2586 cancel () { 2587 clearTimeout(this.#timeoutId); 2588 return new Promise((resolve, reject) => { 2589 resolve(this.current()); 2590 }); 2591 } 2592 2593 /** 2594 * Returns an immediately resolved promise that sets the current pager 2595 * sequence value in the ticker's pager. 2596 * 2597 * @returns {AlertsTicker} 2598 * Reference to self. 2599 */ 2600 setSequence () { 2601 this.counter.textContent = this.current().sequence 2602 return this; 2603 } 2604 2605 /** 2606 * Returns the ordinal index number that represents the last alert in the 2607 * alerts[] property array. 2608 * 2609 * @returns {number} 2610 */ 2611 #lastIndex () { 2612 return this.alerts.length - 1; 2613 } 2614 2615 } 2616 2617 /** 2618 * Represents a single alert display item. 2619 */ 2620 class AlertItem { 2621 2622 /** 2623 * The render classes to apply to the alert item's element on every class 2624 * name change. 2625 * 2626 * @type {[]} 2627 */ 2628 #baseClasses = []; 2629 2630 /** 2631 * The sequence number of this alert item. 2632 * 2633 * @type {number} 2634 */ 2635 sequence; 2636 2637 /** 2638 * Reference to the AlertItem that precedes this AlertItem in the sequence. 2639 * 2640 * @type {AlertItem} 2641 */ 2642 prev; 2643 2644 /** 2645 * Reference to the AlertItem that follows this AlertItem in the sequence. 2646 * 2647 * @type {AlertItem} 2648 */ 2649 next; 2650 2651 /** 2652 * Constructs a new AlertItem object. 2653 * 2654 * @param {string} id 2655 * The HTML ID of the element that this alert item represents. 2656 * @param {[]} settings 2657 * An object of settings, with the following key/value pairs: 2658 * - delay: The number of milliseconds to display this Alert before 2659 * cycling to the next alert in sequence. 2660 * @param {AlertsTicker} parent 2661 * Reference to the parent ticker object. 2662 */ 2663 constructor(id, settings, parent) { 2664 this.parent = parent; 2665 this.id = id; 2666 this.element = document.getElementById(id); 2667 this.settings = settings; 2668 this.oscillator = new Oscillator( 2669 this.parent.container, 2670 this.element.querySelector('.alert-message'), 2671 this.settings.delay 2672 ); 2673 this.#baseClasses = this 2674 .element.className 2675 .replace('hidden', '') 2676 .split(' '); 2677 this.#baseClasses.push('animate__animated'); 2678 } 2679 2680 /** 2681 * Animates this alert item entering the display container from the left. 2682 * 2683 * @param reverse
2684 * If true, the animation enters from the right. 2685 * @returns {Promise} 2686 * Reference to self. 2687 */ 2688 display (reverse = false) { 2689 return new Promise((resolve, reject) => { 2690 this.element.addEventListener('animationend', () => { 2691 this.reset(); 2692 this.parent.pagerLocked = false; 2693 return resolve(this); 2694 }, {once: true}); 2695 this.parent.pagerLocked = true; 2696 this.element.className = this.#concatClasses(Animation.getShow(reverse)); 2697 this.parent.setSequence(); 2698 this.oscillator 2699 .animate() 2700 .monitorResize(); 2701 }); 2702 } 2703 2704 /** 2705 * Animates this alert item exiting the display container to the right. 2706 * 2707 * @param reverse 2708 * If true, the animation exits to the left. 2709 * @returns {Promise} 2710 * Reference to self. 2711 */ 2712 hide (reverse = false) { 2713 return new Promise((resolve, reject) => { 2714 this.element.addEventListener('animationend', () => { 2715 this.parent.pagerLocked = false; 2716 return resolve(this); 2717 }, {once: true}); 2718 this.parent.pagerLocked = true; 2719 this.element.className = this 2720 .#concatClasses(Animation.getHide(reverse)).concat(' animate__fast'); 2721 this.oscillator.clear(); 2722 }); 2723 } 2724 2725 /** 2726 * Resets this AlertItem's element's class string to this alert's base 2727 * classes. 2728 * 2729 * @returns {AlertItem} 2730 * Reference to self. 2731 */ 2732 reset () { 2733 this.element.className = this.#baseClasses.join(' '); 2734 return this; 2735 } 2736 2737 /** 2738 * Returns the classes to apply based on the specified operation. 2739 * 2740 * @param {string} op_class 2741 * The operation class to concatenate with the base classes. 2742 * @returns {string} 2743 * Returns the space-delimited list of classes to apply. 2744 */ 2745 #concatClasses (op_class) { 2746 return this.#baseClasses.concat(op_class).join(' '); 2747 } 2748 2749 } 2750 2751 /** 2752 * Static class that acts as a canonical enum for animation classes. 2753 * 2754 * @see library.animate.css in cwb_alerts.libraries.yml 2755 */ 2756 class Animation { 2757 static ShowNext = new Animation('animate__fadeInLeftBig'); 2758 static ShowPrev = new Animation('animate__fadeInRightBig'); 2759 static HideNext = new Animation('animate__fadeOutRightBig'); 2760 static HidePrev = new Animation('animate__fadeOutLeftBig'); 2761 2762 constructor (name) { 2763 this.name = name; 2764 } 2765 2766 /** 2767 * Returns the class to animate an alert item into the ticker display from 2768 * the left. 2769 * 2770 * @param reverse 2771 * True if the ticker should animate in from the right. 2772 * @returns {string} 2773 */ 2774 static getShow (reverse = false) { 2775 return reverse ? Animation.ShowPrev.name : Animation.ShowNext.name 2776 } 2777 2778 /** 2779 * Returns the class to animate an alert item out of the ticker display to 2780 * the right. 2781 * 2782 * @param reverse 2783 * True if the ticker should animate out to the left. 2784 * @returns {string} 2785 */ 2786 static getHide (reverse = false) { 2787 return reverse ? Animation.HidePrev.name : Animation.HideNext.name; 2788 } 2789 2790 } 2791 2792 /** 2793 * An object for managing alert messages that are longer than their container. 2794 * When an alert message is detected to be longer than its enclosing element, 2795 * it will be horizontally oscillated back and forth. 2796 */ 2797 class Oscillator { 2798 2799 /** 2800 * Amount of overflow, in pixels, that needs to be oscillated. 2801 * 2802 * @type {number} 2803 */ 2804 #overflow; 2805 2806 /** 2807 * The transition time for each oscillation. 2808 * 2809 * @type {number} 2810 */ 2811 #animationSpeed; 2812 2813 /** 2814 * The original CSS transition value for this oscillator's element. 2815 * 2816 * @type {string} 2817 */ 2818 #originalTransition; 2819 2820 /** 2821 * The original CSS margin-left value for this oscillator's element. 2822 * 2823 * @type {string} 2824 */ 2825 #originalMarginLeft; 2826 2827 /** 2828 * A canvas object that provides a computational manifold that does not 2829 * result in DOM reflow. 2830 * 2831 * @type {HTMLCanvasElement} 2832 */ 2833 #canvas = document.createElement("canvas"); 2834 2835 /** 2836 * Constructs a new oscillator object. 2837 * 2838 * @param {HTMLElement} textContainer 2839 * Reference to the element that acts as the enclosing container for all 2840 * alerts. 2841 * @param {HTMLElement} textElement 2842 * Reference to a specific element whose content should be oscillated 2843 * within the enclosing container. 2844 * @param {number} delay 2845 * The number of seconds that this alert will be displayed. 2846 */ 2847 constructor (textContainer, textElement, delay) { 2848 this.container = textContainer; 2849 this.element = textElement; 2850 this.delay = delay; 2851 this.#originalTransition = this.element.style.transition; 2852 this.#originalMarginLeft = this.element.style.marginLeft; 2853 } 2854 2855 /**
2856 * Returns the CSS computed value for a specific CSS style. 2857 * 2858 * @param {string} property 2859 * The name of the CSS property whose computed value should be returned. 2860 * @returns {string} 2861 * The computed CSS value. 2862 */ 2863 #getComputedStyle (property) { 2864 return window.getComputedStyle(this.element, null) 2865 .getPropertyValue(property); 2866 } 2867 2868 /** 2869 * Returns a canvas context whose font has been set to the same as the 2870 * current text element. 2871 * 2872 * @returns {CanvasRenderingContext2D} 2873 * Canvas rendering context. 2874 */ 2875 #getFontContext () { 2876 const context = this.#canvas.getContext("2d"); 2877 context.font = this.#getComputedStyle('font'); 2878 return context; 2879 } 2880 2881 /** 2882 * Returns the width, in pixels, of the container element. 2883 * 2884 * @returns {number} 2885 * The pixel width of the container element. 2886 */ 2887 getContainerWidth () { 2888 return this.container.offsetWidth; 2889 } 2890 2891 /** 2892 * Returns the width, in pixels, of the text element's text content. 2893 * 2894 * @returns {number} 2895 * The pixel width of the text element's text content. 2896 */ 2897 getTextWidth () { 2898 const context = this.#getFontContext(); 2899 const metrics = context.measureText(this.element.textContent); 2900 return metrics.width; 2901 } 2902 2903 /** 2904 * Returns the number of pixels difference between the container element and 2905 * the text element. Positive values indicate surplus space in the 2906 * container; negative values indicate overflowing text. 2907 * 2908 * @returns {number} 2909 * The number of pixels difference between container and text element 2910 * width. 2911 */ 2912 overflow () { 2913 return this.getContainerWidth() - this.getTextWidth(); 2914 } 2915 2916 /** 2917 * Detects any text overflow and animates an oscillation of necessary. 2918 * 2919 * @returns {Oscillator} 2920 * Reference to self. 2921 */ 2922 animate () { 2923 this.#calculate(); 2924 if (this.#overflow >= 0) { 2925 return this; 2926 } 2927 const delay = '0.5'; //Math.min(2, this.#animationSpeed); 2928 this.element.style.transition = this 2929 .#originalTransition 2930 .concat(` margin-left ${this.#animationSpeed}s ease-in-out 2s`); 2931 this.element.style.marginLeft = this.#getMarginLeft(this.element.direction); 2932 this.toggleOscillation = e => { 2933 e.target.direction *= -1; 2934 e.target.style.marginLeft = this.#getMarginLeft(e.target.direction); 2935 }; 2936 this.element.addEventListener('transitionend', this.toggleOscillation); 2937 return this; 2938 } 2939 2940 /** 2941 * Invokes a window resize event listener in order that will respond to 2942 * resize events be re-calculating the overflow and transition time. 2943 * 2944 * @returns {Oscillator} 2945 * Reference to self. 2946 */ 2947 monitorResize () { 2948 this.listenResize = Drupal.debounce(() => { 2949 this.element.removeEventListener('transitionend', this.toggleOscillation); 2950 this.animate(); 2951 }, 300); 2952 window.addEventListener('resize', this.listenResize); 2953 return this; 2954 } 2955 2956 /** 2957 * Clears all oscillation animations for this container/text element pair. 2958 * 2959 * @returns {Oscillator} 2960 * Reference to self. 2961 */ 2962 clear () { 2963 this.element.style.transition = this.#originalTransition; 2964 this.element.style.marginLeft = this.#originalMarginLeft; 2965 this.element.direction = 1; 2966 this.element.removeEventListener('transitionend', this.toggleOscillation); 2967 window.removeEventListener('resize', this.listenResize) 2968 return this; 2969 } 2970 2971 /** 2972 * Calculates the overflow and animation speed and stores those values in 2973 * private local properties. 2974 * 2975 * @returns {Oscillator} 2976 * Reference ot self. 2977 */ 2978 #calculate () { 2979 this.#overflow = Math.min(this.overflow() - this.#totalPadding(), 0); 2980 if (this.#overflow >= 0) { 2981 return this; 2982 } 2983 this.#animationSpeed = Math.min(Math.max( 2984 (this.delay / 1000) - 3, 2985 1), 10); 2986 return this; 2987 } 2988 2989 /** 2990 * Returns an appropriate margin left values based on the direction of 2991 * oscillation. 2992 * 2993 * @param {number} direction 2994 * A value of 1 for the initial oscillation direction (left to right), or 2995 * -1 for the reverse oscillation (right to left). 2996 * @returns {string} 2997 * A valid CSS value for the margin-left property. 2998 */ 2999 #getMarginLeft (direction = 1) { 3000 return direction > 0 ? `${this.#overflow}px` : '0'; 3001 } 3002 3003 /** 3004 * Returns the total width of the text element's left and right padding. 3005 * 3006 * @returns {number} 3007 * Number of total pixels of left and right padding. 3008 */ 3009 #totalPadding () { 3010 return parseInt(this.#getComputedStyle('padding-left')) 3011 + parseInt(this.#getComputedStyle('padding-right')); 3012 } 3013 3014 /** 3015 * Returns the current font size for the text element. 3016 * 3017 * @returns {number} 3018 * The pixel size of the text element's font. 3019 */ 3020 #fontSize () { 3021 return parseInt(this.#getComputedStyle('font-size')); 3022 } 3023 3024 } 3025 3026 // Register the AlertsTicker class with the alertFactory. 3027 Drupal.alertFactory 3028 .register('ticker', (ticker, selectors, alerts) => { 3029 return new AlertsTicker(ticker, selectors, alerts); 3030 }); 3031})(Drupal); 3032; 3033(function ($, undefined) { 3034 'use strict'; 3035 3036 /** 3037 * Attach ticker alerts behavior. 3038 * 3039 * @type {Drupal~behavior} 3040 * 3041 * @prop {Drupal~behaviorAttach} attach 3042 * Initializes the ticker's operation for each instance on page. 3043 * 3044 */ 3045 Drupal.behaviors.cwb_alerts_ticker = { 3046 attach: function (context, settings) { 3047 $('body').once('cwb-alerts-ticker').each(function () { 3048 const 3049 _settings = settings.cwb_alerts_ticker, 3050 selectors = _settings.selectors, 3051 alerts = _settings.data.alerts 3052 ; 3053 $(selectors.ticker, context).each(function () { 3054 const alerts_ticker = Drupal.alertFactory 3055 .get('ticker')(this, selectors, alerts); 3056 alerts_ticker.start(); 3057 }); 3058 }); 3059 } 3060 } 3061})(jQuery); 3062;
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.