vendor: 4,281 bytes, lines 1-142
1/*! js-cookie v3.0.5 | MIT */ 2; 3(function (global, factory) { 4 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 5 typeof define === 'function' && define.amd ? define(factory) : 6 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { 7 var current = global.Cookies; 8 var exports = global.Cookies = factory(); 9 exports.noConflict = function () { global.Cookies = current; return exports; }; 10 })()); 11})(this, (function () { 'use strict'; 12 13 /* eslint-disable no-var */ 14 function assign (target) { 15 for (var i = 1; i < arguments.length; i++) { 16 var source = arguments[i]; 17 for (var key in source) { 18 target[key] = source[key]; 19 } 20 } 21 return target 22 } 23 /* eslint-enable no-var */ 24 25 /* eslint-disable no-var */ 26 var defaultConverter = { 27 read: function (value) { 28 if (value[0] === '"') { 29 value = value.slice(1, -1); 30 } 31 return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) 32 }, 33 write: function (value) { 34 return encodeURIComponent(value).replace( 35 /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, 36 decodeURIComponent 37 ) 38 } 39 }; 40 /* eslint-enable no-var */ 41 42 /* eslint-disable no-var */ 43 44 function init (converter, defaultAttributes) { 45 function set (name, value, attributes) { 46 if (typeof document === 'undefined') { 47 return 48 } 49 50 attributes = assign({}, defaultAttributes, attributes); 51 52 if (typeof attributes.expires === 'number') { 53 attributes.expires = new Date(Date.now() + attributes.expires * 864e5); 54 } 55 if (attributes.expires) { 56 attributes.expires = attributes.expires.toUTCString(); 57 } 58 59 name = encodeURIComponent(name) 60 .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent) 61 .replace(/[()]/g, escape); 62 63 var stringifiedAttributes = ''; 64 for (var attributeName in attributes) { 65 if (!attributes[attributeName]) { 66 continue 67 } 68 69 stringifiedAttributes += '; ' + attributeName; 70 71 if (attributes[attributeName] === true) { 72 continue 73 } 74 75 // Considers RFC 6265 section 5.2: 76 // ... 77 // 3. If the remaining unparsed-attributes contains a %x3B (";") 78 // character: 79 // Consume the characters of the unparsed-attributes up to, 80 // not including, the first %x3B (";") character. 81 // ... 82 stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; 83 } 84 85 return (document.cookie = 86 name + '=' + converter.write(value, name) + stringifiedAttributes) 87 } 88 89 function get (name) { 90 if (typeof document === 'undefined' || (arguments.length && !name)) { 91 return 92 } 93 94 // To prevent the for loop in the first place assign an empty array 95 // in case there are no cookies at all. 96 var cookies = document.cookie ? document.cookie.split('; ') : []; 97 var jar = {}; 98 for (var i = 0; i < cookies.length; i++) { 99 var parts = cookies[i].split('='); 100 var value = parts.slice(1).join('='); 101 102 try { 103 var found = decodeURIComponent(parts[0]); 104 jar[found] = converter.read(value, found); 105 106 if (name === found) { 107 break 108 } 109 } catch (e) {} 110 } 111 112 return name ? jar[name] : jar 113 } 114 115 return Object.create( 116 { 117 set, 118 get, 119 remove: function (name, attributes) { 120 set( 121 name, 122 '', 123 assign({}, attributes, { 124 expires: -1 125 }) 126 ); 127 }, 128 withAttributes: function (attributes) { 129 return init(this.converter, assign({}, this.attributes, attributes)) 130 }, 131 withConverter: function (converter) { 132 return init(assign({}, this.converter, converter), this.attributes) 133 } 134 }, 135 { 136 attributes: { value: Object.freeze(defaultAttributes) }, 137 converter: { value: Object.freeze(converter) } 138 } 139 ) 140 } 141 142 var api = init(defaultConverter, { path: '/' });
vendor: 4,390 bytes, lines 143-294
143 /* eslint-enable no-var */ 144 145 return api; 146 147})); 148 149/*! 150 * Bootstrap v3.3.7 (http://getbootstrap.com) 151 * Copyright 2011-2016 Twitter, Inc. 152 * Licensed under the MIT license 153 */ 154 155if (typeof jQuery === 'undefined') { 156 throw new Error('Bootstrap\'s JavaScript requires jQuery') 157} 158 159+function ($) { 160 'use strict'; 161 var version = $.fn.jquery.split(' ')[0].split('.') 162 if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { 163 throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') 164 } 165}(jQuery); 166 167/* ======================================================================== 168 * Bootstrap: transition.js v3.3.7 169 * http://getbootstrap.com/javascript/#transitions 170 * ======================================================================== 171 * Copyright 2011-2016 Twitter, Inc. 172 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 173 * ======================================================================== */ 174 175 176+function ($) { 177 'use strict'; 178 179 // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) 180 // ============================================================ 181 182 function transitionEnd() { 183 var el = document.createElement('bootstrap') 184 185 var transEndEventNames = { 186 WebkitTransition : 'webkitTransitionEnd', 187 MozTransition : 'transitionend', 188 OTransition : 'oTransitionEnd otransitionend', 189 transition : 'transitionend' 190 } 191 192 for (var name in transEndEventNames) { 193 if (el.style[name] !== undefined) { 194 return { end: transEndEventNames[name] } 195 } 196 } 197 198 return false // explicit for ie8 ( ._.) 199 } 200 201 // http://blog.alexmaccaw.com/css-transitions 202 $.fn.emulateTransitionEnd = function (duration) { 203 var called = false 204 var $el = this 205 $(this).one('bsTransitionEnd', function () { called = true }) 206 var callback = function () { if (!called) $($el).trigger($.support.transition.end) } 207 setTimeout(callback, duration) 208 return this 209 } 210 211 $(function () { 212 $.support.transition = transitionEnd() 213 214 if (!$.support.transition) return 215 216 $.event.special.bsTransitionEnd = { 217 bindType: $.support.transition.end, 218 delegateType: $.support.transition.end, 219 handle: function (e) { 220 if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) 221 } 222 } 223 }) 224 225}(jQuery); 226 227/* ======================================================================== 228 * Bootstrap: alert.js v3.3.7 229 * http://getbootstrap.com/javascript/#alerts 230 * ======================================================================== 231 * Copyright 2011-2016 Twitter, Inc. 232 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 233 * ======================================================================== */ 234 235 236+function ($) { 237 'use strict'; 238 239 // ALERT CLASS DEFINITION 240 // ====================== 241 242 var dismiss = '[data-dismiss="alert"]' 243 var Alert = function (el) { 244 $(el).on('click', dismiss, this.close) 245 } 246 247 Alert.VERSION = '3.3.7' 248 249 Alert.TRANSITION_DURATION = 150 250 251 Alert.prototype.close = function (e) { 252 var $this = $(this) 253 var selector = $this.attr('data-target') 254 255 if (!selector) { 256 selector = $this.attr('href') 257 selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 258 } 259 260 var $parent = $(selector === '#' ? [] : selector) 261 262 if (e) e.preventDefault() 263 264 if (!$parent.length) { 265 $parent = $this.closest('.alert') 266 } 267 268 $parent.trigger(e = $.Event('close.bs.alert')) 269 270 if (e.isDefaultPrevented()) return 271 272 $parent.removeClass('in') 273 274 function removeElement() { 275 // detach from parent, fire event then clean up data 276 $parent.detach().trigger('closed.bs.alert').remove() 277 } 278 279 $.support.transition && $parent.hasClass('fade') ? 280 $parent 281 .one('bsTransitionEnd', removeElement) 282 .emulateTransitionEnd(Alert.TRANSITION_DURATION) : 283 removeElement() 284 } 285 286 287 // ALERT PLUGIN DEFINITION 288 // ======================= 289 290 function Plugin(option) { 291 return this.each(function () { 292 var $this = $(this) 293 var data = $this.data('bs.alert') 294
vendor: 4,161 bytes, lines 295-438
295 if (!data) $this.data('bs.alert', (data = new Alert(this))) 296 if (typeof option == 'string') data[option].call($this) 297 }) 298 } 299 300 var old = $.fn.alert 301 302 $.fn.alert = Plugin 303 $.fn.alert.Constructor = Alert 304 305 306 // ALERT NO CONFLICT 307 // ================= 308 309 $.fn.alert.noConflict = function () { 310 $.fn.alert = old 311 return this 312 } 313 314 315 // ALERT DATA-API 316 // ============== 317 318 $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) 319 320}(jQuery); 321 322/* ======================================================================== 323 * Bootstrap: button.js v3.3.7 324 * http://getbootstrap.com/javascript/#buttons 325 * ======================================================================== 326 * Copyright 2011-2016 Twitter, Inc. 327 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 328 * ======================================================================== */ 329 330 331+function ($) { 332 'use strict'; 333 334 // BUTTON PUBLIC CLASS DEFINITION 335 // ============================== 336 337 var Button = function (element, options) { 338 this.$element = $(element) 339 this.options = $.extend({}, Button.DEFAULTS, options) 340 this.isLoading = false 341 } 342 343 Button.VERSION = '3.3.7' 344 345 Button.DEFAULTS = { 346 loadingText: 'loading...' 347 } 348 349 Button.prototype.setState = function (state) { 350 var d = 'disabled' 351 var $el = this.$element 352 var val = $el.is('input') ? 'val' : 'html' 353 var data = $el.data() 354 355 state += 'Text' 356 357 if (data.resetText == null) $el.data('resetText', $el[val]()) 358 359 // push to event loop to allow forms to submit 360 setTimeout($.proxy(function () { 361 $el[val](data[state] == null ? this.options[state] : data[state]) 362 363 if (state == 'loadingText') { 364 this.isLoading = true 365 $el.addClass(d).attr(d, d).prop(d, true) 366 } else if (this.isLoading) { 367 this.isLoading = false 368 $el.removeClass(d).removeAttr(d).prop(d, false) 369 } 370 }, this), 0) 371 } 372 373 Button.prototype.toggle = function () { 374 var changed = true 375 var $parent = this.$element.closest('[data-toggle="buttons"]') 376 377 if ($parent.length) { 378 var $input = this.$element.find('input') 379 if ($input.prop('type') == 'radio') { 380 if ($input.prop('checked')) changed = false 381 $parent.find('.active').removeClass('active') 382 this.$element.addClass('active') 383 } else if ($input.prop('type') == 'checkbox') { 384 if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false 385 this.$element.toggleClass('active') 386 } 387 $input.prop('checked', this.$element.hasClass('active')) 388 if (changed) $input.trigger('change') 389 } else { 390 this.$element.attr('aria-pressed', !this.$element.hasClass('active')) 391 this.$element.toggleClass('active') 392 } 393 } 394 395 396 // BUTTON PLUGIN DEFINITION 397 // ======================== 398 399 function Plugin(option) { 400 return this.each(function () { 401 var $this = $(this) 402 var data = $this.data('bs.button') 403 var options = typeof option == 'object' && option 404 405 if (!data) $this.data('bs.button', (data = new Button(this, options))) 406 407 if (option == 'toggle') data.toggle() 408 else if (option) data.setState(option) 409 }) 410 } 411 412 var old = $.fn.button 413 414 $.fn.button = Plugin 415 $.fn.button.Constructor = Button 416 417 418 // BUTTON NO CONFLICT 419 // ================== 420 421 $.fn.button.noConflict = function () { 422 $.fn.button = old 423 return this 424 } 425 426 427 // BUTTON DATA-API 428 // =============== 429 430 $(document) 431 .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { 432 var $btn = $(e.target).closest('.btn') 433 Plugin.call($btn, 'toggle') 434 if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { 435 // Prevent double click on radios, and the double selections (so cancellation) on checkboxes 436 e.preventDefault() 437 // The target component still receive the focus 438 if ($btn.is('input,button')) $btn.trigger('focus')
vendor: 4,986 bytes, lines 439-591
439 else $btn.find('input:visible,button:visible').first().trigger('focus') 440 } 441 }) 442 .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { 443 $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) 444 }) 445 446}(jQuery); 447 448/* ======================================================================== 449 * Bootstrap: carousel.js v3.3.7 450 * http://getbootstrap.com/javascript/#carousel 451 * ======================================================================== 452 * Copyright 2011-2016 Twitter, Inc. 453 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 454 * ======================================================================== */ 455 456 457+function ($) { 458 'use strict'; 459 460 // CAROUSEL CLASS DEFINITION 461 // ========================= 462 463 var Carousel = function (element, options) { 464 this.$element = $(element) 465 this.$indicators = this.$element.find('.carousel-indicators') 466 this.options = options 467 this.paused = null 468 this.sliding = null 469 this.interval = null 470 this.$active = null 471 this.$items = null 472 473 this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) 474 475 this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element 476 .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) 477 .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) 478 } 479 480 Carousel.VERSION = '3.3.7' 481 482 Carousel.TRANSITION_DURATION = 600 483 484 Carousel.DEFAULTS = { 485 interval: 5000, 486 pause: 'hover', 487 wrap: true, 488 keyboard: true 489 } 490 491 Carousel.prototype.keydown = function (e) { 492 if (/input|textarea/i.test(e.target.tagName)) return 493 switch (e.which) { 494 case 37: this.prev(); break 495 case 39: this.next(); break 496 default: return 497 } 498 499 e.preventDefault() 500 } 501 502 Carousel.prototype.cycle = function (e) { 503 e || (this.paused = false) 504 505 this.interval && clearInterval(this.interval) 506 507 this.options.interval 508 && !this.paused 509 && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) 510 511 return this 512 } 513 514 Carousel.prototype.getItemIndex = function (item) { 515 this.$items = item.parent().children('.item') 516 return this.$items.index(item || this.$active) 517 } 518 519 Carousel.prototype.getItemForDirection = function (direction, active) { 520 var activeIndex = this.getItemIndex(active) 521 var willWrap = (direction == 'prev' && activeIndex === 0) 522 || (direction == 'next' && activeIndex == (this.$items.length - 1)) 523 if (willWrap && !this.options.wrap) return active 524 var delta = direction == 'prev' ? -1 : 1 525 var itemIndex = (activeIndex + delta) % this.$items.length 526 return this.$items.eq(itemIndex) 527 } 528 529 Carousel.prototype.to = function (pos) { 530 var that = this 531 var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) 532 533 if (pos > (this.$items.length - 1) || pos < 0) return 534 535 if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" 536 if (activeIndex == pos) return this.pause().cycle() 537 538 return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) 539 } 540 541 Carousel.prototype.pause = function (e) { 542 e || (this.paused = true) 543 544 if (this.$element.find('.next, .prev').length && $.support.transition) { 545 this.$element.trigger($.support.transition.end) 546 this.cycle(true) 547 } 548 549 this.interval = clearInterval(this.interval) 550 551 return this 552 } 553 554 Carousel.prototype.next = function () { 555 if (this.sliding) return 556 return this.slide('next') 557 } 558 559 Carousel.prototype.prev = function () { 560 if (this.sliding) return 561 return this.slide('prev') 562 } 563 564 Carousel.prototype.slide = function (type, next) { 565 var $active = this.$element.find('.item.active') 566 var $next = next || this.getItemForDirection(type, $active) 567 var isCycling = this.interval 568 var direction = type == 'next' ? 'left' : 'right' 569 var that = this 570 571 if ($next.hasClass('active')) return (this.sliding = false) 572 573 var relatedTarget = $next[0] 574 var slideEvent = $.Event('slide.bs.carousel', { 575 relatedTarget: relatedTarget, 576 direction: direction 577 }) 578 this.$element.trigger(slideEvent) 579 if (slideEvent.isDefaultPrevented()) return 580 581 this.sliding = true 582 583 isCycling && this.pause() 584 585 if (this.$indicators.length) { 586 this.$indicators.find('.active').removeClass('active') 587 var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) 588 $nextIndicator && $nextIndicator.addClass('active') 589 } 590 591 var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
vendor: 18,259 bytes, lines 592-1217
592 if ($.support.transition && this.$element.hasClass('slide')) { 593 $next.addClass(type) 594 $next[0].offsetWidth // force reflow 595 $active.addClass(direction) 596 $next.addClass(direction) 597 $active 598 .one('bsTransitionEnd', function () { 599 $next.removeClass([type, direction].join(' ')).addClass('active') 600 $active.removeClass(['active', direction].join(' ')) 601 that.sliding = false 602 setTimeout(function () { 603 that.$element.trigger(slidEvent) 604 }, 0) 605 }) 606 .emulateTransitionEnd(Carousel.TRANSITION_DURATION) 607 } else { 608 $active.removeClass('active') 609 $next.addClass('active') 610 this.sliding = false 611 this.$element.trigger(slidEvent) 612 } 613 614 isCycling && this.cycle() 615 616 return this 617 } 618 619 620 // CAROUSEL PLUGIN DEFINITION 621 // ========================== 622 623 function Plugin(option) { 624 return this.each(function () { 625 var $this = $(this) 626 var data = $this.data('bs.carousel') 627 var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) 628 var action = typeof option == 'string' ? option : options.slide 629 630 if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) 631 if (typeof option == 'number') data.to(option) 632 else if (action) data[action]() 633 else if (options.interval) data.pause().cycle() 634 }) 635 } 636 637 var old = $.fn.carousel 638 639 $.fn.carousel = Plugin 640 $.fn.carousel.Constructor = Carousel 641 642 643 // CAROUSEL NO CONFLICT 644 // ==================== 645 646 $.fn.carousel.noConflict = function () { 647 $.fn.carousel = old 648 return this 649 } 650 651 652 // CAROUSEL DATA-API 653 // ================= 654 655 var clickHandler = function (e) { 656 var href 657 var $this = $(this) 658 var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 659 if (!$target.hasClass('carousel')) return 660 var options = $.extend({}, $target.data(), $this.data()) 661 var slideIndex = $this.attr('data-slide-to') 662 if (slideIndex) options.interval = false 663 664 Plugin.call($target, options) 665 666 if (slideIndex) { 667 $target.data('bs.carousel').to(slideIndex) 668 } 669 670 e.preventDefault() 671 } 672 673 $(document) 674 .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) 675 .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) 676 677 $(window).on('load', function () { 678 $('[data-ride="carousel"]').each(function () { 679 var $carousel = $(this) 680 Plugin.call($carousel, $carousel.data()) 681 }) 682 }) 683 684}(jQuery); 685 686/* ======================================================================== 687 * Bootstrap: collapse.js v3.3.7 688 * http://getbootstrap.com/javascript/#collapse 689 * ======================================================================== 690 * Copyright 2011-2016 Twitter, Inc. 691 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 692 * ======================================================================== */ 693 694/* jshint latedef: false */ 695 696+function ($) { 697 'use strict'; 698 699 // COLLAPSE PUBLIC CLASS DEFINITION 700 // ================================ 701 702 var Collapse = function (element, options) { 703 this.$element = $(element) 704 this.options = $.extend({}, Collapse.DEFAULTS, options) 705 this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + 706 '[data-toggle="collapse"][data-target="#' + element.id + '"]') 707 this.transitioning = null 708 709 if (this.options.parent) { 710 this.$parent = this.getParent() 711 } else { 712 this.addAriaAndCollapsedClass(this.$element, this.$trigger) 713 } 714 715 if (this.options.toggle) this.toggle() 716 } 717 718 Collapse.VERSION = '3.3.7' 719 720 Collapse.TRANSITION_DURATION = 350 721 722 Collapse.DEFAULTS = { 723 toggle: true 724 } 725 726 Collapse.prototype.dimension = function () { 727 var hasWidth = this.$element.hasClass('width') 728 return hasWidth ? 'width' : 'height' 729 } 730 731 Collapse.prototype.show = function () { 732 if (this.transitioning || this.$element.hasClass('in')) return 733 734 var activesData 735 var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') 736 737 if (actives && actives.length) { 738 activesData = actives.data('bs.collapse') 739 if (activesData && activesData.transitioning) return 740 } 741 742 var startEvent = $.Event('show.bs.collapse') 743 this.$element.trigger(startEvent) 744 if (startEvent.isDefaultPrevented()) return 745 746 if (actives && actives.length) { 747 Plugin.call(actives, 'hide') 748 activesData || actives.data('bs.collapse', null) 749 } 750 751 var dimension = this.dimension() 752 753 this.$element 754 .removeClass('collapse') 755 .addClass('collapsing')[dimension](0) 756 .attr('aria-expanded', true) 757 758 this.$trigger 759 .removeClass('collapsed') 760 .attr('aria-expanded', true) 761 762 this.transitioning = 1 763 764 var complete = function () { 765 this.$element 766 .removeClass('collapsing') 767 .addClass('collapse in')[dimension]('') 768 this.transitioning = 0 769 this.$element 770 .trigger('shown.bs.collapse') 771 } 772 773 if (!$.support.transition) return complete.call(this) 774 775 var scrollSize = $.camelCase(['scroll', dimension].join('-')) 776 777 this.$element 778 .one('bsTransitionEnd', $.proxy(complete, this)) 779 .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) 780 } 781 782 Collapse.prototype.hide = function () { 783 if (this.transitioning || !this.$element.hasClass('in')) return 784 785 var startEvent = $.Event('hide.bs.collapse') 786 this.$element.trigger(startEvent) 787 if (startEvent.isDefaultPrevented()) return 788 789 var dimension = this.dimension() 790 791 this.$element[dimension](this.$element[dimension]())[0].offsetHeight 792 793 this.$element 794 .addClass('collapsing') 795 .removeClass('collapse in') 796 .attr('aria-expanded', false) 797 798 this.$trigger 799 .addClass('collapsed') 800 .attr('aria-expanded', false) 801 802 this.transitioning = 1 803 804 var complete = function () { 805 this.transitioning = 0 806 this.$element 807 .removeClass('collapsing') 808 .addClass('collapse') 809 .trigger('hidden.bs.collapse') 810 } 811 812 if (!$.support.transition) return complete.call(this) 813 814 this.$element 815 [dimension](0) 816 .one('bsTransitionEnd', $.proxy(complete, this)) 817 .emulateTransitionEnd(Collapse.TRANSITION_DURATION) 818 } 819 820 Collapse.prototype.toggle = function () { 821 this[this.$element.hasClass('in') ? 'hide' : 'show']() 822 } 823 824 Collapse.prototype.getParent = function () { 825 return $(this.options.parent) 826 .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') 827 .each($.proxy(function (i, element) { 828 var $element = $(element) 829 this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) 830 }, this)) 831 .end() 832 } 833 834 Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { 835 var isOpen = $element.hasClass('in') 836 837 $element.attr('aria-expanded', isOpen) 838 $trigger 839 .toggleClass('collapsed', !isOpen) 840 .attr('aria-expanded', isOpen) 841 } 842 843 function getTargetFromTrigger($trigger) { 844 var href 845 var target = $trigger.attr('data-target') 846 || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 847 848 return $(target) 849 } 850 851 852 // COLLAPSE PLUGIN DEFINITION 853 // ========================== 854 855 function Plugin(option) { 856 return this.each(function () { 857 var $this = $(this) 858 var data = $this.data('bs.collapse') 859 var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) 860 861 if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false 862 if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) 863 if (typeof option == 'string') data[option]() 864 }) 865 } 866 867 var old = $.fn.collapse 868 869 $.fn.collapse = Plugin 870 $.fn.collapse.Constructor = Collapse 871 872 873 // COLLAPSE NO CONFLICT 874 // ==================== 875 876 $.fn.collapse.noConflict = function () { 877 $.fn.collapse = old 878 return this 879 } 880 881 882 // COLLAPSE DATA-API 883 // ================= 884 885 $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { 886 var $this = $(this) 887 888 if (!$this.attr('data-target')) e.preventDefault() 889 890 var $target = getTargetFromTrigger($this) 891 var data = $target.data('bs.collapse') 892 var option = data ? 'toggle' : $this.data() 893 894 Plugin.call($target, option) 895 }) 896 897}(jQuery); 898 899/* ======================================================================== 900 * Bootstrap: dropdown.js v3.3.7 901 * http://getbootstrap.com/javascript/#dropdowns 902 * ======================================================================== 903 * Copyright 2011-2016 Twitter, Inc. 904 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 905 * ======================================================================== */ 906 907 908+function ($) { 909 'use strict'; 910 911 // DROPDOWN CLASS DEFINITION 912 // ========================= 913 914 var backdrop = '.dropdown-backdrop' 915 var toggle = '[data-toggle="dropdown"]' 916 var Dropdown = function (element) { 917 $(element).on('click.bs.dropdown', this.toggle) 918 } 919 920 Dropdown.VERSION = '3.3.7' 921 922 function getParent($this) { 923 var selector = $this.attr('data-target') 924 925 if (!selector) { 926 selector = $this.attr('href') 927 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 928 } 929 930 var $parent = selector && $(selector) 931 932 return $parent && $parent.length ? $parent : $this.parent() 933 } 934 935 function clearMenus(e) { 936 if (e && e.which === 3) return 937 $(backdrop).remove() 938 $(toggle).each(function () { 939 var $this = $(this) 940 var $parent = getParent($this) 941 var relatedTarget = { relatedTarget: this } 942 943 if (!$parent.hasClass('open')) return 944 945 if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return 946 947 $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) 948 949 if (e.isDefaultPrevented()) return 950 951 $this.attr('aria-expanded', 'false') 952 $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) 953 }) 954 } 955 956 Dropdown.prototype.toggle = function (e) { 957 var $this = $(this) 958 959 if ($this.is('.disabled, :disabled')) return 960 961 var $parent = getParent($this) 962 var isActive = $parent.hasClass('open') 963 964 clearMenus() 965 966 if (!isActive) { 967 if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { 968 // if mobile we use a backdrop because click events don't delegate 969 $(document.createElement('div')) 970 .addClass('dropdown-backdrop') 971 .insertAfter($(this)) 972 .on('click', clearMenus) 973 } 974 975 var relatedTarget = { relatedTarget: this } 976 $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) 977 978 if (e.isDefaultPrevented()) return 979 980 $this 981 .trigger('focus') 982 .attr('aria-expanded', 'true') 983 984 $parent 985 .toggleClass('open') 986 .trigger($.Event('shown.bs.dropdown', relatedTarget)) 987 } 988 989 return false 990 } 991 992 Dropdown.prototype.keydown = function (e) { 993 if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return 994 995 var $this = $(this) 996 997 e.preventDefault() 998 e.stopPropagation() 999 1000 if ($this.is('.disabled, :disabled')) return 1001 1002 var $parent = getParent($this) 1003 var isActive = $parent.hasClass('open') 1004 1005 if (!isActive && e.which != 27 || isActive && e.which == 27) { 1006 if (e.which == 27) $parent.find(toggle).trigger('focus') 1007 return $this.trigger('click') 1008 } 1009 1010 var desc = ' li:not(.disabled):visible a' 1011 var $items = $parent.find('.dropdown-menu' + desc) 1012 1013 if (!$items.length) return 1014 1015 var index = $items.index(e.target) 1016 1017 if (e.which == 38 && index > 0) index-- // up 1018 if (e.which == 40 && index < $items.length - 1) index++ // down 1019 if (!~index) index = 0 1020 1021 $items.eq(index).trigger('focus') 1022 } 1023 1024 1025 // DROPDOWN PLUGIN DEFINITION 1026 // ========================== 1027 1028 function Plugin(option) { 1029 return this.each(function () { 1030 var $this = $(this) 1031 var data = $this.data('bs.dropdown') 1032 1033 if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) 1034 if (typeof option == 'string') data[option].call($this) 1035 }) 1036 } 1037 1038 var old = $.fn.dropdown 1039 1040 $.fn.dropdown = Plugin 1041 $.fn.dropdown.Constructor = Dropdown 1042 1043 1044 // DROPDOWN NO CONFLICT 1045 // ==================== 1046 1047 $.fn.dropdown.noConflict = function () { 1048 $.fn.dropdown = old 1049 return this 1050 } 1051 1052 1053 // APPLY TO STANDARD DROPDOWN ELEMENTS 1054 // =================================== 1055 1056 $(document) 1057 .on('click.bs.dropdown.data-api', clearMenus) 1058 .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) 1059 .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) 1060 .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) 1061 .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) 1062 1063}(jQuery); 1064 1065/* ======================================================================== 1066 * Bootstrap: modal.js v3.3.7 1067 * http://getbootstrap.com/javascript/#modals 1068 * ======================================================================== 1069 * Copyright 2011-2016 Twitter, Inc. 1070 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1071 * ======================================================================== */ 1072 1073 1074+function ($) { 1075 'use strict'; 1076 1077 // MODAL CLASS DEFINITION 1078 // ====================== 1079 1080 var Modal = function (element, options) { 1081 this.options = options 1082 this.$body = $(document.body) 1083 this.$element = $(element) 1084 this.$dialog = this.$element.find('.modal-dialog') 1085 this.$backdrop = null 1086 this.isShown = null 1087 this.originalBodyPad = null 1088 this.scrollbarWidth = 0 1089 this.ignoreBackdropClick = false 1090 1091 if (this.options.remote) { 1092 this.$element 1093 .find('.modal-content') 1094 .load(this.options.remote, $.proxy(function () { 1095 this.$element.trigger('loaded.bs.modal') 1096 }, this)) 1097 } 1098 } 1099 1100 Modal.VERSION = '3.3.7' 1101 1102 Modal.TRANSITION_DURATION = 300 1103 Modal.BACKDROP_TRANSITION_DURATION = 150 1104 1105 Modal.DEFAULTS = { 1106 backdrop: true, 1107 keyboard: true, 1108 show: true 1109 } 1110 1111 Modal.prototype.toggle = function (_relatedTarget) { 1112 return this.isShown ? this.hide() : this.show(_relatedTarget) 1113 } 1114 1115 Modal.prototype.show = function (_relatedTarget) { 1116 var that = this 1117 var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 1118 1119 this.$element.trigger(e) 1120 1121 if (this.isShown || e.isDefaultPrevented()) return 1122 1123 this.isShown = true 1124 1125 this.checkScrollbar() 1126 this.setScrollbar() 1127 this.$body.addClass('modal-open') 1128 1129 this.escape() 1130 this.resize() 1131 1132 this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 1133 1134 this.$dialog.on('mousedown.dismiss.bs.modal', function () { 1135 that.$element.one('mouseup.dismiss.bs.modal', function (e) { 1136 if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true 1137 }) 1138 }) 1139 1140 this.backdrop(function () { 1141 var transition = $.support.transition && that.$element.hasClass('fade') 1142 1143 if (!that.$element.parent().length) { 1144 that.$element.appendTo(that.$body) // don't move modals dom position 1145 } 1146 1147 that.$element 1148 .show() 1149 .scrollTop(0) 1150 1151 that.adjustDialog() 1152 1153 if (transition) { 1154 that.$element[0].offsetWidth // force reflow 1155 } 1156 1157 that.$element.addClass('in') 1158 1159 that.enforceFocus() 1160 1161 var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 1162 1163 transition ? 1164 that.$dialog // wait for modal to slide in 1165 .one('bsTransitionEnd', function () { 1166 that.$element.trigger('focus').trigger(e) 1167 }) 1168 .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1169 that.$element.trigger('focus').trigger(e) 1170 }) 1171 } 1172 1173 Modal.prototype.hide = function (e) { 1174 if (e) e.preventDefault() 1175 1176 e = $.Event('hide.bs.modal') 1177 1178 this.$element.trigger(e) 1179 1180 if (!this.isShown || e.isDefaultPrevented()) return 1181 1182 this.isShown = false 1183 1184 this.escape() 1185 this.resize() 1186 1187 $(document).off('focusin.bs.modal') 1188 1189 this.$element 1190 .removeClass('in') 1191 .off('click.dismiss.bs.modal') 1192 .off('mouseup.dismiss.bs.modal') 1193 1194 this.$dialog.off('mousedown.dismiss.bs.modal') 1195 1196 $.support.transition && this.$element.hasClass('fade') ? 1197 this.$element 1198 .one('bsTransitionEnd', $.proxy(this.hideModal, this)) 1199 .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 1200 this.hideModal() 1201 } 1202 1203 Modal.prototype.enforceFocus = function () { 1204 $(document) 1205 .off('focusin.bs.modal') // guard against infinite focus loop 1206 .on('focusin.bs.modal', $.proxy(function (e) { 1207 if (document !== e.target && 1208 this.$element[0] !== e.target && 1209 !this.$element.has(e.target).length) { 1210 this.$element.trigger('focus') 1211 } 1212 }, this)) 1213 } 1214 1215 Modal.prototype.escape = function () { 1216 if (this.isShown && this.options.keyboard) { 1217 this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
vendor: 9,817 bytes, lines 1218-1523
1218 e.which == 27 && this.hide() 1219 }, this)) 1220 } else if (!this.isShown) { 1221 this.$element.off('keydown.dismiss.bs.modal') 1222 } 1223 } 1224 1225 Modal.prototype.resize = function () { 1226 if (this.isShown) { 1227 $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) 1228 } else { 1229 $(window).off('resize.bs.modal') 1230 } 1231 } 1232 1233 Modal.prototype.hideModal = function () { 1234 var that = this 1235 this.$element.hide() 1236 this.backdrop(function () { 1237 that.$body.removeClass('modal-open') 1238 that.resetAdjustments() 1239 that.resetScrollbar() 1240 that.$element.trigger('hidden.bs.modal') 1241 }) 1242 } 1243 1244 Modal.prototype.removeBackdrop = function () { 1245 this.$backdrop && this.$backdrop.remove() 1246 this.$backdrop = null 1247 } 1248 1249 Modal.prototype.backdrop = function (callback) { 1250 var that = this 1251 var animate = this.$element.hasClass('fade') ? 'fade' : '' 1252 1253 if (this.isShown && this.options.backdrop) { 1254 var doAnimate = $.support.transition && animate 1255 1256 this.$backdrop = $(document.createElement('div')) 1257 .addClass('modal-backdrop ' + animate) 1258 .appendTo(this.$body) 1259 1260 this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { 1261 if (this.ignoreBackdropClick) { 1262 this.ignoreBackdropClick = false 1263 return 1264 } 1265 if (e.target !== e.currentTarget) return 1266 this.options.backdrop == 'static' 1267 ? this.$element[0].focus() 1268 : this.hide() 1269 }, this)) 1270 1271 if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 1272 1273 this.$backdrop.addClass('in') 1274 1275 if (!callback) return 1276 1277 doAnimate ? 1278 this.$backdrop 1279 .one('bsTransitionEnd', callback) 1280 .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1281 callback() 1282 1283 } else if (!this.isShown && this.$backdrop) { 1284 this.$backdrop.removeClass('in') 1285 1286 var callbackRemove = function () { 1287 that.removeBackdrop() 1288 callback && callback() 1289 } 1290 $.support.transition && this.$element.hasClass('fade') ? 1291 this.$backdrop 1292 .one('bsTransitionEnd', callbackRemove) 1293 .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : 1294 callbackRemove() 1295 1296 } else if (callback) { 1297 callback() 1298 } 1299 } 1300 1301 // these following methods are used to handle overflowing modals 1302 1303 Modal.prototype.handleUpdate = function () { 1304 this.adjustDialog() 1305 } 1306 1307 Modal.prototype.adjustDialog = function () { 1308 var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight 1309 1310 this.$element.css({ 1311 paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', 1312 paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' 1313 }) 1314 } 1315 1316 Modal.prototype.resetAdjustments = function () { 1317 this.$element.css({ 1318 paddingLeft: '', 1319 paddingRight: '' 1320 }) 1321 } 1322 1323 Modal.prototype.checkScrollbar = function () { 1324 var fullWindowWidth = window.innerWidth 1325 if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 1326 var documentElementRect = document.documentElement.getBoundingClientRect() 1327 fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) 1328 } 1329 this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth 1330 this.scrollbarWidth = this.measureScrollbar() 1331 } 1332 1333 Modal.prototype.setScrollbar = function () { 1334 var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) 1335 this.originalBodyPad = document.body.style.paddingRight || '' 1336 if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) 1337 } 1338 1339 Modal.prototype.resetScrollbar = function () { 1340 this.$body.css('padding-right', this.originalBodyPad) 1341 } 1342 1343 Modal.prototype.measureScrollbar = function () { // thx walsh 1344 var scrollDiv = document.createElement('div') 1345 scrollDiv.className = 'modal-scrollbar-measure' 1346 this.$body.append(scrollDiv) 1347 var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth 1348 this.$body[0].removeChild(scrollDiv) 1349 return scrollbarWidth 1350 } 1351 1352 1353 // MODAL PLUGIN DEFINITION 1354 // ======================= 1355 1356 function Plugin(option, _relatedTarget) { 1357 return this.each(function () { 1358 var $this = $(this) 1359 var data = $this.data('bs.modal') 1360 var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) 1361 1362 if (!data) $this.data('bs.modal', (data = new Modal(this, options))) 1363 if (typeof option == 'string') data[option](_relatedTarget) 1364 else if (options.show) data.show(_relatedTarget) 1365 }) 1366 } 1367 1368 var old = $.fn.modal 1369 1370 $.fn.modal = Plugin 1371 $.fn.modal.Constructor = Modal 1372 1373 1374 // MODAL NO CONFLICT 1375 // ================= 1376 1377 $.fn.modal.noConflict = function () { 1378 $.fn.modal = old 1379 return this 1380 } 1381 1382 1383 // MODAL DATA-API 1384 // ============== 1385 1386 $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { 1387 var $this = $(this) 1388 var href = $this.attr('href') 1389 var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 1390 var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) 1391 1392 if ($this.is('a')) e.preventDefault() 1393 1394 $target.one('show.bs.modal', function (showEvent) { 1395 if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown 1396 $target.one('hidden.bs.modal', function () { 1397 $this.is(':visible') && $this.trigger('focus') 1398 }) 1399 }) 1400 Plugin.call($target, option, this) 1401 }) 1402 1403}(jQuery); 1404 1405/* ======================================================================== 1406 * Bootstrap: tooltip.js v3.3.7 1407 * http://getbootstrap.com/javascript/#tooltip 1408 * Inspired by the original jQuery.tipsy by Jason Frame 1409 * ======================================================================== 1410 * Copyright 2011-2016 Twitter, Inc. 1411 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1412 * ======================================================================== */ 1413 1414 1415+function ($) { 1416 'use strict'; 1417 1418 // TOOLTIP PUBLIC CLASS DEFINITION 1419 // =============================== 1420 1421 var Tooltip = function (element, options) { 1422 this.type = null 1423 this.options = null 1424 this.enabled = null 1425 this.timeout = null 1426 this.hoverState = null 1427 this.$element = null 1428 this.inState = null 1429 1430 this.init('tooltip', element, options) 1431 } 1432 1433 Tooltip.VERSION = '3.3.7' 1434 1435 Tooltip.TRANSITION_DURATION = 150 1436 1437 Tooltip.DEFAULTS = { 1438 animation: true, 1439 placement: 'top', 1440 selector: false, 1441 template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', 1442 trigger: 'hover focus', 1443 title: '', 1444 delay: 0, 1445 html: false, 1446 container: false, 1447 viewport: { 1448 selector: 'body', 1449 padding: 0 1450 } 1451 } 1452 1453 Tooltip.prototype.init = function (type, element, options) { 1454 this.enabled = true 1455 this.type = type 1456 this.$element = $(element) 1457 this.options = this.getOptions(options) 1458 this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) 1459 this.inState = { click: false, hover: false, focus: false } 1460 1461 if (this.$element[0] instanceof document.constructor && !this.options.selector) { 1462 throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') 1463 } 1464 1465 var triggers = this.options.trigger.split(' ') 1466 1467 for (var i = triggers.length; i--;) { 1468 var trigger = triggers[i] 1469 1470 if (trigger == 'click') { 1471 this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) 1472 } else if (trigger != 'manual') { 1473 var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' 1474 var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' 1475 1476 this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) 1477 this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) 1478 } 1479 } 1480 1481 this.options.selector ? 1482 (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : 1483 this.fixTitle() 1484 } 1485 1486 Tooltip.prototype.getDefaults = function () { 1487 return Tooltip.DEFAULTS 1488 } 1489 1490 Tooltip.prototype.getOptions = function (options) { 1491 options = $.extend({}, this.getDefaults(), this.$element.data(), options) 1492 1493 if (options.delay && typeof options.delay == 'number') { 1494 options.delay = { 1495 show: options.delay, 1496 hide: options.delay 1497 } 1498 } 1499 1500 return options 1501 } 1502 1503 Tooltip.prototype.getDelegateOptions = function () { 1504 var options = {} 1505 var defaults = this.getDefaults() 1506 1507 this._options && $.each(this._options, function (key, value) { 1508 if (defaults[key] != value) options[key] = value 1509 }) 1510 1511 return options 1512 } 1513 1514 Tooltip.prototype.enter = function (obj) { 1515 var self = obj instanceof this.constructor ? 1516 obj : $(obj.currentTarget).data('bs.' + this.type) 1517 1518 if (!self) { 1519 self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1520 $(obj.currentTarget).data('bs.' + this.type, self) 1521 } 1522 1523 if (obj instanceof $.Event) {
vendor: 10,031 bytes, lines 1524-1802
1524 self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true 1525 } 1526 1527 if (self.tip().hasClass('in') || self.hoverState == 'in') { 1528 self.hoverState = 'in' 1529 return 1530 } 1531 1532 clearTimeout(self.timeout) 1533 1534 self.hoverState = 'in' 1535 1536 if (!self.options.delay || !self.options.delay.show) return self.show() 1537 1538 self.timeout = setTimeout(function () { 1539 if (self.hoverState == 'in') self.show() 1540 }, self.options.delay.show) 1541 } 1542 1543 Tooltip.prototype.isInStateTrue = function () { 1544 for (var key in this.inState) { 1545 if (this.inState[key]) return true 1546 } 1547 1548 return false 1549 } 1550 1551 Tooltip.prototype.leave = function (obj) { 1552 var self = obj instanceof this.constructor ? 1553 obj : $(obj.currentTarget).data('bs.' + this.type) 1554 1555 if (!self) { 1556 self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) 1557 $(obj.currentTarget).data('bs.' + this.type, self) 1558 } 1559 1560 if (obj instanceof $.Event) { 1561 self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false 1562 } 1563 1564 if (self.isInStateTrue()) return 1565 1566 clearTimeout(self.timeout) 1567 1568 self.hoverState = 'out' 1569 1570 if (!self.options.delay || !self.options.delay.hide) return self.hide() 1571 1572 self.timeout = setTimeout(function () { 1573 if (self.hoverState == 'out') self.hide() 1574 }, self.options.delay.hide) 1575 } 1576 1577 Tooltip.prototype.show = function () { 1578 var e = $.Event('show.bs.' + this.type) 1579 1580 if (this.hasContent() && this.enabled) { 1581 this.$element.trigger(e) 1582 1583 var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) 1584 if (e.isDefaultPrevented() || !inDom) return 1585 var that = this 1586 1587 var $tip = this.tip() 1588 1589 var tipId = this.getUID(this.type) 1590 1591 this.setContent() 1592 $tip.attr('id', tipId) 1593 this.$element.attr('aria-describedby', tipId) 1594 1595 if (this.options.animation) $tip.addClass('fade') 1596 1597 var placement = typeof this.options.placement == 'function' ? 1598 this.options.placement.call(this, $tip[0], this.$element[0]) : 1599 this.options.placement 1600 1601 var autoToken = /\s?auto?\s?/i 1602 var autoPlace = autoToken.test(placement) 1603 if (autoPlace) placement = placement.replace(autoToken, '') || 'top' 1604 1605 $tip 1606 .detach() 1607 .css({ top: 0, left: 0, display: 'block' }) 1608 .addClass(placement) 1609 .data('bs.' + this.type, this) 1610 1611 this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) 1612 this.$element.trigger('inserted.bs.' + this.type) 1613 1614 var pos = this.getPosition() 1615 var actualWidth = $tip[0].offsetWidth 1616 var actualHeight = $tip[0].offsetHeight 1617 1618 if (autoPlace) { 1619 var orgPlacement = placement 1620 var viewportDim = this.getPosition(this.$viewport) 1621 1622 placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : 1623 placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : 1624 placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : 1625 placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : 1626 placement 1627 1628 $tip 1629 .removeClass(orgPlacement) 1630 .addClass(placement) 1631 } 1632 1633 var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) 1634 1635 this.applyPlacement(calculatedOffset, placement) 1636 1637 var complete = function () { 1638 var prevHoverState = that.hoverState 1639 that.$element.trigger('shown.bs.' + that.type) 1640 that.hoverState = null 1641 1642 if (prevHoverState == 'out') that.leave(that) 1643 } 1644 1645 $.support.transition && this.$tip.hasClass('fade') ? 1646 $tip 1647 .one('bsTransitionEnd', complete) 1648 .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1649 complete() 1650 } 1651 } 1652 1653 Tooltip.prototype.applyPlacement = function (offset, placement) { 1654 var $tip = this.tip() 1655 var width = $tip[0].offsetWidth 1656 var height = $tip[0].offsetHeight 1657 1658 // manually read margins because getBoundingClientRect includes difference 1659 var marginTop = parseInt($tip.css('margin-top'), 10) 1660 var marginLeft = parseInt($tip.css('margin-left'), 10) 1661 1662 // we must check for NaN for ie 8/9 1663 if (isNaN(marginTop)) marginTop = 0 1664 if (isNaN(marginLeft)) marginLeft = 0 1665 1666 offset.top += marginTop 1667 offset.left += marginLeft 1668 1669 // $.fn.offset doesn't round pixel values 1670 // so we use setOffset directly with our own function B-0 1671 $.offset.setOffset($tip[0], $.extend({ 1672 using: function (props) { 1673 $tip.css({ 1674 top: Math.round(props.top), 1675 left: Math.round(props.left) 1676 }) 1677 } 1678 }, offset), 0) 1679 1680 $tip.addClass('in') 1681 1682 // check to see if placing tip in new offset caused the tip to resize itself 1683 var actualWidth = $tip[0].offsetWidth 1684 var actualHeight = $tip[0].offsetHeight 1685 1686 if (placement == 'top' && actualHeight != height) { 1687 offset.top = offset.top + height - actualHeight 1688 } 1689 1690 var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) 1691 1692 if (delta.left) offset.left += delta.left 1693 else offset.top += delta.top 1694 1695 var isVertical = /top|bottom/.test(placement) 1696 var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight 1697 var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' 1698 1699 $tip.offset(offset) 1700 this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) 1701 } 1702 1703 Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { 1704 this.arrow() 1705 .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') 1706 .css(isVertical ? 'top' : 'left', '') 1707 } 1708 1709 Tooltip.prototype.setContent = function () { 1710 var $tip = this.tip() 1711 var title = this.getTitle() 1712 1713 $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) 1714 $tip.removeClass('fade in top bottom left right') 1715 } 1716 1717 Tooltip.prototype.hide = function (callback) { 1718 var that = this 1719 var $tip = $(this.$tip) 1720 var e = $.Event('hide.bs.' + this.type) 1721 1722 function complete() { 1723 if (that.hoverState != 'in') $tip.detach() 1724 if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. 1725 that.$element 1726 .removeAttr('aria-describedby') 1727 .trigger('hidden.bs.' + that.type) 1728 } 1729 callback && callback() 1730 } 1731 1732 this.$element.trigger(e) 1733 1734 if (e.isDefaultPrevented()) return 1735 1736 $tip.removeClass('in') 1737 1738 $.support.transition && $tip.hasClass('fade') ? 1739 $tip 1740 .one('bsTransitionEnd', complete) 1741 .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : 1742 complete() 1743 1744 this.hoverState = null 1745 1746 return this 1747 } 1748 1749 Tooltip.prototype.fixTitle = function () { 1750 var $e = this.$element 1751 if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { 1752 $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') 1753 } 1754 } 1755 1756 Tooltip.prototype.hasContent = function () { 1757 return this.getTitle() 1758 } 1759 1760 Tooltip.prototype.getPosition = function ($element) { 1761 $element = $element || this.$element 1762 1763 var el = $element[0] 1764 var isBody = el.tagName == 'BODY' 1765 1766 var elRect = el.getBoundingClientRect() 1767 if (elRect.width == null) { 1768 // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 1769 elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) 1770 } 1771 var isSvg = window.SVGElement && el instanceof window.SVGElement 1772 // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. 1773 // See https://github.com/twbs/bootstrap/issues/20280 1774 var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) 1775 var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } 1776 var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null 1777 1778 return $.extend({}, elRect, scroll, outerDims, elOffset) 1779 } 1780 1781 Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { 1782 return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1783 placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : 1784 placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : 1785 /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } 1786 1787 } 1788 1789 Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { 1790 var delta = { top: 0, left: 0 } 1791 if (!this.$viewport) return delta 1792 1793 var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 1794 var viewportDimensions = this.getPosition(this.$viewport) 1795 1796 if (/right|left/.test(placement)) { 1797 var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll 1798 var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight 1799 if (topEdgeOffset < viewportDimensions.top) { // top overflow 1800 delta.top = viewportDimensions.top - topEdgeOffset 1801 } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow 1802 delta.top = viewportDimensions.top + viewportDimensions.height - bottom
vendor: 8,124 bytes, lines 1802-2081
1802EdgeOffset 1803 } 1804 } else { 1805 var leftEdgeOffset = pos.left - viewportPadding 1806 var rightEdgeOffset = pos.left + viewportPadding + actualWidth 1807 if (leftEdgeOffset < viewportDimensions.left) { // left overflow 1808 delta.left = viewportDimensions.left - leftEdgeOffset 1809 } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow 1810 delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset 1811 } 1812 } 1813 1814 return delta 1815 } 1816 1817 Tooltip.prototype.getTitle = function () { 1818 var title 1819 var $e = this.$element 1820 var o = this.options 1821 1822 title = $e.attr('data-original-title') 1823 || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) 1824 1825 return title 1826 } 1827 1828 Tooltip.prototype.getUID = function (prefix) { 1829 do prefix += ~~(Math.random() * 1000000) 1830 while (document.getElementById(prefix)) 1831 return prefix 1832 } 1833 1834 Tooltip.prototype.tip = function () { 1835 if (!this.$tip) { 1836 this.$tip = $(this.options.template) 1837 if (this.$tip.length != 1) { 1838 throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') 1839 } 1840 } 1841 return this.$tip 1842 } 1843 1844 Tooltip.prototype.arrow = function () { 1845 return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) 1846 } 1847 1848 Tooltip.prototype.enable = function () { 1849 this.enabled = true 1850 } 1851 1852 Tooltip.prototype.disable = function () { 1853 this.enabled = false 1854 } 1855 1856 Tooltip.prototype.toggleEnabled = function () { 1857 this.enabled = !this.enabled 1858 } 1859 1860 Tooltip.prototype.toggle = function (e) { 1861 var self = this 1862 if (e) { 1863 self = $(e.currentTarget).data('bs.' + this.type) 1864 if (!self) { 1865 self = new this.constructor(e.currentTarget, this.getDelegateOptions()) 1866 $(e.currentTarget).data('bs.' + this.type, self) 1867 } 1868 } 1869 1870 if (e) { 1871 self.inState.click = !self.inState.click 1872 if (self.isInStateTrue()) self.enter(self) 1873 else self.leave(self) 1874 } else { 1875 self.tip().hasClass('in') ? self.leave(self) : self.enter(self) 1876 } 1877 } 1878 1879 Tooltip.prototype.destroy = function () { 1880 var that = this 1881 clearTimeout(this.timeout) 1882 this.hide(function () { 1883 that.$element.off('.' + that.type).removeData('bs.' + that.type) 1884 if (that.$tip) { 1885 that.$tip.detach() 1886 } 1887 that.$tip = null 1888 that.$arrow = null 1889 that.$viewport = null 1890 that.$element = null 1891 }) 1892 } 1893 1894 1895 // TOOLTIP PLUGIN DEFINITION 1896 // ========================= 1897 1898 function Plugin(option) { 1899 return this.each(function () { 1900 var $this = $(this) 1901 var data = $this.data('bs.tooltip') 1902 var options = typeof option == 'object' && option 1903 1904 if (!data && /destroy|hide/.test(option)) return 1905 if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) 1906 if (typeof option == 'string') data[option]() 1907 }) 1908 } 1909 1910 var old = $.fn.tooltip 1911 1912 $.fn.tooltip = Plugin 1913 $.fn.tooltip.Constructor = Tooltip 1914 1915 1916 // TOOLTIP NO CONFLICT 1917 // =================== 1918 1919 $.fn.tooltip.noConflict = function () { 1920 $.fn.tooltip = old 1921 return this 1922 } 1923 1924}(jQuery); 1925 1926/* ======================================================================== 1927 * Bootstrap: popover.js v3.3.7 1928 * http://getbootstrap.com/javascript/#popovers 1929 * ======================================================================== 1930 * Copyright 2011-2016 Twitter, Inc. 1931 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 1932 * ======================================================================== */ 1933 1934 1935+function ($) { 1936 'use strict'; 1937 1938 // POPOVER PUBLIC CLASS DEFINITION 1939 // =============================== 1940 1941 var Popover = function (element, options) { 1942 this.init('popover', element, options) 1943 } 1944 1945 if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') 1946 1947 Popover.VERSION = '3.3.7' 1948 1949 Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { 1950 placement: 'right', 1951 trigger: 'click', 1952 content: '', 1953 template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' 1954 }) 1955 1956 1957 // NOTE: POPOVER EXTENDS tooltip.js 1958 // ================================ 1959 1960 Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) 1961 1962 Popover.prototype.constructor = Popover 1963 1964 Popover.prototype.getDefaults = function () { 1965 return Popover.DEFAULTS 1966 } 1967 1968 Popover.prototype.setContent = function () { 1969 var $tip = this.tip() 1970 var title = this.getTitle() 1971 var content = this.getContent() 1972 1973 $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) 1974 $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events 1975 this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' 1976 ](content) 1977 1978 $tip.removeClass('fade top bottom left right in') 1979 1980 // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do 1981 // this manually by checking the contents. 1982 if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() 1983 } 1984 1985 Popover.prototype.hasContent = function () { 1986 return this.getTitle() || this.getContent() 1987 } 1988 1989 Popover.prototype.getContent = function () { 1990 var $e = this.$element 1991 var o = this.options 1992 1993 return $e.attr('data-content') 1994 || (typeof o.content == 'function' ? 1995 o.content.call($e[0]) : 1996 o.content) 1997 } 1998 1999 Popover.prototype.arrow = function () { 2000 return (this.$arrow = this.$arrow || this.tip().find('.arrow')) 2001 } 2002 2003 2004 // POPOVER PLUGIN DEFINITION 2005 // ========================= 2006 2007 function Plugin(option) { 2008 return this.each(function () { 2009 var $this = $(this) 2010 var data = $this.data('bs.popover') 2011 var options = typeof option == 'object' && option 2012 2013 if (!data && /destroy|hide/.test(option)) return 2014 if (!data) $this.data('bs.popover', (data = new Popover(this, options))) 2015 if (typeof option == 'string') data[option]() 2016 }) 2017 } 2018 2019 var old = $.fn.popover 2020 2021 $.fn.popover = Plugin 2022 $.fn.popover.Constructor = Popover 2023 2024 2025 // POPOVER NO CONFLICT 2026 // =================== 2027 2028 $.fn.popover.noConflict = function () { 2029 $.fn.popover = old 2030 return this 2031 } 2032 2033}(jQuery); 2034 2035/* ======================================================================== 2036 * Bootstrap: scrollspy.js v3.3.7 2037 * http://getbootstrap.com/javascript/#scrollspy 2038 * ======================================================================== 2039 * Copyright 2011-2016 Twitter, Inc. 2040 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2041 * ======================================================================== */ 2042 2043 2044+function ($) { 2045 'use strict'; 2046 2047 // SCROLLSPY CLASS DEFINITION 2048 // ========================== 2049 2050 function ScrollSpy(element, options) { 2051 this.$body = $(document.body) 2052 this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) 2053 this.options = $.extend({}, ScrollSpy.DEFAULTS, options) 2054 this.selector = (this.options.target || '') + ' .nav li > a' 2055 this.offsets = [] 2056 this.targets = [] 2057 this.activeTarget = null 2058 this.scrollHeight = 0 2059 2060 this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) 2061 this.refresh() 2062 this.process() 2063 } 2064 2065 ScrollSpy.VERSION = '3.3.7' 2066 2067 ScrollSpy.DEFAULTS = { 2068 offset: 10 2069 } 2070 2071 ScrollSpy.prototype.getScrollHeight = function () { 2072 return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) 2073 } 2074 2075 ScrollSpy.prototype.refresh = function () { 2076 var that = this 2077 var offsetMethod = 'offset' 2078 var offsetBase = 0 2079 2080 this.offsets = [] 2081 this.targets = []
vendor: 14,034 bytes, lines 2082-2563
2082 this.scrollHeight = this.getScrollHeight() 2083 2084 if (!$.isWindow(this.$scrollElement[0])) { 2085 offsetMethod = 'position' 2086 offsetBase = this.$scrollElement.scrollTop() 2087 } 2088 2089 this.$body 2090 .find(this.selector) 2091 .map(function () { 2092 var $el = $(this) 2093 var href = $el.data('target') || $el.attr('href') 2094 var $href = /^#./.test(href) && $(href) 2095 2096 return ($href 2097 && $href.length 2098 && $href.is(':visible') 2099 && [[$href[offsetMethod]().top + offsetBase, href]]) || null 2100 }) 2101 .sort(function (a, b) { return a[0] - b[0] }) 2102 .each(function () { 2103 that.offsets.push(this[0]) 2104 that.targets.push(this[1]) 2105 }) 2106 } 2107 2108 ScrollSpy.prototype.process = function () { 2109 var scrollTop = this.$scrollElement.scrollTop() + this.options.offset 2110 var scrollHeight = this.getScrollHeight() 2111 var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() 2112 var offsets = this.offsets 2113 var targets = this.targets 2114 var activeTarget = this.activeTarget 2115 var i 2116 2117 if (this.scrollHeight != scrollHeight) { 2118 this.refresh() 2119 } 2120 2121 if (scrollTop >= maxScroll) { 2122 return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) 2123 } 2124 2125 if (activeTarget && scrollTop < offsets[0]) { 2126 this.activeTarget = null 2127 return this.clear() 2128 } 2129 2130 for (i = offsets.length; i--;) { 2131 activeTarget != targets[i] 2132 && scrollTop >= offsets[i] 2133 && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) 2134 && this.activate(targets[i]) 2135 } 2136 } 2137 2138 ScrollSpy.prototype.activate = function (target) { 2139 this.activeTarget = target 2140 2141 this.clear() 2142 2143 var selector = this.selector + 2144 '[data-target="' + target + '"],' + 2145 this.selector + '[href="' + target + '"]' 2146 2147 var active = $(selector) 2148 .parents('li') 2149 .addClass('active') 2150 2151 if (active.parent('.dropdown-menu').length) { 2152 active = active 2153 .closest('li.dropdown') 2154 .addClass('active') 2155 } 2156 2157 active.trigger('activate.bs.scrollspy') 2158 } 2159 2160 ScrollSpy.prototype.clear = function () { 2161 $(this.selector) 2162 .parentsUntil(this.options.target, '.active') 2163 .removeClass('active') 2164 } 2165 2166 2167 // SCROLLSPY PLUGIN DEFINITION 2168 // =========================== 2169 2170 function Plugin(option) { 2171 return this.each(function () { 2172 var $this = $(this) 2173 var data = $this.data('bs.scrollspy') 2174 var options = typeof option == 'object' && option 2175 2176 if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) 2177 if (typeof option == 'string') data[option]() 2178 }) 2179 } 2180 2181 var old = $.fn.scrollspy 2182 2183 $.fn.scrollspy = Plugin 2184 $.fn.scrollspy.Constructor = ScrollSpy 2185 2186 2187 // SCROLLSPY NO CONFLICT 2188 // ===================== 2189 2190 $.fn.scrollspy.noConflict = function () { 2191 $.fn.scrollspy = old 2192 return this 2193 } 2194 2195 2196 // SCROLLSPY DATA-API 2197 // ================== 2198 2199 $(window).on('load.bs.scrollspy.data-api', function () { 2200 $('[data-spy="scroll"]').each(function () { 2201 var $spy = $(this) 2202 Plugin.call($spy, $spy.data()) 2203 }) 2204 }) 2205 2206}(jQuery); 2207 2208/* ======================================================================== 2209 * Bootstrap: tab.js v3.3.7 2210 * http://getbootstrap.com/javascript/#tabs 2211 * ======================================================================== 2212 * Copyright 2011-2016 Twitter, Inc. 2213 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2214 * ======================================================================== */ 2215 2216 2217+function ($) { 2218 'use strict'; 2219 2220 // TAB CLASS DEFINITION 2221 // ==================== 2222 2223 var Tab = function (element) { 2224 // jscs:disable requireDollarBeforejQueryAssignment 2225 this.element = $(element) 2226 // jscs:enable requireDollarBeforejQueryAssignment 2227 } 2228 2229 Tab.VERSION = '3.3.7' 2230 2231 Tab.TRANSITION_DURATION = 150 2232 2233 Tab.prototype.show = function () { 2234 var $this = this.element 2235 var $ul = $this.closest('ul:not(.dropdown-menu)') 2236 var selector = $this.data('target') 2237 2238 if (!selector) { 2239 selector = $this.attr('href') 2240 selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 2241 } 2242 2243 if ($this.parent('li').hasClass('active')) return 2244 2245 var $previous = $ul.find('.active:last a') 2246 var hideEvent = $.Event('hide.bs.tab', { 2247 relatedTarget: $this[0] 2248 }) 2249 var showEvent = $.Event('show.bs.tab', { 2250 relatedTarget: $previous[0] 2251 }) 2252 2253 $previous.trigger(hideEvent) 2254 $this.trigger(showEvent) 2255 2256 if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return 2257 2258 var $target = $(selector) 2259 2260 this.activate($this.closest('li'), $ul) 2261 this.activate($target, $target.parent(), function () { 2262 $previous.trigger({ 2263 type: 'hidden.bs.tab', 2264 relatedTarget: $this[0] 2265 }) 2266 $this.trigger({ 2267 type: 'shown.bs.tab', 2268 relatedTarget: $previous[0] 2269 }) 2270 }) 2271 } 2272 2273 Tab.prototype.activate = function (element, container, callback) { 2274 var $active = container.find('> .active') 2275 var transition = callback 2276 && $.support.transition 2277 && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) 2278 2279 function next() { 2280 $active 2281 .removeClass('active') 2282 .find('> .dropdown-menu > .active') 2283 .removeClass('active') 2284 .end() 2285 .find('[data-toggle="tab"]') 2286 .attr('aria-expanded', false) 2287 2288 element 2289 .addClass('active') 2290 .find('[data-toggle="tab"]') 2291 .attr('aria-expanded', true) 2292 2293 if (transition) { 2294 element[0].offsetWidth // reflow for transition 2295 element.addClass('in') 2296 } else { 2297 element.removeClass('fade') 2298 } 2299 2300 if (element.parent('.dropdown-menu').length) { 2301 element 2302 .closest('li.dropdown') 2303 .addClass('active') 2304 .end() 2305 .find('[data-toggle="tab"]') 2306 .attr('aria-expanded', true) 2307 } 2308 2309 callback && callback() 2310 } 2311 2312 $active.length && transition ? 2313 $active 2314 .one('bsTransitionEnd', next) 2315 .emulateTransitionEnd(Tab.TRANSITION_DURATION) : 2316 next() 2317 2318 $active.removeClass('in') 2319 } 2320 2321 2322 // TAB PLUGIN DEFINITION 2323 // ===================== 2324 2325 function Plugin(option) { 2326 return this.each(function () { 2327 var $this = $(this) 2328 var data = $this.data('bs.tab') 2329 2330 if (!data) $this.data('bs.tab', (data = new Tab(this))) 2331 if (typeof option == 'string') data[option]() 2332 }) 2333 } 2334 2335 var old = $.fn.tab 2336 2337 $.fn.tab = Plugin 2338 $.fn.tab.Constructor = Tab 2339 2340 2341 // TAB NO CONFLICT 2342 // =============== 2343 2344 $.fn.tab.noConflict = function () { 2345 $.fn.tab = old 2346 return this 2347 } 2348 2349 2350 // TAB DATA-API 2351 // ============ 2352 2353 var clickHandler = function (e) { 2354 e.preventDefault() 2355 Plugin.call($(this), 'show') 2356 } 2357 2358 $(document) 2359 .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) 2360 .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) 2361 2362}(jQuery); 2363 2364/* ======================================================================== 2365 * Bootstrap: affix.js v3.3.7 2366 * http://getbootstrap.com/javascript/#affix 2367 * ======================================================================== 2368 * Copyright 2011-2016 Twitter, Inc. 2369 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 2370 * ======================================================================== */ 2371 2372 2373+function ($) { 2374 'use strict'; 2375 2376 // AFFIX CLASS DEFINITION 2377 // ====================== 2378 2379 var Affix = function (element, options) { 2380 this.options = $.extend({}, Affix.DEFAULTS, options) 2381 2382 this.$target = $(this.options.target) 2383 .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) 2384 .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) 2385 2386 this.$element = $(element) 2387 this.affixed = null 2388 this.unpin = null 2389 this.pinnedOffset = null 2390 2391 this.checkPosition() 2392 } 2393 2394 Affix.VERSION = '3.3.7' 2395 2396 Affix.RESET = 'affix affix-top affix-bottom' 2397 2398 Affix.DEFAULTS = { 2399 offset: 0, 2400 target: window 2401 } 2402 2403 Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { 2404 var scrollTop = this.$target.scrollTop() 2405 var position = this.$element.offset() 2406 var targetHeight = this.$target.height() 2407 2408 if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false 2409 2410 if (this.affixed == 'bottom') { 2411 if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' 2412 return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' 2413 } 2414 2415 var initializing = this.affixed == null 2416 var colliderTop = initializing ? scrollTop : position.top 2417 var colliderHeight = initializing ? targetHeight : height 2418 2419 if (offsetTop != null && scrollTop <= offsetTop) return 'top' 2420 if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' 2421 2422 return false 2423 } 2424 2425 Affix.prototype.getPinnedOffset = function () { 2426 if (this.pinnedOffset) return this.pinnedOffset 2427 this.$element.removeClass(Affix.RESET).addClass('affix') 2428 var scrollTop = this.$target.scrollTop() 2429 var position = this.$element.offset() 2430 return (this.pinnedOffset = position.top - scrollTop) 2431 } 2432 2433 Affix.prototype.checkPositionWithEventLoop = function () { 2434 setTimeout($.proxy(this.checkPosition, this), 1) 2435 } 2436 2437 Affix.prototype.checkPosition = function () { 2438 if (!this.$element.is(':visible')) return 2439 2440 var height = this.$element.height() 2441 var offset = this.options.offset 2442 var offsetTop = offset.top 2443 var offsetBottom = offset.bottom 2444 var scrollHeight = Math.max($(document).height(), $(document.body).height()) 2445 2446 if (typeof offset != 'object') offsetBottom = offsetTop = offset 2447 if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) 2448 if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) 2449 2450 var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) 2451 2452 if (this.affixed != affix) { 2453 if (this.unpin != null) this.$element.css('top', '') 2454 2455 var affixType = 'affix' + (affix ? '-' + affix : '') 2456 var e = $.Event(affixType + '.bs.affix') 2457 2458 this.$element.trigger(e) 2459 2460 if (e.isDefaultPrevented()) return 2461 2462 this.affixed = affix 2463 this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null 2464 2465 this.$element 2466 .removeClass(Affix.RESET) 2467 .addClass(affixType) 2468 .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') 2469 } 2470 2471 if (affix == 'bottom') { 2472 this.$element.offset({ 2473 top: scrollHeight - height - offsetBottom 2474 }) 2475 } 2476 } 2477 2478 2479 // AFFIX PLUGIN DEFINITION 2480 // ======================= 2481 2482 function Plugin(option) { 2483 return this.each(function () { 2484 var $this = $(this) 2485 var data = $this.data('bs.affix') 2486 var options = typeof option == 'object' && option 2487 2488 if (!data) $this.data('bs.affix', (data = new Affix(this, options))) 2489 if (typeof option == 'string') data[option]() 2490 }) 2491 } 2492 2493 var old = $.fn.affix 2494 2495 $.fn.affix = Plugin 2496 $.fn.affix.Constructor = Affix 2497 2498 2499 // AFFIX NO CONFLICT 2500 // ================= 2501 2502 $.fn.affix.noConflict = function () { 2503 $.fn.affix = old 2504 return this 2505 } 2506 2507 2508 // AFFIX DATA-API 2509 // ============== 2510 2511 $(window).on('load', function () { 2512 $('[data-spy="affix"]').each(function () { 2513 var $spy = $(this) 2514 var data = $spy.data() 2515 2516 data.offset = data.offset || {} 2517 2518 if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom 2519 if (data.offsetTop != null) data.offset.top = data.offsetTop 2520 2521 Plugin.call($spy, data) 2522 }) 2523 }) 2524 2525}(jQuery); 2526 2527/*! 2528 * @fileOverview TouchSwipe - jQuery Plugin 2529 * @version 1.6.18 2530 * 2531 * @author Matt Bryson http://www.github.com/mattbryson 2532 * @see https://github.com/mattbryson/TouchSwipe-Jquery-Plugin 2533 * @see http://labs.rampinteractive.co.uk/touchSwipe/ 2534 * @see http://plugins.jquery.com/project/touchSwipe 2535 * @license 2536 * Copyright (c) 2010-2015 Matt Bryson 2537 * Dual licensed under the MIT or GPL Version 2 licenses. 2538 * 2539 * 2540 * taken from https://cdnjs.cloudflare.com/ajax/libs/jquery.touchswipe/1.6.18/jquery.touchSwipe.js 2541 */ 2542 2543/* 2544 * 2545 * Changelog 2546 * $Date: 2010-12-12 (Wed, 12 Dec 2010) $ 2547 * $version: 1.0.0 2548 * $version: 1.0.1 - removed multibyte comments 2549 * 2550 * $Date: 2011-21-02 (Mon, 21 Feb 2011) $ 2551 * $version: 1.1.0 - added allowPageScroll property to allow swiping and scrolling of page 2552 * - changed handler signatures so one handler can be used for multiple events 2553 * $Date: 2011-23-02 (Wed, 23 Feb 2011) $ 2554 * $version: 1.2.0 - added click handler. This is fired if the user simply clicks and does not swipe. The event object and click target are passed to handler. 2555 * - If you use the http://code.google.com/p/jquery-ui-for-ipad-and-iphone/ plugin, you can also assign jQuery mouse events to children of a touchSwipe object. 2556 * $version: 1.2.1 - removed console log! 2557 * 2558 * $version: 1.2.2 - Fixed bug where scope was not preserved in callback methods. 2559 * 2560 * $Date: 2011-28-04 (Thurs, 28 April 2011) $ 2561 * $version: 1.2.4 - Changed licence terms to be MIT or GPL inline with jQuery. Added check for support of touch events to stop non compatible browsers erroring. 2562 * 2563 * $Date: 2011-27-09 (Tues, 27 September 2011) $
2564 * $version: 1.2.5 - Added support for testing swipes with mouse on desktop browser (thanks to https://github.com/joelhy) 2565 * 2566 * $Date: 2012-14-05 (Mon, 14 May 2012) $ 2567 * $version: 1.2.6 - Added timeThreshold between start and end touch, so user can ignore slow swipes (thanks to Mark Chase). Default is null, all swipes are detected 2568 * 2569 * $Date: 2012-05-06 (Tues, 05 June 2012) $ 2570 * $version: 1.2.7 - Changed time threshold to have null default for backwards compatibility. Added duration param passed back in events, and refactored how time is handled. 2571 * 2572 * $Date: 2012-05-06 (Tues, 05 June 2012) $ 2573 * $version: 1.2.8 - Added the possibility to return a value like null or false in the trigger callback. In that way we can control when the touch start/move should take effect or not (simply by returning in some cases return null; or return false;) This effects the ontouchstart/ontouchmove event. 2574 * 2575 * $Date: 2012-06-06 (Wed, 06 June 2012) $ 2576 * $version: 1.3.0 - Refactored whole plugin to allow for methods to be executed, as well as exposed defaults for user override. Added 'enable', 'disable', and 'destroy' methods 2577 * 2578 * $Date: 2012-05-06 (Fri, 05 June 2012) $ 2579 * $version: 1.3.1 - Bug fixes - bind() with false as last argument is no longer supported in jQuery 1.6, also, if you just click, the duration is now returned correctly. 2580 * 2581 * $Date: 2012-29-07 (Sun, 29 July 2012) $ 2582 * $version: 1.3.2 - Added fallbackToMouseEvents option to NOT capture mouse events on non touch devices. 2583 * - Added "all" fingers value to the fingers property, so any combination of fingers triggers the swipe, allowing event handlers to check the finger count 2584 * 2585 * $Date: 2012-09-08 (Thurs, 9 Aug 2012) $ 2586 * $version: 1.3.3 - Code tidy prep for minefied version 2587 * 2588 * $Date: 2012-04-10 (wed, 4 Oct 2012) $ 2589 * $version: 1.4.0 - Added pinch support, pinchIn and pinchOut 2590 * 2591 * $Date: 2012-11-10 (Thurs, 11 Oct 2012) $ 2592 * $version: 1.5.0 - Added excludedElements, a jquery selector that specifies child elements that do NOT trigger swipes. By default, this is .noSwipe 2593 * 2594 * $Date: 2012-22-10 (Mon, 22 Oct 2012) $ 2595 * $version: 1.5.1 - Fixed bug with jQuery 1.8 and trailing comma in excludedElements 2596 * - Fixed bug with IE and eventPreventDefault() 2597 * $Date: 2013-01-12 (Fri, 12 Jan 2013) $ 2598 * $version: 1.6.0 - Fixed bugs with pinching, mainly when both pinch and swipe enabled, as well as adding time threshold for multifinger gestures, so releasing one finger beofre the other doesnt trigger as single finger gesture. 2599 * - made the demo site all static local HTML pages so they can be run locally by a developer 2600 * - added jsDoc comments and added documentation for the plugin 2601 * - code tidy 2602 * - added triggerOnTouchLeave property that will end the event when the user swipes off the element. 2603 * $Date: 2013-03-23 (Sat, 23 Mar 2013) $
2604 * $version: 1.6.1 - Added support for ie8 touch events 2605 * $version: 1.6.2 - Added support for events binding with on / off / bind in jQ for all callback names. 2606 * - Deprecated the 'click' handler in favour of tap. 2607 * - added cancelThreshold property 2608 * - added option method to update init options at runtime 2609 * $version 1.6.3 - added doubletap, longtap events and longTapThreshold, doubleTapThreshold property 2610 * 2611 * $Date: 2013-04-04 (Thurs, 04 April 2013) $ 2612 * $version 1.6.4 - Fixed bug with cancelThreshold introduced in 1.6.3, where swipe status no longer fired start event, and stopped once swiping back. 2613 * 2614 * $Date: 2013-08-24 (Sat, 24 Aug 2013) $ 2615 * $version 1.6.5 - Merged a few pull requests fixing various bugs, added AMD support. 2616 * 2617 * $Date: 2014-06-04 (Wed, 04 June 2014) $ 2618 * $version 1.6.6 - Merge of pull requests. 2619 * - IE10 touch support 2620 * - Only prevent default event handling on valid swipe 2621 * - Separate license/changelog comment 2622 * - Detect if the swipe is valid at the end of the touch event. 2623 * - Pass fingerdata to event handlers. 2624 * - Add 'hold' gesture 2625 * - Be more tolerant about the tap distance 2626 * - Typos and minor fixes 2627 * 2628 * $Date: 2015-22-01 (Thurs, 22 Jan 2015) $ 2629 * $version 1.6.7 - Added patch from https://github.com/mattbryson/TouchSwipe-Jquery-Plugin/issues/206 to fix memory leak 2630 * 2631 * $Date: 2015-2-2 (Mon, 2 Feb 2015) $ 2632 * $version 1.6.8 - Added preventDefaultEvents option to proxy events regardless. 2633 * - Fixed issue with swipe and pinch not triggering at the same time 2634 * 2635 * $Date: 2015-9-6 (Tues, 9 June 2015) $ 2636 * $version 1.6.9 - Added PR from jdalton/hybrid to fix pointer events 2637 * - Added scrolling demo 2638 * - Added version property to plugin 2639 * 2640 * $Date: 2015-1-10 (Wed, 1 October 2015) $ 2641 * $version 1.6.10 - Added PR from beatspace to fix tap events 2642 * $version 1.6.11 - Added PRs from indri-indri ( Doc tidyup), kkirsche ( Bower tidy up ), UziTech (preventDefaultEvents fixes ) 2643 * - Allowed setting multiple options via .swipe("options", options_hash) and more simply .swipe(options_hash) or exisitng instances 2644 * $version 1.6.12 - Fixed bug with multi finger releases above 2 not triggering events 2645 * 2646 * $Date: 2015-12-18 (Fri, 18 December 2015) $ 2647 * $version 1.6.13 - Added PRs 2648 * - Fixed #267 allowPageScroll not working correctly 2649 * $version 1.6.14 - Fixed #220 / #248 doubletap not firing with swipes, #223 commonJS compatible 2650 * $version 1.6.15 - More bug fixes 2651 * 2652 * $Date: 2016-04-29 (Fri, 29 April 2016) $ 2653 * $version 1.6.16 - Swipes with 0 distance now allow default events to trigger. So tapping any form elements or A tags will allow default interaction, but swiping will trigger a swipe. 2654 Removed the a, input, select etc from the excluded Children list as the 0 distance tap solves that issue. 2655* $Date: 2016-05-19 (Fri, 29 April 2016) $ 2656* $version 1.6.17 - Fixed context issue when calling instance methods via $("selector").swipe("method"); 2657* $version 1.6.18 - now honors fallbackToMouseEvents=false for MS Pointer events when a Mouse is used. 2658 2659 */ 2660 2661/** 2662 * See (http://jquery.com/). 2663 * @name $ 2664 * @class 2665 * See the jQuery Library (http://jquery.com/) for full details. This just 2666 * documents the function and classes that are added to jQuery by this plug-in. 2667 */ 2668 2669/** 2670 * See (http://jquery.com/) 2671 * @name fn 2672 * @class 2673 * See the jQuery Library (http://jquery.com/) for full details. This just 2674 * documents the function and classes that are added to jQuery by this plug-in. 2675 * @memberOf $ 2676 */ 2677 2678 2679(function(factory) { 2680 if (typeof define === 'function' && define.amd && define.amd.jQuery) { 2681 // AMD. Register as anonymous module. 2682 define(['jquery'], factory); 2683 } else if (typeof module !== 'undefined' && module.exports) { 2684 // CommonJS Module 2685 factory(require("jquery")); 2686 } else { 2687 // Browser globals. 2688 factory(jQuery); 2689 } 2690}(function($) { 2691 "use strict"; 2692 2693 //Constants 2694 var VERSION = "1.6.18", 2695 LEFT = "left", 2696 RIGHT = "right", 2697 UP = "up", 2698 DOWN = "down", 2699 IN = "in", 2700 OUT = "out", 2701 2702 NONE = "none", 2703 AUTO = "auto", 2704 2705 SWIPE = "swipe", 2706 PINCH = "pinch", 2707 TAP = "tap", 2708 DOUBLE_TAP = "doubletap", 2709 LONG_TAP = "longtap", 2710 HOLD = "hold", 2711 2712 HORIZONTAL = "horizontal", 2713 VERTICAL = "vertical", 2714 2715 ALL_FINGERS = "all", 2716 2717 DOUBLE_TAP_THRESHOLD = 10, 2718 2719 PHASE_START = "start", 2720 PHASE_MOVE = "move", 2721 PHASE_END = "end", 2722 PHASE_CANCEL = "cancel", 2723 2724 SUPPORTS_TOUCH = 'ontouchstart' in window, 2725 2726 SUPPORTS_POINTER_IE10 = window.navigator.msPointerEnabled && !window.navigator.pointerEnabled && !SUPPORTS_TOUCH, 2727 2728 SUPPORTS_POINTER = (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) && !SUPPORTS_TOUCH, 2729 2730 PLUGIN_NS = 'TouchSwipe'; 2731 2732 2733 2734 /** 2735 * The default configuration, and available options to configure touch swipe with. 2736 * You can set the default values by updating any of the properties prior to instantiation. 2737 * @name $.fn.swipe.defaults 2738 * @namespace 2739 * @property {int} [fingers=1] The number of fingers to detect in a swipe. Any swipes that do not meet this requirement will NOT trigger swipe handlers. 2740 * @property {int} [threshold=75] The number of pixels that the user must move their finger by before it is considered a swipe. 2741 * @property {int} [cancelThreshold=null] The number of pixels that the user must move their finger back from the original swipe direction to cancel the gesture. 2742 * @property {int} [pinchThreshold=20] The number of pixels that the user must pinch their finger by before it is considered a pinch. 2743 * @property {int} [maxTimeThreshold=null] Time, in milliseconds, between touchStart and touchEnd must NOT exceed in order to be considered a swipe. 2744 * @property {int} [fingerReleaseThreshold=250] Time in milliseconds between releasing multiple fingers. If 2 fingers are down, and are released one after the other, if they are within this threshold, it counts as a simultaneous release. 2745 * @property {int} [longTapThreshold=500] Time in milliseconds between tap and release for a long tap 2746 * @property {int} [doubleTapThreshold=200] Time in milliseconds between 2 taps to count as a double tap 2747 * @property {function} [swipe=null] A handler to catch all swipes. See {@link $.fn.swipe#event:swipe} 2748 * @property {function} [swipeLeft=null] A handler that is triggered for "left" swipes. See {@link $.fn.swipe#event:swipeLeft} 2749 * @property {function} [swipeRight=null] A handler that is triggered for "right" swipes. See {@link $.fn.swipe#event:swipeRight} 2750 * @property {function} [swipeUp=null] A handler that is triggered for "up" swipes. See {@link $.fn.swipe#event:swipeUp} 2751 * @property {function} [swipeDown=null] A handler that is triggered for "down" swipes. See {@link $.fn.swipe#event:swipeDown} 2752 * @property {function} [swipeStatus=null] A handler triggered for every phase of the swipe. See {@link $.fn.swipe#event:swipeStatus} 2753 * @property {function} [pinchIn=null] A handler triggered for pinch in events. See {@link $.fn.swipe#event:pinchIn} 2754 * @property {function} [pinchOut=null] A handler triggered for pinch out events. See {@link $.fn.swipe#event:pinchOut} 2755 * @property {function} [pinchStatus=null] A handler triggered for every phase of a pinch. See {@link $.fn.swipe#event:pinchStatus} 2756 * @property {function} [tap=null] A handler triggered when a user just taps on the item, rather than swipes it. If they do not move, tap is triggered, if they do move, it is not. 2757 * @property {function} [doubleTap=null] A handler triggered when a user double taps on the item. The delay between taps can be set with the doubleTapThreshold property. See {@link $.fn.swipe.defaults#doubleTapThreshold} 2758 * @property {function} [longTap=null] A handler triggered when a user long taps on the item. The delay between start and end can be set with the longTapThreshold property. See {@link $.fn.swipe.defaults#longTapThreshold} 2759 * @property (function) [hold=null] A handler triggered when a user reaches longTapThreshold on the item. See {@link $.fn.swipe.defaults#longTapThreshold} 2760 * @property {boolean} [triggerOnTouchEnd=true] If true, the swipe events are triggered when the touch end event is received (user releases finger). If false, it will be triggered on reaching the threshold, and then cancel the touch event automatically. 2761 * @property {boolean} [triggerOnTouchLeave=false] If true, then when the user leaves the swipe object, the swipe will end and trigger appropriate handlers. 2762 * @property {string|undefined} [allowPageScroll='auto'] How the browser handles page scrolls when the user is swiping on a touchSwipe object. See {@link $.fn.swipe.pageScroll}. <br/><br/> 2763 <code>"auto"</code> : all undefined swipes will cause the page to scroll in that direction. <br/> 2764 <code>"none"</code> : the page will not scroll when user swipes. <br/> 2765 <code>"horizontal"</code> : will force page to scroll on horizontal swipes. <br/> 2766 <code>"vertical"</code> : will force page to scroll on vertical swipes. <br/> 2767 * @property {boolean} [fallbackToMouseEvents=true] If true mouse events are used when run on a non touch device, false will stop swipes being triggered by mouse events on non tocuh devices. 2768 * @property {string} [excludedElements=".noSwipe"] A jquery selector that specifies child elements that do NOT trigger swipes. By default this excludes elements with the class .noSwipe . 2769 * @property {boolean} [preventDefaultEvents=true] by default default events are cancelled, so the page doesn't move. You can dissable this so both native events fire as well as your handlers. 2770 2771 */ 2772 var defaults = { 2773 fingers: 1, 2774 threshold: 75, 2775 cancelThreshold: null, 2776 pinchThreshold: 20, 2777 maxTimeThreshold: null, 2778 fingerReleaseThreshold: 250, 2779 longTapThreshold: 500, 2780 doubleTapThreshold: 200, 2781 swipe: null, 2782 swipeLeft: null, 2783 swipeRight: null, 2784 swipeUp: null, 2785 swipeDown: null, 2786 swipeStatus: null, 2787 pinchIn: null, 2788 pinchOut: null, 2789 pinchStatus: null, 2790 click: null, //Deprecated since 1.6.2 2791 tap: null, 2792 doubleTap: null, 2793 longTap: null, 2794 hold: null, 2795 triggerOnTouchEnd: true, 2796 triggerOnTouchLeave: false, 2797 allowPageScroll: "auto", 2798 fallbackToMouseEvents: true,
vendor: 55,804 bytes, lines 2799-4424
2799 excludedElements: ".noSwipe", 2800 preventDefaultEvents: true 2801 }; 2802 2803 2804 2805 /** 2806 * Applies TouchSwipe behaviour to one or more jQuery objects. 2807 * The TouchSwipe plugin can be instantiated via this method, or methods within 2808 * TouchSwipe can be executed via this method as per jQuery plugin architecture. 2809 * An existing plugin can have its options changed simply by re calling .swipe(options) 2810 * @see TouchSwipe 2811 * @class 2812 * @param {Mixed} method If the current DOMNode is a TouchSwipe object, and <code>method</code> is a TouchSwipe method, then 2813 * the <code>method</code> is executed, and any following arguments are passed to the TouchSwipe method. 2814 * If <code>method</code> is an object, then the TouchSwipe class is instantiated on the current DOMNode, passing the 2815 * configuration properties defined in the object. See TouchSwipe 2816 * 2817 */ 2818 $.fn.swipe = function(method) { 2819 var $this = $(this), 2820 plugin = $this.data(PLUGIN_NS); 2821 2822 //Check if we are already instantiated and trying to execute a method 2823 if (plugin && typeof method === 'string') { 2824 if (plugin[method]) { 2825 return plugin[method].apply(plugin, Array.prototype.slice.call(arguments, 1)); 2826 } else { 2827 $.error('Method ' + method + ' does not exist on jQuery.swipe'); 2828 } 2829 } 2830 2831 //Else update existing plugin with new options hash 2832 else if (plugin && typeof method === 'object') { 2833 plugin['option'].apply(plugin, arguments); 2834 } 2835 2836 //Else not instantiated and trying to pass init object (or nothing) 2837 else if (!plugin && (typeof method === 'object' || !method)) { 2838 return init.apply(this, arguments); 2839 } 2840 2841 return $this; 2842 }; 2843 2844 /** 2845 * The version of the plugin 2846 * @readonly 2847 */ 2848 $.fn.swipe.version = VERSION; 2849 2850 2851 2852 //Expose our defaults so a user could override the plugin defaults 2853 $.fn.swipe.defaults = defaults; 2854 2855 /** 2856 * The phases that a touch event goes through. The <code>phase</code> is passed to the event handlers. 2857 * These properties are read only, attempting to change them will not alter the values passed to the event handlers. 2858 * @namespace 2859 * @readonly 2860 * @property {string} PHASE_START Constant indicating the start phase of the touch event. Value is <code>"start"</code>. 2861 * @property {string} PHASE_MOVE Constant indicating the move phase of the touch event. Value is <code>"move"</code>. 2862 * @property {string} PHASE_END Constant indicating the end phase of the touch event. Value is <code>"end"</code>. 2863 * @property {string} PHASE_CANCEL Constant indicating the cancel phase of the touch event. Value is <code>"cancel"</code>. 2864 */ 2865 $.fn.swipe.phases = { 2866 PHASE_START: PHASE_START, 2867 PHASE_MOVE: PHASE_MOVE, 2868 PHASE_END: PHASE_END, 2869 PHASE_CANCEL: PHASE_CANCEL 2870 }; 2871 2872 /** 2873 * The direction constants that are passed to the event handlers. 2874 * These properties are read only, attempting to change them will not alter the values passed to the event handlers. 2875 * @namespace 2876 * @readonly 2877 * @property {string} LEFT Constant indicating the left direction. Value is <code>"left"</code>. 2878 * @property {string} RIGHT Constant indicating the right direction. Value is <code>"right"</code>. 2879 * @property {string} UP Constant indicating the up direction. Value is <code>"up"</code>. 2880 * @property {string} DOWN Constant indicating the down direction. Value is <code>"cancel"</code>. 2881 * @property {string} IN Constant indicating the in direction. Value is <code>"in"</code>. 2882 * @property {string} OUT Constant indicating the out direction. Value is <code>"out"</code>. 2883 */ 2884 $.fn.swipe.directions = { 2885 LEFT: LEFT, 2886 RIGHT: RIGHT, 2887 UP: UP, 2888 DOWN: DOWN, 2889 IN: IN, 2890 OUT: OUT 2891 }; 2892 2893 /** 2894 * The page scroll constants that can be used to set the value of <code>allowPageScroll</code> option 2895 * These properties are read only 2896 * @namespace 2897 * @readonly 2898 * @see $.fn.swipe.defaults#allowPageScroll 2899 * @property {string} NONE Constant indicating no page scrolling is allowed. Value is <code>"none"</code>. 2900 * @property {string} HORIZONTAL Constant indicating horizontal page scrolling is allowed. Value is <code>"horizontal"</code>. 2901 * @property {string} VERTICAL Constant indicating vertical page scrolling is allowed. Value is <code>"vertical"</code>. 2902 * @property {string} AUTO Constant indicating either horizontal or vertical will be allowed, depending on the swipe handlers registered. Value is <code>"auto"</code>. 2903 */ 2904 $.fn.swipe.pageScroll = { 2905 NONE: NONE, 2906 HORIZONTAL: HORIZONTAL, 2907 VERTICAL: VERTICAL, 2908 AUTO: AUTO 2909 }; 2910 2911 /** 2912 * Constants representing the number of fingers used in a swipe. These are used to set both the value of <code>fingers</code> in the 2913 * options object, as well as the value of the <code>fingers</code> event property. 2914 * These properties are read only, attempting to change them will not alter the values passed to the event handlers. 2915 * @namespace 2916 * @readonly 2917 * @see $.fn.swipe.defaults#fingers 2918 * @property {string} ONE Constant indicating 1 finger is to be detected / was detected. Value is <code>1</code>. 2919 * @property {string} TWO Constant indicating 2 fingers are to be detected / were detected. Value is <code>2</code>. 2920 * @property {string} THREE Constant indicating 3 finger are to be detected / were detected. Value is <code>3</code>. 2921 * @property {string} FOUR Constant indicating 4 finger are to be detected / were detected. Not all devices support this. Value is <code>4</code>. 2922 * @property {string} FIVE Constant indicating 5 finger are to be detected / were detected. Not all devices support this. Value is <code>5</code>. 2923 * @property {string} ALL Constant indicating any combination of finger are to be detected. Value is <code>"all"</code>. 2924 */ 2925 $.fn.swipe.fingers = { 2926 ONE: 1, 2927 TWO: 2, 2928 THREE: 3, 2929 FOUR: 4, 2930 FIVE: 5, 2931 ALL: ALL_FINGERS 2932 }; 2933 2934 /** 2935 * Initialise the plugin for each DOM element matched 2936 * This creates a new instance of the main TouchSwipe class for each DOM element, and then 2937 * saves a reference to that instance in the elements data property. 2938 * @internal 2939 */ 2940 function init(options) { 2941 //Prep and extend the options 2942 if (options && (options.allowPageScroll === undefined && (options.swipe !== undefined || options.swipeStatus !== undefined))) { 2943 options.allowPageScroll = NONE; 2944 } 2945 2946 //Check for deprecated options 2947 //Ensure that any old click handlers are assigned to the new tap, unless we have a tap 2948 if (options.click !== undefined && options.tap === undefined) { 2949 options.tap = options.click; 2950 } 2951 2952 if (!options) { 2953 options = {}; 2954 } 2955 2956 //pass empty object so we dont modify the defaults 2957 options = $.extend({}, $.fn.swipe.defaults, options); 2958 2959 //For each element instantiate the plugin 2960 return this.each(function() { 2961 var $this = $(this); 2962 2963 //Check we havent already initialised the plugin 2964 var plugin = $this.data(PLUGIN_NS); 2965 2966 if (!plugin) { 2967 plugin = new TouchSwipe(this, options); 2968 $this.data(PLUGIN_NS, plugin); 2969 } 2970 }); 2971 } 2972 2973 /** 2974 * Main TouchSwipe Plugin Class. 2975 * Do not use this to construct your TouchSwipe object, use the jQuery plugin method $.fn.swipe(); {@link $.fn.swipe} 2976 * @private 2977 * @name TouchSwipe 2978 * @param {DOMNode} element The HTML DOM object to apply to plugin to 2979 * @param {Object} options The options to configure the plugin with. @link {$.fn.swipe.defaults} 2980 * @see $.fh.swipe.defaults 2981 * @see $.fh.swipe 2982 * @class 2983 */ 2984 function TouchSwipe(element, options) { 2985 2986 //take a local/instacne level copy of the options - should make it this.options really... 2987 var options = $.extend({}, options); 2988 2989 var useTouchEvents = (SUPPORTS_TOUCH || SUPPORTS_POINTER || !options.fallbackToMouseEvents), 2990 START_EV = useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerDown' : 'pointerdown') : 'touchstart') : 'mousedown', 2991 MOVE_EV = useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerMove' : 'pointermove') : 'touchmove') : 'mousemove', 2992 END_EV = useTouchEvents ? (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerUp' : 'pointerup') : 'touchend') : 'mouseup', 2993 LEAVE_EV = useTouchEvents ? (SUPPORTS_POINTER ? 'mouseleave' : null) : 'mouseleave', //we manually detect leave on touch devices, so null event here 2994 CANCEL_EV = (SUPPORTS_POINTER ? (SUPPORTS_POINTER_IE10 ? 'MSPointerCancel' : 'pointercancel') : 'touchcancel'); 2995 2996 2997 2998 //touch properties 2999 var distance = 0, 3000 direction = null, 3001 currentDirection = null, 3002 duration = 0, 3003 startTouchesDistance = 0, 3004 endTouchesDistance = 0, 3005 pinchZoom = 1, 3006 pinchDistance = 0, 3007 pinchDirection = 0, 3008 maximumsMap = null; 3009 3010 3011 3012 //jQuery wrapped element for this instance 3013 var $element = $(element); 3014 3015 //Current phase of th touch cycle 3016 var phase = "start"; 3017 3018 // the current number of fingers being used. 3019 var fingerCount = 0; 3020 3021 //track mouse points / delta 3022 var fingerData = {}; 3023 3024 //track times 3025 var startTime = 0, 3026 endTime = 0, 3027 previousTouchEndTime = 0, 3028 fingerCountAtRelease = 0, 3029 doubleTapStartTime = 0; 3030 3031 //Timeouts 3032 var singleTapTimeout = null, 3033 holdTimeout = null; 3034 3035 // Add gestures to all swipable areas if supported 3036 try { 3037 $element.bind(START_EV, touchStart); 3038 $element.bind(CANCEL_EV, touchCancel); 3039 } catch (e) { 3040 $.error('events not supported ' + START_EV + ',' + CANCEL_EV + ' on jQuery.swipe'); 3041 } 3042 3043 // 3044 //Public methods 3045 // 3046 3047 /** 3048 * re-enables the swipe plugin with the previous configuration 3049 * @function 3050 * @name $.fn.swipe#enable 3051 * @return {DOMNode} The Dom element that was registered with TouchSwipe 3052 * @example $("#element").swipe("enable"); 3053 */ 3054 this.enable = function() { 3055 //Incase we are already enabled, clean up... 3056 this.disable(); 3057 $element.bind(START_EV, touchStart); 3058 $element.bind(CANCEL_EV, touchCancel); 3059 return $element; 3060 }; 3061 3062 /** 3063 * disables the swipe plugin 3064 * @function 3065 * @name $.fn.swipe#disable 3066 * @return {DOMNode} The Dom element that is now registered with TouchSwipe 3067 * @example $("#element").swipe("disable"); 3068 */ 3069 this.disable = function() { 3070 removeListeners(); 3071 return $element; 3072 }; 3073 3074 /** 3075 * Destroy the swipe plugin completely. To use any swipe methods, you must re initialise the plugin. 3076 * @function 3077 * @name $.fn.swipe#destroy 3078 * @example $("#element").swipe("destroy"); 3079 */ 3080 this.destroy = function() { 3081 removeListeners(); 3082 $element.data(PLUGIN_NS, null); 3083 $element = null; 3084 }; 3085 3086 3087 /** 3088 * Allows run time updating of the swipe configuration options. 3089 * @function 3090 * @name $.fn.swipe#option 3091 * @param {String} property The option property to get or set, or a has of multiple options to set 3092 * @param {Object} [value] The value to set the property to 3093 * @return {Object} If only a property name is passed, then that property value is returned. If nothing is passed the current options hash is returned. 3094 * @example $("#element").swipe("option", "threshold"); // return the threshold 3095 * @example $("#element").swipe("option", "threshold", 100); // set the threshold after init 3096 * @example $("#element").swipe("option", {threshold:100, fingers:3} ); // set multiple properties after init 3097 * @example $("#element").swipe({threshold:100, fingers:3} ); // set multiple properties after init - the "option" method is optional! 3098 * @example $("#element").swipe("option"); // Return the current options hash 3099 * @see $.fn.swipe.defaults 3100 * 3101 */ 3102 this.option = function(property, value) { 3103 3104 if (typeof property === 'object') { 3105 options = $.extend(options, property); 3106 } else if (options[property] !== undefined) { 3107 if (value === undefined) { 3108 return options[property]; 3109 } else { 3110 options[property] = value; 3111 } 3112 } else if (!property) { 3113 return options; 3114 } else { 3115 $.error('Option ' + property + ' does not exist on jQuery.swipe.options'); 3116 } 3117 3118 return null; 3119 } 3120 3121 3122 3123 // 3124 // Private methods 3125 // 3126 3127 // 3128 // EVENTS 3129 // 3130 /** 3131 * Event handler for a touch start event. 3132 * Stops the default click event from triggering and stores where we touched 3133 * @inner 3134 * @param {object} jqEvent The normalised jQuery event object. 3135 */ 3136 function touchStart(jqEvent) { 3137 3138 //If we already in a touch event (a finger already in use) then ignore subsequent ones.. 3139 if (getTouchInProgress()) { 3140 return; 3141 } 3142 3143 //Check if this element matches any in the excluded elements selectors, or its parent is excluded, if so, DON'T swipe 3144 if ($(jqEvent.target).closest(options.excludedElements, $element).length > 0) { 3145 return; 3146 } 3147 3148 //As we use Jquery bind for events, we need to target the original event object 3149 //If these events are being programmatically triggered, we don't have an original event object, so use the Jq one. 3150 var event = jqEvent.originalEvent ? jqEvent.originalEvent : jqEvent; 3151 3152 3153 //If we have a pointer event, whoes type is 'mouse' and we have said NO mouse events, then dont do anything. 3154 if(event.pointerType && event.pointerType=="mouse" && options.fallbackToMouseEvents==false) { 3155 return; 3156 }; 3157 3158 var ret, 3159 touches = event.touches, 3160 evt = touches ? touches[0] : event; 3161 3162 phase = PHASE_START; 3163 3164 //If we support touches, get the finger count 3165 if (touches) { 3166 // get the total number of fingers touching the screen 3167 fingerCount = touches.length; 3168 } 3169 //Else this is the desktop, so stop the browser from dragging content 3170 else if (options.preventDefaultEvents !== false) { 3171 jqEvent.preventDefault(); //call this on jq event so we are cross browser 3172 } 3173 3174 //clear vars.. 3175 distance = 0; 3176 direction = null; 3177 currentDirection=null; 3178 pinchDirection = null; 3179 duration = 0; 3180 startTouchesDistance = 0; 3181 endTouchesDistance = 0; 3182 pinchZoom = 1; 3183 pinchDistance = 0; 3184 maximumsMap = createMaximumsData(); 3185 cancelMultiFingerRelease(); 3186 3187 //Create the default finger data 3188 createFingerData(0, evt); 3189 3190 // check the number of fingers is what we are looking for, or we are capturing pinches 3191 if (!touches || (fingerCount === options.fingers || options.fingers === ALL_FINGERS) || hasPinches()) { 3192 // get the coordinates of the touch 3193 startTime = getTimeStamp(); 3194 3195 if (fingerCount == 2) { 3196 //Keep track of the initial pinch distance, so we can calculate the diff later 3197 //Store second finger data as start 3198 createFingerData(1, touches[1]); 3199 startTouchesDistance = endTouchesDistance = calculateTouchesDistance(fingerData[0].start, fingerData[1].start); 3200 } 3201 3202 if (options.swipeStatus || options.pinchStatus) { 3203 ret = triggerHandler(event, phase); 3204 } 3205 } else { 3206 //A touch with more or less than the fingers we are looking for, so cancel 3207 ret = false; 3208 } 3209 3210 //If we have a return value from the users handler, then return and cancel 3211 if (ret === false) { 3212 phase = PHASE_CANCEL; 3213 triggerHandler(event, phase); 3214 return ret; 3215 } else { 3216 if (options.hold) { 3217 holdTimeout = setTimeout($.proxy(function() { 3218 //Trigger the event 3219 $element.trigger('hold', [event.target]); 3220 //Fire the callback 3221 if (options.hold) { 3222 ret = options.hold.call($element, event, event.target); 3223 } 3224 }, this), options.longTapThreshold); 3225 } 3226 3227 setTouchInProgress(true); 3228 } 3229 3230 return null; 3231 }; 3232 3233 3234 3235 /** 3236 * Event handler for a touch move event. 3237 * If we change fingers during move, then cancel the event 3238 * @inner 3239 * @param {object} jqEvent The normalised jQuery event object. 3240 */ 3241 function touchMove(jqEvent) { 3242 3243 //As we use Jquery bind for events, we need to target the original event object 3244 //If these events are being programmatically triggered, we don't have an original event object, so use the Jq one. 3245 var event = jqEvent.originalEvent ? jqEvent.originalEvent : jqEvent; 3246 3247 //If we are ending, cancelling, or within the threshold of 2 fingers being released, don't track anything.. 3248 if (phase === PHASE_END || phase === PHASE_CANCEL || inMultiFingerRelease()) 3249 return; 3250 3251 var ret, 3252 touches = event.touches, 3253 evt = touches ? touches[0] : event; 3254 3255 3256 //Update the finger data 3257 var currentFinger = updateFingerData(evt); 3258 endTime = getTimeStamp(); 3259 3260 if (touches) { 3261 fingerCount = touches.length; 3262 } 3263 3264 if (options.hold) { 3265 clearTimeout(holdTimeout); 3266 } 3267 3268 phase = PHASE_MOVE; 3269 3270 //If we have 2 fingers get Touches distance as well 3271 if (fingerCount == 2) { 3272 3273 //Keep track of the initial pinch distance, so we can calculate the diff later 3274 //We do this here as well as the start event, in case they start with 1 finger, and the press 2 fingers 3275 if (startTouchesDistance == 0) { 3276 //Create second finger if this is the first time... 3277 createFingerData(1, touches[1]); 3278 3279 startTouchesDistance = endTouchesDistance = calculateTouchesDistance(fingerData[0].start, fingerData[1].start); 3280 } else { 3281 //Else just update the second finger 3282 updateFingerData(touches[1]); 3283 3284 endTouchesDistance = calculateTouchesDistance(fingerData[0].end, fingerData[1].end); 3285 pinchDirection = calculatePinchDirection(fingerData[0].end, fingerData[1].end); 3286 } 3287 3288 pinchZoom = calculatePinchZoom(startTouchesDistance, endTouchesDistance); 3289 pinchDistance = Math.abs(startTouchesDistance - endTouchesDistance); 3290 } 3291 3292 if ((fingerCount === options.fingers || options.fingers === ALL_FINGERS) || !touches || hasPinches()) { 3293 3294 //The overall direction of the swipe. From start to now. 3295 direction = calculateDirection(currentFinger.start, currentFinger.end); 3296 3297 //The immediate direction of the swipe, direction between the last movement and this one. 3298 currentDirection = calculateDirection(currentFinger.last, currentFinger.end); 3299 3300 //Check if we need to prevent default event (page scroll / pinch zoom) or not 3301 validateDefaultEvent(jqEvent, currentDirection); 3302 3303 //Distance and duration are all off the main finger 3304 distance = calculateDistance(currentFinger.start, currentFinger.end); 3305 duration = calculateDuration(); 3306 3307 //Cache the maximum distance we made in this direction 3308 setMaxDistance(direction, distance); 3309 3310 //Trigger status handler 3311 ret = triggerHandler(event, phase); 3312 3313 3314 //If we trigger end events when threshold are met, or trigger events when touch leaves element 3315 if (!options.triggerOnTouchEnd || options.triggerOnTouchLeave) { 3316 3317 var inBounds = true; 3318 3319 //If checking if we leave the element, run the bounds check (we can use touchleave as its not supported on webkit) 3320 if (options.triggerOnTouchLeave) { 3321 var bounds = getbounds(this); 3322 inBounds = isInBounds(currentFinger.end, bounds); 3323 } 3324 3325 //Trigger end handles as we swipe if thresholds met or if we have left the element if the user has asked to check these.. 3326 if (!options.triggerOnTouchEnd && inBounds) { 3327 phase = getNextPhase(PHASE_MOVE); 3328 } 3329 //We end if out of bounds here, so set current phase to END, and check if its modified 3330 else if (options.triggerOnTouchLeave && !inBounds) { 3331 phase = getNextPhase(PHASE_END); 3332 } 3333 3334 if (phase == PHASE_CANCEL || phase == PHASE_END) { 3335 triggerHandler(event, phase); 3336 } 3337 } 3338 } else { 3339 phase = PHASE_CANCEL; 3340 triggerHandler(event, phase); 3341 } 3342 3343 if (ret === false) { 3344 phase = PHASE_CANCEL; 3345 triggerHandler(event, phase); 3346 } 3347 } 3348 3349 3350 3351 3352 /** 3353 * Event handler for a touch end event. 3354 * Calculate the direction and trigger events 3355 * @inner 3356 * @param {object} jqEvent The normalised jQuery event object. 3357 */ 3358 function touchEnd(jqEvent) { 3359 //As we use Jquery bind for events, we need to target the original event object 3360 //If these events are being programmatically triggered, we don't have an original event object, so use the Jq one. 3361 var event = jqEvent.originalEvent ? jqEvent.originalEvent : jqEvent, 3362 touches = event.touches; 3363 3364 //If we are still in a touch with the device wait a fraction and see if the other finger comes up 3365 //if it does within the threshold, then we treat it as a multi release, not a single release and end the touch / swipe 3366 if (touches) { 3367 if (touches.length && !inMultiFingerRelease()) { 3368 startMultiFingerRelease(event); 3369 return true; 3370 } else if (touches.length && inMultiFingerRelease()) { 3371 return true; 3372 } 3373 } 3374 3375 //If a previous finger has been released, check how long ago, if within the threshold, then assume it was a multifinger release. 3376 //This is used to allow 2 fingers to release fractionally after each other, whilst maintaining the event as containing 2 fingers, not 1 3377 if (inMultiFingerRelease()) { 3378 fingerCount = fingerCountAtRelease; 3379 } 3380 3381 //Set end of swipe 3382 endTime = getTimeStamp(); 3383 3384 //Get duration incase move was never fired 3385 duration = calculateDuration(); 3386 3387 //If we trigger handlers at end of swipe OR, we trigger during, but they didnt trigger and we are still in the move phase 3388 if (didSwipeBackToCancel() || !validateSwipeDistance()) { 3389 phase = PHASE_CANCEL; 3390 triggerHandler(event, phase); 3391 } else if (options.triggerOnTouchEnd || (options.triggerOnTouchEnd === false && phase === PHASE_MOVE)) { 3392 //call this on jq event so we are cross browser 3393 if (options.preventDefaultEvents !== false) { 3394 jqEvent.preventDefault(); 3395 } 3396 phase = PHASE_END; 3397 triggerHandler(event, phase); 3398 } 3399 //Special cases - A tap should always fire on touch end regardless, 3400 //So here we manually trigger the tap end handler by itself 3401 //We dont run trigger handler as it will re-trigger events that may have fired already 3402 else if (!options.triggerOnTouchEnd && hasTap()) { 3403 //Trigger the pinch events... 3404 phase = PHASE_END; 3405 triggerHandlerForGesture(event, phase, TAP); 3406 } else if (phase === PHASE_MOVE) { 3407 phase = PHASE_CANCEL; 3408 triggerHandler(event, phase); 3409 } 3410 3411 setTouchInProgress(false); 3412 3413 return null; 3414 } 3415 3416 3417 3418 /** 3419 * Event handler for a touch cancel event. 3420 * Clears current vars 3421 * @inner 3422 */ 3423 function touchCancel() { 3424 // reset the variables back to default values 3425 fingerCount = 0; 3426 endTime = 0; 3427 startTime = 0; 3428 startTouchesDistance = 0; 3429 endTouchesDistance = 0; 3430 pinchZoom = 1; 3431 3432 //If we were in progress of tracking a possible multi touch end, then re set it. 3433 cancelMultiFingerRelease(); 3434 3435 setTouchInProgress(false); 3436 } 3437 3438 3439 /** 3440 * Event handler for a touch leave event. 3441 * This is only triggered on desktops, in touch we work this out manually 3442 * as the touchleave event is not supported in webkit 3443 * @inner 3444 */ 3445 function touchLeave(jqEvent) { 3446 //If these events are being programmatically triggered, we don't have an original event object, so use the Jq one. 3447 var event = jqEvent.originalEvent ? jqEvent.originalEvent : jqEvent; 3448 3449 //If we have the trigger on leave property set.... 3450 if (options.triggerOnTouchLeave) { 3451 phase = getNextPhase(PHASE_END); 3452 triggerHandler(event, phase); 3453 } 3454 } 3455 3456 /** 3457 * Removes all listeners that were associated with the plugin 3458 * @inner 3459 */ 3460 function removeListeners() { 3461 $element.unbind(START_EV, touchStart); 3462 $element.unbind(CANCEL_EV, touchCancel); 3463 $element.unbind(MOVE_EV, touchMove); 3464 $element.unbind(END_EV, touchEnd); 3465 3466 //we only have leave events on desktop, we manually calculate leave on touch as its not supported in webkit 3467 if (LEAVE_EV) { 3468 $element.unbind(LEAVE_EV, touchLeave); 3469 } 3470 3471 setTouchInProgress(false); 3472 } 3473 3474 3475 /** 3476 * Checks if the time and distance thresholds have been met, and if so then the appropriate handlers are fired. 3477 */ 3478 function getNextPhase(currentPhase) { 3479 3480 var nextPhase = currentPhase; 3481 3482 // Ensure we have valid swipe (under time and over distance and check if we are out of bound...) 3483 var validTime = validateSwipeTime(); 3484 var validDistance = validateSwipeDistance(); 3485 var didCancel = didSwipeBackToCancel(); 3486 3487 //If we have exceeded our time, then cancel 3488 if (!validTime || didCancel) { 3489 nextPhase = PHASE_CANCEL; 3490 } 3491 //Else if we are moving, and have reached distance then end 3492 else if (validDistance && currentPhase == PHASE_MOVE && (!options.triggerOnTouchEnd || options.triggerOnTouchLeave)) { 3493 nextPhase = PHASE_END; 3494 } 3495 //Else if we have ended by leaving and didn't reach distance, then cancel 3496 else if (!validDistance && currentPhase == PHASE_END && options.triggerOnTouchLeave) { 3497 nextPhase = PHASE_CANCEL; 3498 } 3499 3500 return nextPhase; 3501 } 3502 3503 3504 /** 3505 * Trigger the relevant event handler 3506 * The handlers are passed the original event, the element that was swiped, and in the case of the catch all handler, the direction that was swiped, "left", "right", "up", or "down" 3507 * @param {object} event the original event object 3508 * @param {string} phase the phase of the swipe (start, end cancel etc) {@link $.fn.swipe.phases} 3509 * @inner 3510 */ 3511 function triggerHandler(event, phase) { 3512 3513 3514 3515 var ret, 3516 touches = event.touches; 3517 3518 // SWIPE GESTURES 3519 if (didSwipe() || hasSwipes()) { 3520 ret = triggerHandlerForGesture(event, phase, SWIPE); 3521 } 3522 3523 // PINCH GESTURES (if the above didn't cancel) 3524 if ((didPinch() || hasPinches()) && ret !== false) { 3525 ret = triggerHandlerForGesture(event, phase, PINCH); 3526 } 3527 3528 // CLICK / TAP (if the above didn't cancel) 3529 if (didDoubleTap() && ret !== false) { 3530 //Trigger the tap events... 3531 ret = triggerHandlerForGesture(event, phase, DOUBLE_TAP); 3532 } 3533 3534 // CLICK / TAP (if the above didn't cancel) 3535 else if (didLongTap() && ret !== false) { 3536 //Trigger the tap events... 3537 ret = triggerHandlerForGesture(event, phase, LONG_TAP); 3538 } 3539 3540 // CLICK / TAP (if the above didn't cancel) 3541 else if (didTap() && ret !== false) { 3542 //Trigger the tap event.. 3543 ret = triggerHandlerForGesture(event, phase, TAP); 3544 } 3545 3546 3547 3548 // If we are cancelling the gesture, then manually trigger the reset handler 3549 if (phase === PHASE_CANCEL) { 3550 3551 touchCancel(event); 3552 } 3553 3554 3555 3556 3557 // If we are ending the gesture, then manually trigger the reset handler IF all fingers are off 3558 if (phase === PHASE_END) { 3559 //If we support touch, then check that all fingers are off before we cancel 3560 if (touches) { 3561 if (!touches.length) { 3562 touchCancel(event); 3563 } 3564 } else { 3565 touchCancel(event); 3566 } 3567 } 3568 3569 return ret; 3570 } 3571 3572 3573 3574 /** 3575 * Trigger the relevant event handler 3576 * The handlers are passed the original event, the element that was swiped, and in the case of the catch all handler, the direction that was swiped, "left", "right", "up", or "down" 3577 * @param {object} event the original event object 3578 * @param {string} phase the phase of the swipe (start, end cancel etc) {@link $.fn.swipe.phases} 3579 * @param {string} gesture the gesture to trigger a handler for : PINCH or SWIPE {@link $.fn.swipe.gestures} 3580 * @return Boolean False, to indicate that the event should stop propagation, or void. 3581 * @inner 3582 */ 3583 function triggerHandlerForGesture(event, phase, gesture) { 3584 3585 var ret; 3586 3587 //SWIPES.... 3588 if (gesture == SWIPE) { 3589 //Trigger status every time.. 3590 $element.trigger('swipeStatus', [phase, direction || null, distance || 0, duration || 0, fingerCount, fingerData, currentDirection]); 3591 3592 if (options.swipeStatus) { 3593 ret = options.swipeStatus.call($element, event, phase, direction || null, distance || 0, duration || 0, fingerCount, fingerData, currentDirection); 3594 //If the status cancels, then dont run the subsequent event handlers.. 3595 if (ret === false) return false; 3596 } 3597 3598 if (phase == PHASE_END && validateSwipe()) { 3599 3600 //Cancel any taps that were in progress... 3601 clearTimeout(singleTapTimeout); 3602 clearTimeout(holdTimeout); 3603 3604 $element.trigger('swipe', [direction, distance, duration, fingerCount, fingerData, currentDirection]); 3605 3606 if (options.swipe) { 3607 ret = options.swipe.call($element, event, direction, distance, duration, fingerCount, fingerData, currentDirection); 3608 //If the status cancels, then dont run the subsequent event handlers.. 3609 if (ret === false) return false; 3610 } 3611 3612 //trigger direction specific event handlers 3613 switch (direction) { 3614 case LEFT: 3615 $element.trigger('swipeLeft', [direction, distance, duration, fingerCount, fingerData, currentDirection]); 3616 3617 if (options.swipeLeft) { 3618 ret = options.swipeLeft.call($element, event, direction, distance, duration, fingerCount, fingerData, currentDirection); 3619 } 3620 break; 3621 3622 case RIGHT: 3623 $element.trigger('swipeRight', [direction, distance, duration, fingerCount, fingerData, currentDirection]); 3624 3625 if (options.swipeRight) { 3626 ret = options.swipeRight.call($element, event, direction, distance, duration, fingerCount, fingerData, currentDirection); 3627 } 3628 break; 3629 3630 case UP: 3631 $element.trigger('swipeUp', [direction, distance, duration, fingerCount, fingerData, currentDirection]); 3632 3633 if (options.swipeUp) { 3634 ret = options.swipeUp.call($element, event, direction, distance, duration, fingerCount, fingerData, currentDirection); 3635 } 3636 break; 3637 3638 case DOWN: 3639 $element.trigger('swipeDown', [direction, distance, duration, fingerCount, fingerData, currentDirection]); 3640 3641 if (options.swipeDown) { 3642 ret = options.swipeDown.call($element, event, direction, distance, duration, fingerCount, fingerData, currentDirection); 3643 } 3644 break; 3645 } 3646 } 3647 } 3648 3649 3650 //PINCHES.... 3651 if (gesture == PINCH) { 3652 $element.trigger('pinchStatus', [phase, pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData]); 3653 3654 if (options.pinchStatus) { 3655 ret = options.pinchStatus.call($element, event, phase, pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData); 3656 //If the status cancels, then dont run the subsequent event handlers.. 3657 if (ret === false) return false; 3658 } 3659 3660 if (phase == PHASE_END && validatePinch()) { 3661 3662 switch (pinchDirection) { 3663 case IN: 3664 $element.trigger('pinchIn', [pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData]); 3665 3666 if (options.pinchIn) { 3667 ret = options.pinchIn.call($element, event, pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData); 3668 } 3669 break; 3670 3671 case OUT: 3672 $element.trigger('pinchOut', [pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData]); 3673 3674 if (options.pinchOut) { 3675 ret = options.pinchOut.call($element, event, pinchDirection || null, pinchDistance || 0, duration || 0, fingerCount, pinchZoom, fingerData); 3676 } 3677 break; 3678 } 3679 } 3680 } 3681 3682 if (gesture == TAP) { 3683 if (phase === PHASE_CANCEL || phase === PHASE_END) { 3684 3685 clearTimeout(singleTapTimeout); 3686 clearTimeout(holdTimeout); 3687 3688 //If we are also looking for doubelTaps, wait incase this is one... 3689 if (hasDoubleTap() && !inDoubleTap()) { 3690 doubleTapStartTime = getTimeStamp(); 3691 3692 //Now wait for the double tap timeout, and trigger this single tap 3693 //if its not cancelled by a double tap 3694 singleTapTimeout = setTimeout($.proxy(function() { 3695 doubleTapStartTime = null; 3696 $element.trigger('tap', [event.target]); 3697 3698 if (options.tap) { 3699 ret = options.tap.call($element, event, event.target); 3700 } 3701 }, this), options.doubleTapThreshold); 3702 3703 } else { 3704 doubleTapStartTime = null; 3705 $element.trigger('tap', [event.target]); 3706 if (options.tap) { 3707 ret = options.tap.call($element, event, event.target); 3708 } 3709 } 3710 } 3711 } else if (gesture == DOUBLE_TAP) { 3712 if (phase === PHASE_CANCEL || phase === PHASE_END) { 3713 clearTimeout(singleTapTimeout); 3714 clearTimeout(holdTimeout); 3715 doubleTapStartTime = null; 3716 $element.trigger('doubletap', [event.target]); 3717 3718 if (options.doubleTap) { 3719 ret = options.doubleTap.call($element, event, event.target); 3720 } 3721 } 3722 } else if (gesture == LONG_TAP) { 3723 if (phase === PHASE_CANCEL || phase === PHASE_END) { 3724 clearTimeout(singleTapTimeout); 3725 doubleTapStartTime = null; 3726 3727 $element.trigger('longtap', [event.target]); 3728 if (options.longTap) { 3729 ret = options.longTap.call($element, event, event.target); 3730 } 3731 } 3732 } 3733 3734 return ret; 3735 } 3736 3737 3738 // 3739 // GESTURE VALIDATION 3740 // 3741 3742 /** 3743 * Checks the user has swipe far enough 3744 * @return Boolean if <code>threshold</code> has been set, return true if the threshold was met, else false. 3745 * If no threshold was set, then we return true. 3746 * @inner 3747 */ 3748 function validateSwipeDistance() { 3749 var valid = true; 3750 //If we made it past the min swipe distance.. 3751 if (options.threshold !== null) { 3752 valid = distance >= options.threshold; 3753 } 3754 3755 return valid; 3756 } 3757 3758 /** 3759 * Checks the user has swiped back to cancel. 3760 * @return Boolean if <code>cancelThreshold</code> has been set, return true if the cancelThreshold was met, else false. 3761 * If no cancelThreshold was set, then we return true. 3762 * @inner 3763 */ 3764 function didSwipeBackToCancel() { 3765 var cancelled = false; 3766 if (options.cancelThreshold !== null && direction !== null) { 3767 cancelled = (getMaxDistance(direction) - distance) >= options.cancelThreshold; 3768 } 3769 3770 return cancelled; 3771 } 3772 3773 /** 3774 * Checks the user has pinched far enough 3775 * @return Boolean if <code>pinchThreshold</code> has been set, return true if the threshold was met, else false. 3776 * If no threshold was set, then we return true. 3777 * @inner 3778 */ 3779 function validatePinchDistance() { 3780 if (options.pinchThreshold !== null) { 3781 return pinchDistance >= options.pinchThreshold; 3782 } 3783 return true; 3784 } 3785 3786 /** 3787 * Checks that the time taken to swipe meets the minimum / maximum requirements 3788 * @return Boolean 3789 * @inner 3790 */ 3791 function validateSwipeTime() { 3792 var result; 3793 //If no time set, then return true 3794 if (options.maxTimeThreshold) { 3795 if (duration >= options.maxTimeThreshold) { 3796 result = false; 3797 } else { 3798 result = true; 3799 } 3800 } else { 3801 result = true; 3802 } 3803 3804 return result; 3805 } 3806 3807 3808 /** 3809 * Checks direction of the swipe and the value allowPageScroll to see if we should allow or prevent the default behaviour from occurring. 3810 * This will essentially allow page scrolling or not when the user is swiping on a touchSwipe object. 3811 * @param {object} jqEvent The normalised jQuery representation of the event object. 3812 * @param {string} direction The direction of the event. See {@link $.fn.swipe.directions} 3813 * @see $.fn.swipe.directions 3814 * @inner 3815 */ 3816 function validateDefaultEvent(jqEvent, direction) { 3817 3818 //If the option is set, allways allow the event to bubble up (let user handle weirdness) 3819 if (options.preventDefaultEvents === false) { 3820 return; 3821 } 3822 3823 if (options.allowPageScroll === NONE) { 3824 jqEvent.preventDefault(); 3825 } else { 3826 var auto = options.allowPageScroll === AUTO; 3827 3828 switch (direction) { 3829 case LEFT: 3830 if ((options.swipeLeft && auto) || (!auto && options.allowPageScroll != HORIZONTAL)) { 3831 jqEvent.preventDefault(); 3832 } 3833 break; 3834 3835 case RIGHT: 3836 if ((options.swipeRight && auto) || (!auto && options.allowPageScroll != HORIZONTAL)) { 3837 jqEvent.preventDefault(); 3838 } 3839 break; 3840 3841 case UP: 3842 if ((options.swipeUp && auto) || (!auto && options.allowPageScroll != VERTICAL)) { 3843 jqEvent.preventDefault(); 3844 } 3845 break; 3846 3847 case DOWN: 3848 if ((options.swipeDown && auto) || (!auto && options.allowPageScroll != VERTICAL)) { 3849 jqEvent.preventDefault(); 3850 } 3851 break; 3852 3853 case NONE: 3854 3855 break; 3856 } 3857 } 3858 } 3859 3860 3861 // PINCHES 3862 /** 3863 * Returns true of the current pinch meets the thresholds 3864 * @return Boolean 3865 * @inner 3866 */ 3867 function validatePinch() { 3868 var hasCorrectFingerCount = validateFingers(); 3869 var hasEndPoint = validateEndPoint(); 3870 var hasCorrectDistance = validatePinchDistance(); 3871 return hasCorrectFingerCount && hasEndPoint && hasCorrectDistance; 3872 3873 } 3874 3875 /** 3876 * Returns true if any Pinch events have been registered 3877 * @return Boolean 3878 * @inner 3879 */ 3880 function hasPinches() { 3881 //Enure we dont return 0 or null for false values 3882 return !!(options.pinchStatus || options.pinchIn || options.pinchOut); 3883 } 3884 3885 /** 3886 * Returns true if we are detecting pinches, and have one 3887 * @return Boolean 3888 * @inner 3889 */ 3890 function didPinch() { 3891 //Enure we dont return 0 or null for false values 3892 return !!(validatePinch() && hasPinches()); 3893 } 3894 3895 3896 3897 3898 // SWIPES 3899 /** 3900 * Returns true if the current swipe meets the thresholds 3901 * @return Boolean 3902 * @inner 3903 */ 3904 function validateSwipe() { 3905 //Check validity of swipe 3906 var hasValidTime = validateSwipeTime(); 3907 var hasValidDistance = validateSwipeDistance(); 3908 var hasCorrectFingerCount = validateFingers(); 3909 var hasEndPoint = validateEndPoint(); 3910 var didCancel = didSwipeBackToCancel(); 3911 3912 // if the user swiped more than the minimum length, perform the appropriate action 3913 // hasValidDistance is null when no distance is set 3914 var valid = !didCancel && hasEndPoint && hasCorrectFingerCount && hasValidDistance && hasValidTime; 3915 3916 return valid; 3917 } 3918 3919 /** 3920 * Returns true if any Swipe events have been registered 3921 * @return Boolean 3922 * @inner 3923 */ 3924 function hasSwipes() { 3925 //Enure we dont return 0 or null for false values 3926 return !!(options.swipe || options.swipeStatus || options.swipeLeft || options.swipeRight || options.swipeUp || options.swipeDown); 3927 } 3928 3929 3930 /** 3931 * Returns true if we are detecting swipes and have one 3932 * @return Boolean 3933 * @inner 3934 */ 3935 function didSwipe() { 3936 //Enure we dont return 0 or null for false values 3937 return !!(validateSwipe() && hasSwipes()); 3938 } 3939 3940 /** 3941 * Returns true if we have matched the number of fingers we are looking for 3942 * @return Boolean 3943 * @inner 3944 */ 3945 function validateFingers() { 3946 //The number of fingers we want were matched, or on desktop we ignore 3947 return ((fingerCount === options.fingers || options.fingers === ALL_FINGERS) || !SUPPORTS_TOUCH); 3948 } 3949 3950 /** 3951 * Returns true if we have an end point for the swipe 3952 * @return Boolean 3953 * @inner 3954 */ 3955 function validateEndPoint() { 3956 //We have an end value for the finger 3957 return fingerData[0].end.x !== 0; 3958 } 3959 3960 // TAP / CLICK 3961 /** 3962 * Returns true if a click / tap events have been registered 3963 * @return Boolean 3964 * @inner 3965 */ 3966 function hasTap() { 3967 //Enure we dont return 0 or null for false values 3968 return !!(options.tap); 3969 } 3970 3971 /** 3972 * Returns true if a double tap events have been registered 3973 * @return Boolean 3974 * @inner 3975 */ 3976 function hasDoubleTap() { 3977 //Enure we dont return 0 or null for false values 3978 return !!(options.doubleTap); 3979 } 3980 3981 /** 3982 * Returns true if any long tap events have been registered 3983 * @return Boolean 3984 * @inner 3985 */ 3986 function hasLongTap() { 3987 //Enure we dont return 0 or null for false values 3988 return !!(options.longTap); 3989 } 3990 3991 /** 3992 * Returns true if we could be in the process of a double tap (one tap has occurred, we are listening for double taps, and the threshold hasn't past. 3993 * @return Boolean 3994 * @inner 3995 */ 3996 function validateDoubleTap() { 3997 if (doubleTapStartTime == null) { 3998 return false; 3999 } 4000 var now = getTimeStamp(); 4001 return (hasDoubleTap() && ((now - doubleTapStartTime) <= options.doubleTapThreshold)); 4002 } 4003 4004 /** 4005 * Returns true if we could be in the process of a double tap (one tap has occurred, we are listening for double taps, and the threshold hasn't past. 4006 * @return Boolean 4007 * @inner 4008 */ 4009 function inDoubleTap() { 4010 return validateDoubleTap(); 4011 } 4012 4013 4014 /** 4015 * Returns true if we have a valid tap 4016 * @return Boolean 4017 * @inner 4018 */ 4019 function validateTap() { 4020 return ((fingerCount === 1 || !SUPPORTS_TOUCH) && (isNaN(distance) || distance < options.threshold)); 4021 } 4022 4023 /** 4024 * Returns true if we have a valid long tap 4025 * @return Boolean 4026 * @inner 4027 */ 4028 function validateLongTap() { 4029 //slight threshold on moving finger 4030 return ((duration > options.longTapThreshold) && (distance < DOUBLE_TAP_THRESHOLD)); 4031 } 4032 4033 /** 4034 * Returns true if we are detecting taps and have one 4035 * @return Boolean 4036 * @inner 4037 */ 4038 function didTap() { 4039 //Enure we dont return 0 or null for false values 4040 return !!(validateTap() && hasTap()); 4041 } 4042 4043 4044 /** 4045 * Returns true if we are detecting double taps and have one 4046 * @return Boolean 4047 * @inner 4048 */ 4049 function didDoubleTap() { 4050 //Enure we dont return 0 or null for false values 4051 return !!(validateDoubleTap() && hasDoubleTap()); 4052 } 4053 4054 /** 4055 * Returns true if we are detecting long taps and have one 4056 * @return Boolean 4057 * @inner 4058 */ 4059 function didLongTap() { 4060 //Enure we dont return 0 or null for false values 4061 return !!(validateLongTap() && hasLongTap()); 4062 } 4063 4064 4065 4066 4067 // MULTI FINGER TOUCH 4068 /** 4069 * Starts tracking the time between 2 finger releases, and keeps track of how many fingers we initially had up 4070 * @inner 4071 */ 4072 function startMultiFingerRelease(event) { 4073 previousTouchEndTime = getTimeStamp(); 4074 fingerCountAtRelease = event.touches.length + 1; 4075 } 4076 4077 /** 4078 * Cancels the tracking of time between 2 finger releases, and resets counters 4079 * @inner 4080 */ 4081 function cancelMultiFingerRelease() { 4082 previousTouchEndTime = 0; 4083 fingerCountAtRelease = 0; 4084 } 4085 4086 /** 4087 * Checks if we are in the threshold between 2 fingers being released 4088 * @return Boolean 4089 * @inner 4090 */ 4091 function inMultiFingerRelease() { 4092 4093 var withinThreshold = false; 4094 4095 if (previousTouchEndTime) { 4096 var diff = getTimeStamp() - previousTouchEndTime 4097 if (diff <= options.fingerReleaseThreshold) { 4098 withinThreshold = true; 4099 } 4100 } 4101 4102 return withinThreshold; 4103 } 4104 4105 4106 /** 4107 * gets a data flag to indicate that a touch is in progress 4108 * @return Boolean 4109 * @inner 4110 */ 4111 function getTouchInProgress() { 4112 //strict equality to ensure only true and false are returned 4113 return !!($element.data(PLUGIN_NS + '_intouch') === true); 4114 } 4115 4116 /** 4117 * Sets a data flag to indicate that a touch is in progress 4118 * @param {boolean} val The value to set the property to 4119 * @inner 4120 */ 4121 function setTouchInProgress(val) { 4122 4123 //If destroy is called in an event handler, we have no el, and we have already cleaned up, so return. 4124 if(!$element) { return; } 4125 4126 //Add or remove event listeners depending on touch status 4127 if (val === true) { 4128 $element.bind(MOVE_EV, touchMove); 4129 $element.bind(END_EV, touchEnd); 4130 4131 //we only have leave events on desktop, we manually calcuate leave on touch as its not supported in webkit 4132 if (LEAVE_EV) { 4133 $element.bind(LEAVE_EV, touchLeave); 4134 } 4135 } else { 4136 4137 $element.unbind(MOVE_EV, touchMove, false); 4138 $element.unbind(END_EV, touchEnd, false); 4139 4140 //we only have leave events on desktop, we manually calcuate leave on touch as its not supported in webkit 4141 if (LEAVE_EV) { 4142 $element.unbind(LEAVE_EV, touchLeave, false); 4143 } 4144 } 4145 4146 4147 //strict equality to ensure only true and false can update the value 4148 $element.data(PLUGIN_NS + '_intouch', val === true); 4149 } 4150 4151 4152 /** 4153 * Creates the finger data for the touch/finger in the event object. 4154 * @param {int} id The id to store the finger data under (usually the order the fingers were pressed) 4155 * @param {object} evt The event object containing finger data 4156 * @return finger data object 4157 * @inner 4158 */ 4159 function createFingerData(id, evt) { 4160 var f = { 4161 start: { 4162 x: 0, 4163 y: 0 4164 }, 4165 last: { 4166 x: 0, 4167 y: 0 4168 }, 4169 end: { 4170 x: 0, 4171 y: 0 4172 } 4173 }; 4174 f.start.x = f.last.x = f.end.x = evt.pageX || evt.clientX; 4175 f.start.y = f.last.y = f.end.y = evt.pageY || evt.clientY; 4176 fingerData[id] = f; 4177 return f; 4178 } 4179 4180 /** 4181 * Updates the finger data for a particular event object 4182 * @param {object} evt The event object containing the touch/finger data to upadte 4183 * @return a finger data object. 4184 * @inner 4185 */ 4186 function updateFingerData(evt) { 4187 var id = evt.identifier !== undefined ? evt.identifier : 0; 4188 var f = getFingerData(id); 4189 4190 if (f === null) { 4191 f = createFingerData(id, evt); 4192 } 4193 4194 f.last.x = f.end.x; 4195 f.last.y = f.end.y; 4196 4197 f.end.x = evt.pageX || evt.clientX; 4198 f.end.y = evt.pageY || evt.clientY; 4199 4200 return f; 4201 } 4202 4203 /** 4204 * Returns a finger data object by its event ID. 4205 * Each touch event has an identifier property, which is used 4206 * to track repeat touches 4207 * @param {int} id The unique id of the finger in the sequence of touch events. 4208 * @return a finger data object. 4209 * @inner 4210 */ 4211 function getFingerData(id) { 4212 return fingerData[id] || null; 4213 } 4214 4215 4216 /** 4217 * Sets the maximum distance swiped in the given direction. 4218 * If the new value is lower than the current value, the max value is not changed. 4219 * @param {string} direction The direction of the swipe 4220 * @param {int} distance The distance of the swipe 4221 * @inner 4222 */ 4223 function setMaxDistance(direction, distance) { 4224 if(direction==NONE) return; 4225 distance = Math.max(distance, getMaxDistance(direction)); 4226 maximumsMap[direction].distance = distance; 4227 } 4228 4229 /** 4230 * gets the maximum distance swiped in the given direction. 4231 * @param {string} direction The direction of the swipe 4232 * @return int The distance of the swipe 4233 * @inner 4234 */ 4235 function getMaxDistance(direction) { 4236 if (maximumsMap[direction]) return maximumsMap[direction].distance; 4237 return undefined; 4238 } 4239 4240 /** 4241 * Creats a map of directions to maximum swiped values. 4242 * @return Object A dictionary of maximum values, indexed by direction. 4243 * @inner 4244 */ 4245 function createMaximumsData() { 4246 var maxData = {}; 4247 maxData[LEFT] = createMaximumVO(LEFT); 4248 maxData[RIGHT] = createMaximumVO(RIGHT); 4249 maxData[UP] = createMaximumVO(UP); 4250 maxData[DOWN] = createMaximumVO(DOWN); 4251 4252 return maxData; 4253 } 4254 4255 /** 4256 * Creates a map maximum swiped values for a given swipe direction 4257 * @param {string} The direction that these values will be associated with 4258 * @return Object Maximum values 4259 * @inner 4260 */ 4261 function createMaximumVO(dir) { 4262 return { 4263 direction: dir, 4264 distance: 0 4265 } 4266 } 4267 4268 4269 // 4270 // MATHS / UTILS 4271 // 4272 4273 /** 4274 * Calculate the duration of the swipe 4275 * @return int 4276 * @inner 4277 */ 4278 function calculateDuration() { 4279 return endTime - startTime; 4280 } 4281 4282 /** 4283 * Calculate the distance between 2 touches (pinch) 4284 * @param {point} startPoint A point object containing x and y co-ordinates 4285 * @param {point} endPoint A point object containing x and y co-ordinates 4286 * @return int; 4287 * @inner 4288 */ 4289 function calculateTouchesDistance(startPoint, endPoint) { 4290 var diffX = Math.abs(startPoint.x - endPoint.x); 4291 var diffY = Math.abs(startPoint.y - endPoint.y); 4292 4293 return Math.round(Math.sqrt(diffX * diffX + diffY * diffY)); 4294 } 4295 4296 /** 4297 * Calculate the zoom factor between the start and end distances 4298 * @param {int} startDistance Distance (between 2 fingers) the user started pinching at 4299 * @param {int} endDistance Distance (between 2 fingers) the user ended pinching at 4300 * @return float The zoom value from 0 to 1. 4301 * @inner 4302 */ 4303 function calculatePinchZoom(startDistance, endDistance) { 4304 var percent = (endDistance / startDistance) * 1; 4305 return percent.toFixed(2); 4306 } 4307 4308 4309 /** 4310 * Returns the pinch direction, either IN or OUT for the given points 4311 * @return string Either {@link $.fn.swipe.directions.IN} or {@link $.fn.swipe.directions.OUT} 4312 * @see $.fn.swipe.directions 4313 * @inner 4314 */ 4315 function calculatePinchDirection() { 4316 if (pinchZoom < 1) { 4317 return OUT; 4318 } else { 4319 return IN; 4320 } 4321 } 4322 4323 4324 /** 4325 * Calculate the length / distance of the swipe 4326 * @param {point} startPoint A point object containing x and y co-ordinates 4327 * @param {point} endPoint A point object containing x and y co-ordinates 4328 * @return int 4329 * @inner 4330 */ 4331 function calculateDistance(startPoint, endPoint) { 4332 return Math.round(Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2))); 4333 } 4334 4335 /** 4336 * Calculate the angle of the swipe 4337 * @param {point} startPoint A point object containing x and y co-ordinates 4338 * @param {point} endPoint A point object containing x and y co-ordinates 4339 * @return int 4340 * @inner 4341 */ 4342 function calculateAngle(startPoint, endPoint) { 4343 var x = startPoint.x - endPoint.x; 4344 var y = endPoint.y - startPoint.y; 4345 var r = Math.atan2(y, x); //radians 4346 var angle = Math.round(r * 180 / Math.PI); //degrees 4347 4348 //ensure value is positive 4349 if (angle < 0) { 4350 angle = 360 - Math.abs(angle); 4351 } 4352 4353 return angle; 4354 } 4355 4356 /** 4357 * Calculate the direction of the swipe 4358 * This will also call calculateAngle to get the latest angle of swipe 4359 * @param {point} startPoint A point object containing x and y co-ordinates 4360 * @param {point} endPoint A point object containing x and y co-ordinates 4361 * @return string Either {@link $.fn.swipe.directions.LEFT} / {@link $.fn.swipe.directions.RIGHT} / {@link $.fn.swipe.directions.DOWN} / {@link $.fn.swipe.directions.UP} 4362 * @see $.fn.swipe.directions 4363 * @inner 4364 */ 4365 function calculateDirection(startPoint, endPoint) { 4366 4367 if( comparePoints(startPoint, endPoint) ) { 4368 return NONE; 4369 } 4370 4371 var angle = calculateAngle(startPoint, endPoint); 4372 4373 if ((angle <= 45) && (angle >= 0)) { 4374 return LEFT; 4375 } else if ((angle <= 360) && (angle >= 315)) { 4376 return LEFT; 4377 } else if ((angle >= 135) && (angle <= 225)) { 4378 return RIGHT; 4379 } else if ((angle > 45) && (angle < 135)) { 4380 return DOWN; 4381 } else { 4382 return UP; 4383 } 4384 } 4385 4386 4387 /** 4388 * Returns a MS time stamp of the current time 4389 * @return int 4390 * @inner 4391 */ 4392 function getTimeStamp() { 4393 var now = new Date(); 4394 return now.getTime(); 4395 } 4396 4397 4398 4399 /** 4400 * Returns a bounds object with left, right, top and bottom properties for the element specified. 4401 * @param {DomNode} The DOM node to get the bounds for. 4402 */ 4403 function getbounds(el) { 4404 el = $(el); 4405 var offset = el.offset(); 4406 4407 var bounds = { 4408 left: offset.left, 4409 right: offset.left + el.outerWidth(), 4410 top: offset.top, 4411 bottom: offset.top + el.outerHeight() 4412 } 4413 4414 return bounds; 4415 } 4416 4417 4418 /** 4419 * Checks if the point object is in the bounds object. 4420 * @param {object} point A point object. 4421 * @param {int} point.x The x value of the point. 4422 * @param {int} point.y The x value of the point. 4423 * @param {object} bounds The bounds object to test 4424 * @param {int}
4424 bounds.left The leftmost value 4425 * @param {int} bounds.right The righttmost value 4426 * @param {int} bounds.top The topmost value 4427 * @param {int} bounds.bottom The bottommost value 4428 */ 4429 function isInBounds(point, bounds) { 4430 return (point.x > bounds.left && point.x < bounds.right && point.y > bounds.top && point.y < bounds.bottom); 4431 }; 4432 4433 /** 4434 * Checks if the two points are equal 4435 * @param {object} point A point object. 4436 * @param {object} point B point object. 4437 * @return true of the points match 4438 */ 4439 function comparePoints(pointA, pointB) { 4440 return (pointA.x == pointB.x && pointA.y == pointB.y); 4441 } 4442 4443 4444 } 4445 4446 4447 4448 4449 /** 4450 * A catch all handler that is triggered for all swipe directions. 4451 * @name $.fn.swipe#swipe 4452 * @event 4453 * @default null 4454 * @param {EventObject} event The original event object 4455 * @param {int} direction The direction the user swiped in. See {@link $.fn.swipe.directions} 4456 * @param {int} distance The distance the user swiped 4457 * @param {int} duration The duration of the swipe in milliseconds 4458 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4459 * @param {object} fingerData The coordinates of fingers in event 4460 * @param {string} currentDirection The current direction the user is swiping. 4461 */ 4462 4463 4464 4465 4466 /** 4467 * A handler that is triggered for "left" swipes. 4468 * @name $.fn.swipe#swipeLeft 4469 * @event 4470 * @default null 4471 * @param {EventObject} event The original event object 4472 * @param {int} direction The direction the user swiped in. See {@link $.fn.swipe.directions} 4473 * @param {int} distance The distance the user swiped 4474 * @param {int} duration The duration of the swipe in milliseconds 4475 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4476 * @param {object} fingerData The coordinates of fingers in event 4477 * @param {string} currentDirection The current direction the user is swiping. 4478 */ 4479 4480 /** 4481 * A handler that is triggered for "right" swipes. 4482 * @name $.fn.swipe#swipeRight 4483 * @event 4484 * @default null 4485 * @param {EventObject} event The original event object 4486 * @param {int} direction The direction the user swiped in. See {@link $.fn.swipe.directions} 4487 * @param {int} distance The distance the user swiped 4488 * @param {int} duration The duration of the swipe in milliseconds 4489 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4490 * @param {object} fingerData The coordinates of fingers in event 4491 * @param {string} currentDirection The current direction the user is swiping. 4492 */ 4493 4494 /** 4495 * A handler that is triggered for "up" swipes. 4496 * @name $.fn.swipe#swipeUp 4497 * @event 4498 * @default null 4499 * @param {EventObject} event The original event object 4500 * @param {int} direction The direction the user swiped in. See {@link $.fn.swipe.directions} 4501 * @param {int} distance The distance the user swiped 4502 * @param {int} duration The duration of the swipe in milliseconds 4503 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4504 * @param {object} fingerData The coordinates of fingers in event 4505 * @param {string} currentDirection The current direction the user is swiping. 4506 */ 4507 4508 /** 4509 * A handler that is triggered for "down" swipes. 4510 * @name $.fn.swipe#swipeDown 4511 * @event 4512 * @default null 4513 * @param {EventObject} event The original event object 4514 * @param {int} direction The direction the user swiped in. See {@link $.fn.swipe.directions} 4515 * @param {int} distance The distance the user swiped 4516 * @param {int} duration The duration of the swipe in milliseconds 4517 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4518 * @param {object} fingerData The coordinates of fingers in event 4519 * @param {string} currentDirection The current direction the user is swiping. 4520 */ 4521 4522 /** 4523 * A handler triggered for every phase of the swipe. This handler is constantly f
4523ired for the duration of the pinch. 4524 * This is triggered regardless of swipe thresholds. 4525 * @name $.fn.swipe#swipeStatus 4526 * @event 4527 * @default null 4528 * @param {EventObject} event The original event object 4529 * @param {string} phase The phase of the swipe event. See {@link $.fn.swipe.phases} 4530 * @param {string} direction The direction the user swiped in. This is null if the user has yet to move. See {@link $.fn.swipe.directions} 4531 * @param {int} distance The distance the user swiped. This is 0 if the user has yet to move. 4532 * @param {int} duration The duration of the swipe in milliseconds 4533 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4534 * @param {object} fingerData The coordinates of fingers in event 4535 * @param {string} currentDirection The current direction the user is swiping. 4536 */ 4537 4538 /** 4539 * A handler triggered for pinch in events. 4540 * @name $.fn.swipe#pinchIn 4541 * @event 4542 * @default null 4543 * @param {EventObject} event The original event object 4544 * @param {int} direction The direction the user pinched in. See {@link $.fn.swipe.directions} 4545 * @param {int} distance The distance the user pinched 4546 * @param {int} duration The duration of the swipe in milliseconds 4547 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4548 * @param {int} zoom The zoom/scale level the user pinched too, 0-1. 4549 * @param {object} fingerData The coordinates of fingers in event 4550 */ 4551 4552 /** 4553 * A handler triggered for pinch out events. 4554 * @name $.fn.swipe#pinchOut 4555 * @event 4556 * @default null 4557 * @param {EventObject} event The original event object 4558 * @param {int} direction The direction the user pinched in. See {@link $.fn.swipe.directions} 4559 * @param {int} distance The distance the user pinched 4560 * @param {int} duration The duration of the swipe in milliseconds 4561 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4562 * @param {int} zoom The zoom/scale level the user pinched too, 0-1. 4563 * @param {object} fingerData The coordinates of fingers in event 4564 */ 4565 4566 /** 4567 * A handler triggered for all pinch events. This handler is constantly fired for the duration of the pinch. This is triggered regardless of thresholds. 4568 * @name $.fn.swipe#pinchStatus 4569 * @event 4570 * @default null 4571 * @param {EventObject} event The original event object 4572 * @param {int} direction The direction the user pinched in. See {@link $.fn.swipe.directions} 4573 * @param {int} distance The distance the user pinched 4574 * @param {int} duration The duration of the swipe in milliseconds 4575 * @param {int} fingerCount The number of fingers used. See {@link $.fn.swipe.fingers} 4576 * @param {int} zoom The zoom/scale level the user pinched too, 0-1. 4577 * @param {object} fingerData The coordinates of fingers in event 4578 */ 4579 4580 /** 4581 * A click handler triggered when a user simply clicks, rather than swipes on an element. 4582 * This is deprecated since version 1.6.2, any assignment to click will be assigned to the tap handler. 4583 * You cannot use <code>on</code> to bind to this event as the default jQ <code>click</code> event will be triggered. 4584 * Use the <code>tap</code> event instead. 4585 * @name $.fn.swipe#click 4586 * @event 4587 * @deprecated since version 1.6.2, please use {@link $.fn.swipe#tap} instead 4588 * @default null 4589 * @param {EventObject} event The original event object 4590 * @param {DomObject} target The element clicked on. 4591 */ 4592 4593 /** 4594 * A click / tap handler triggered when a user simply clicks or taps, rather than swipes on an element. 4595 * @name $.fn.swipe#tap 4596 * @event 4597 * @default null 4598 * @param {EventObject} event The original event object 4599 * @param {DomObject} target The element clicked on. 4600 */ 4601 4602 /** 4603 * A double tap handler triggered when a user double clicks or taps on an element. 4604 * You can set the time delay for a double tap with the {@link $.fn.swipe.defaults#doubleTapThreshold} property. 4605 * Note: If you set both <code>doubleTap</code> and <code>tap</code> handlers, the <code>tap</code> event will be delayed by the <code>doubleTapThreshold</code> 4606 * as the script needs to check if its a double tap. 4607 * @name $.fn.swipe#doubleTap 4608 * @see $.fn.swipe.defaults#doubleTapThreshold 4609 * @event 4610 * @default null 4611 * @param {EventObject} event The original event object 4612 * @param {DomObject} target The element clicked on. 4613 */ 4614 4615 /** 4616 * A long tap handler triggered once a tap has been release if the tap was longer than the longTapThreshold. 4617 * You can set the time delay for a long tap with the {@link $.fn.swipe.defaults#longTapThreshold} property. 4618 * @name $.fn.swipe#longTap 4619 * @see $.fn.swipe.defaults#longTapThreshold 4620 * @event 4621 * @default null 4622 * @param {EventObject} event The original event object 4623 * @param {DomObject} target The element clicked on. 4624 */ 4625 4626 /** 4627 * A hold tap handler triggered as soon as the longTapThreshold is reached 4628 * You can set the time delay for a long tap with the {@link $.fn.swipe.defaults#longTapThreshold} property. 4629 * @name $.fn.swipe#hold 4630 * @see $.fn.swipe.defaults#longTapThreshold 4631 * @event 4632 * @default null 4633 * @param {EventObject} event The original event object 4634 * @param {DomObject} target The element clicked on. 4635 */ 4636 4637})); 4638 4639/*! 4640* Parsley.js 4641* Version 2.9.2 - built Tue, Dec 10th 2019, 6:18 pm 4642* http://parsleyjs.org 4643* Guillaume Potier - <[email protected]> 4644* Marc-Andre Lafortune - <[email protected]> 4645* MIT Licensed 4646*/ 4647 4648// The source code below is generated by babel as 4649// Parsley is written in ECMAScript 6 4650// 4651 4652(function (global, factory) { 4653 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) : 4654 typeof define === 'function' && define.amd ? define(['jquery'], factory) : 4655 (global = global || self, global.parsley = factory(global.jQuery)); 4656}(this, (function ($) { 'use strict'; 4657 4658 function _typeof(obj) { 4659 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 4660 _typeof = function (obj) { 4661 return typeof obj; 4662 }; 4663 } else { 4664 _typeof = function (obj) { 4665 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 4666 }; 4667 } 4668 4669 return _typeof(obj); 4670 } 4671 4672 function _extends() { 4673 _extends = Object.assign || function (target) { 4674 for (var i = 1; i < arguments.length; i++) { 4675 var source = arguments[i]; 4676 4677 for (var key in source) { 4678 if (Object.prototype.hasOwnProperty.call(source, key)) { 4679 target[key] = source[key]; 4680 } 4681 } 4682 } 4683 4684 return target; 4685 }; 4686 4687 return _extends.apply(this, arguments); 4688 } 4689 4690 function _slicedToArray(arr, i) { 4691 return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); 4692 } 4693 4694 function _toConsumableArray(arr) { 4695 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 4696 } 4697 4698 function _arrayWithoutHoles(arr) { 4699 if (Array.isArray(arr)) { 4700 for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 4701 4702 return arr2; 4703 } 4704 } 4705 4706 function _arrayWithHoles(arr) { 4707 if (Array.isArray(arr)) return arr; 4708 } 4709 4710 function _iterableToArray(iter) { 4711 if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 4712 } 4713 4714 function _iterableToArrayLimit(arr, i) { 4715 if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { 4716 return; 4717 } 4718 4719 var _arr = []; 4720 var _n = true; 4721 var _d = false; 4722 var _e = undefined; 4723 4724 try { 4725 for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { 4726 _arr.push(_s.value); 4727 4728 if (i && _arr.length === i) break; 4729 } 4730 } catch (err) { 4731 _d = true; 4732 _e = err; 4733 } finally {
vendor: 94,581 bytes, lines 4734-7101
4734 try { 4735 if (!_n && _i["return"] != null) _i["return"](); 4736 } finally { 4737 if (_d) throw _e; 4738 } 4739 } 4740 4741 return _arr; 4742 } 4743 4744 function _nonIterableSpread() { 4745 throw new TypeError("Invalid attempt to spread non-iterable instance"); 4746 } 4747 4748 function _nonIterableRest() { 4749 throw new TypeError("Invalid attempt to destructure non-iterable instance"); 4750 } 4751 4752 var globalID = 1; 4753 var pastWarnings = {}; 4754 var Utils = { 4755 // Parsley DOM-API 4756 // returns object from dom attributes and values 4757 attr: function attr(element, namespace, obj) { 4758 var i; 4759 var attribute; 4760 var attributes; 4761 var regex = new RegExp('^' + namespace, 'i'); 4762 if ('undefined' === typeof obj) obj = {};else { 4763 // Clear all own properties. This won't affect prototype's values 4764 for (i in obj) { 4765 if (obj.hasOwnProperty(i)) delete obj[i]; 4766 } 4767 } 4768 if (!element) return obj; 4769 attributes = element.attributes; 4770 4771 for (i = attributes.length; i--;) { 4772 attribute = attributes[i]; 4773 4774 if (attribute && attribute.specified && regex.test(attribute.name)) { 4775 obj[this.camelize(attribute.name.slice(namespace.length))] = this.deserializeValue(attribute.value); 4776 } 4777 } 4778 4779 return obj; 4780 }, 4781 checkAttr: function checkAttr(element, namespace, _checkAttr) { 4782 return element.hasAttribute(namespace + _checkAttr); 4783 }, 4784 setAttr: function setAttr(element, namespace, attr, value) { 4785 element.setAttribute(this.dasherize(namespace + attr), String(value)); 4786 }, 4787 getType: function getType(element) { 4788 return element.getAttribute('type') || 'text'; 4789 }, 4790 generateID: function generateID() { 4791 return '' + globalID++; 4792 }, 4793 4794 /** Third party functions **/ 4795 deserializeValue: function deserializeValue(value) { 4796 var num; 4797 4798 try { 4799 return value ? value == "true" || (value == "false" ? false : value == "null" ? null : !isNaN(num = Number(value)) ? num : /^[\[\{]/.test(value) ? JSON.parse(value) : value) : value; 4800 } catch (e) { 4801 return value; 4802 } 4803 }, 4804 // Zepto camelize function 4805 camelize: function camelize(str) { 4806 return str.replace(/-+(.)?/g, function (match, chr) { 4807 return chr ? chr.toUpperCase() : ''; 4808 }); 4809 }, 4810 // Zepto dasherize function 4811 dasherize: function dasherize(str) { 4812 return str.replace(/::/g, '/').replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2').replace(/([a-z\d])([A-Z])/g, '$1_$2').replace(/_/g, '-').toLowerCase(); 4813 }, 4814 warn: function warn() { 4815 var _window$console; 4816 4817 if (window.console && 'function' === typeof window.console.warn) (_window$console = window.console).warn.apply(_window$console, arguments); 4818 }, 4819 warnOnce: function warnOnce(msg) { 4820 if (!pastWarnings[msg]) { 4821 pastWarnings[msg] = true; 4822 this.warn.apply(this, arguments); 4823 } 4824 }, 4825 _resetWarnings: function _resetWarnings() { 4826 pastWarnings = {}; 4827 }, 4828 trimString: function trimString(string) { 4829 return string.replace(/^\s+|\s+$/g, ''); 4830 }, 4831 parse: { 4832 date: function date(string) { 4833 var parsed = string.match(/^(\d{4,})-(\d\d)-(\d\d)$/); 4834 if (!parsed) return null; 4835 4836 var _parsed$map = parsed.map(function (x) { 4837 return parseInt(x, 10); 4838 }), 4839 _parsed$map2 = _slicedToArray(_parsed$map, 4), 4840 _ = _parsed$map2[0], 4841 year = _parsed$map2[1], 4842 month = _parsed$map2[2], 4843 day = _parsed$map2[3]; 4844 4845 var date = new Date(year, month - 1, day); 4846 if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) return null; 4847 return date; 4848 }, 4849 string: function string(_string) { 4850 return _string; 4851 }, 4852 integer: function integer(string) { 4853 if (isNaN(string)) return null; 4854 return parseInt(string, 10); 4855 }, 4856 number: function number(string) { 4857 if (isNaN(string)) throw null; 4858 return parseFloat(string); 4859 }, 4860 'boolean': function _boolean(string) { 4861 return !/^\s*false\s*$/i.test(string); 4862 }, 4863 object: function object(string) { 4864 return Utils.deserializeValue(string); 4865 }, 4866 regexp: function regexp(_regexp) { 4867 var flags = ''; // Test if RegExp is literal, if not, nothing to be done, otherwise, we need to isolate flags and pattern 4868 4869 if (/^\/.*\/(?:[gimy]*)$/.test(_regexp)) { 4870 // Replace the regexp literal string with the first match group: ([gimy]*) 4871 // If no flag is present, this will be a blank string 4872 flags = _regexp.replace(/.*\/([gimy]*)$/, '$1'); // Again, replace the regexp literal string with the first match group: 4873 // everything excluding the opening and closing slashes and the flags 4874 4875 _regexp = _regexp.replace(new RegExp('^/(.*?)/' + flags + '$'), '$1'); 4876 } else { 4877 // Anchor regexp: 4878 _regexp = '^' + _regexp + '$'; 4879 } 4880 4881 return new RegExp(_regexp, flags); 4882 } 4883 }, 4884 parseRequirement: function parseRequirement(requirementType, string) { 4885 var converter = this.parse[requirementType || 'string']; 4886 if (!converter) throw 'Unknown requirement specification: "' + requirementType + '"'; 4887 var converted = converter(string); 4888 if (converted === null) throw "Requirement is not a ".concat(requirementType, ": \"").concat(string, "\""); 4889 return converted; 4890 }, 4891 namespaceEvents: function namespaceEvents(events, namespace) { 4892 events = this.trimString(events || '').split(/\s+/); 4893 if (!events[0]) return ''; 4894 return $.map(events, function (evt) { 4895 return "".concat(evt, ".").concat(namespace); 4896 }).join(' '); 4897 }, 4898 difference: function difference(array, remove) { 4899 // This is O(N^2), should be optimized 4900 var result = []; 4901 $.each(array, function (_, elem) { 4902 if (remove.indexOf(elem) == -1) result.push(elem); 4903 }); 4904 return result; 4905 }, 4906 // Alter-ego to native Promise.all, but for jQuery 4907 all: function all(promises) { 4908 // jQuery treats $.when() and $.when(singlePromise) differently; let's avoid that and add spurious elements 4909 return $.when.apply($, _toConsumableArray(promises).concat([42, 42])); 4910 }, 4911 // Object.create polyfill, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill 4912 objectCreate: Object.create || function () { 4913 var Object = function Object() {}; 4914 4915 return function (prototype) { 4916 if (arguments.length > 1) { 4917 throw Error('Second argument not supported'); 4918 } 4919 4920 if (_typeof(prototype) != 'object') { 4921 throw TypeError('Argument must be an object'); 4922 } 4923 4924 Object.prototype = prototype; 4925 var result = new Object(); 4926 Object.prototype = null; 4927 return result; 4928 }; 4929 }(), 4930 _SubmitSelector: 'input[type="submit"], button:submit' 4931 }; 4932 4933 // All these options could be overriden and specified directly in DOM using 4934 // `data-parsley-` default DOM-API 4935 // eg: `inputs` can be set in DOM using `data-parsley-inputs="input, textarea"` 4936 // eg: `data-parsley-stop-on-first-failing-constraint="false"` 4937 var Defaults = { 4938 // ### General 4939 // Default data-namespace for DOM API 4940 namespace: 'data-parsley-', 4941 // Supported inputs by default 4942 inputs: 'input, textarea, select', 4943 // Excluded inputs by default 4944 excluded: 'input[type=button], input[type=submit], input[type=reset], input[type=hidden]', 4945 // Stop validating field on highest priority failing constraint 4946 priorityEnabled: true, 4947 // ### Field only 4948 // identifier used to group together inputs (e.g. radio buttons...) 4949 multiple: null, 4950 // identifier (or array of identifiers) used to validate only a select group of inputs 4951 group: null, 4952 // ### UI 4953 // Enable\Disable error messages 4954 uiEnabled: true, 4955 // Key events threshold before validation 4956 validationThreshold: 3, 4957 // Focused field on form validation error. 'first'|'last'|'none' 4958 focus: 'first', 4959 // event(s) that will trigger validation before first failure. eg: `input`... 4960 trigger: false, 4961 // event(s) that will trigger validation after first failure. 4962 triggerAfterFailure: 'input', 4963 // Class that would be added on every failing validation Parsley field 4964 errorClass: 'parsley-error', 4965 // Same for success validation 4966 successClass: 'parsley-success', 4967 // Return the `$element` that will receive these above success or error classes 4968 // Could also be (and given directly from DOM) a valid selector like `'#div'` 4969 classHandler: function classHandler(Field) {}, 4970 // Return the `$element` where errors will be appended 4971 // Could also be (and given directly from DOM) a valid selector like `'#div'` 4972 errorsContainer: function errorsContainer(Field) {}, 4973 // ul elem that would receive errors' list 4974 errorsWrapper: '<ul class="parsley-errors-list"></ul>', 4975 // li elem that would receive error message 4976 errorTemplate: '<li></li>' 4977 }; 4978 4979 var Base = function Base() { 4980 this.__id__ = Utils.generateID(); 4981 }; 4982 4983 Base.prototype = { 4984 asyncSupport: true, 4985 // Deprecated 4986 _pipeAccordingToValidationResult: function _pipeAccordingToValidationResult() { 4987 var _this = this; 4988 4989 var pipe = function pipe() { 4990 var r = $.Deferred(); 4991 if (true !== _this.validationResult) r.reject(); 4992 return r.resolve().promise(); 4993 }; 4994 4995 return [pipe, pipe]; 4996 }, 4997 actualizeOptions: function actualizeOptions() { 4998 Utils.attr(this.element, this.options.namespace, this.domOptions); 4999 if (this.parent && this.parent.actualizeOptions) this.parent.actualizeOptions(); 5000 return this; 5001 }, 5002 _resetOptions: function _resetOptions(initOptions) { 5003 this.domOptions = Utils.objectCreate(this.parent.options); 5004 this.options = Utils.objectCreate(this.domOptions); // Shallow copy of ownProperties of initOptions: 5005 5006 for (var i in initOptions) { 5007 if (initOptions.hasOwnProperty(i)) this.options[i] = initOptions[i]; 5008 } 5009 5010 this.actualizeOptions(); 5011 }, 5012 _listeners: null, 5013 // Register a callback for the given event name 5014 // Callback is called with context as the first argument and the `this` 5015 // The context is the current parsley instance, or window.Parsley if global 5016 // A return value of `false` will interrupt the calls 5017 on: function on(name, fn) { 5018 this._listeners = this._listeners || {}; 5019 var queue = this._listeners[name] = this._listeners[name] || []; 5020 queue.push(fn); 5021 return this; 5022 }, 5023 // Deprecated. Use `on` instead 5024 subscribe: function subscribe(name, fn) { 5025 $.listenTo(this, name.toLowerCase(), fn); 5026 }, 5027 // Unregister a callback (or all if none is given) for the given event name 5028 off: function off(name, fn) { 5029 var queue = this._listeners && this._listeners[name]; 5030 5031 if (queue) { 5032 if (!fn) { 5033 delete this._listeners[name]; 5034 } else { 5035 for (var i = queue.length; i--;) { 5036 if (queue[i] === fn) queue.splice(i, 1); 5037 } 5038 } 5039 } 5040 5041 return this; 5042 }, 5043 // Deprecated. Use `off` 5044 unsubscribe: function unsubscribe(name, fn) { 5045 $.unsubscribeTo(this, name.toLowerCase()); 5046 }, 5047 // Trigger an event of the given name 5048 // A return value of `false` interrupts the callback chain 5049 // Returns false if execution was interrupted 5050 trigger: function trigger(name, target, extraArg) { 5051 target = target || this; 5052 var queue = this._listeners && this._listeners[name]; 5053 var result; 5054 5055 if (queue) { 5056 for (var i = queue.length; i--;) { 5057 result = queue[i].call(target, target, extraArg); 5058 if (result === false) return result; 5059 } 5060 } 5061 5062 if (this.parent) { 5063 return this.parent.trigger(name, target, extraArg); 5064 } 5065 5066 return true; 5067 }, 5068 asyncIsValid: function asyncIsValid(group, force) { 5069 Utils.warnOnce("asyncIsValid is deprecated; please use whenValid instead"); 5070 return this.whenValid({ 5071 group: group, 5072 force: force 5073 }); 5074 }, 5075 _findRelated: function _findRelated() { 5076 return this.options.multiple ? $(this.parent.element.querySelectorAll("[".concat(this.options.namespace, "multiple=\"").concat(this.options.multiple, "\"]"))) : this.$element; 5077 } 5078 }; 5079 5080 var convertArrayRequirement = function convertArrayRequirement(string, length) { 5081 var m = string.match(/^\s*\[(.*)\]\s*$/); 5082 if (!m) throw 'Requirement is not an array: "' + string + '"'; 5083 var values = m[1].split(',').map(Utils.trimString); 5084 if (values.length !== length) throw 'Requirement has ' + values.length + ' values when ' + length + ' are needed'; 5085 return values; 5086 }; 5087 5088 var convertExtraOptionRequirement = function convertExtraOptionRequirement(requirementSpec, string, extraOptionReader) { 5089 var main = null; 5090 var extra = {}; 5091 5092 for (var key in requirementSpec) { 5093 if (key) { 5094 var value = extraOptionReader(key); 5095 if ('string' === typeof value) value = Utils.parseRequirement(requirementSpec[key], value); 5096 extra[key] = value; 5097 } else { 5098 main = Utils.parseRequirement(requirementSpec[key], string); 5099 } 5100 } 5101 5102 return [main, extra]; 5103 }; // A Validator needs to implement the methods `validate` and `parseRequirements` 5104 5105 5106 var Validator = function Validator(spec) { 5107 $.extend(true, this, spec); 5108 }; 5109 5110 Validator.prototype = { 5111 // Returns `true` iff the given `value` is valid according the given requirements. 5112 validate: function validate(value, requirementFirstArg) { 5113 if (this.fn) { 5114 // Legacy style validator 5115 if (arguments.length > 3) // If more args then value, requirement, instance... 5116 requirementFirstArg = [].slice.call(arguments, 1, -1); // Skip first arg (value) and last (instance), combining the rest 5117 5118 return this.fn(value, requirementFirstArg); 5119 } 5120 5121 if (Array.isArray(value)) { 5122 if (!this.validateMultiple) throw 'Validator `' + this.name + '` does not handle multiple values'; 5123 return this.validateMultiple.apply(this, arguments); 5124 } else { 5125 var instance = arguments[arguments.length - 1]; 5126 5127 if (this.validateDate && instance._isDateInput()) { 5128 arguments[0] = Utils.parse.date(arguments[0]); 5129 if (arguments[0] === null) return false; 5130 return this.validateDate.apply(this, arguments); 5131 } 5132 5133 if (this.validateNumber) { 5134 if (!value) // Builtin validators all accept empty strings, except `required` of course 5135 return true; 5136 if (isNaN(value)) return false; 5137 arguments[0] = parseFloat(arguments[0]); 5138 return this.validateNumber.apply(this, arguments); 5139 } 5140 5141 if (this.validateString) { 5142 return this.validateString.apply(this, arguments); 5143 } 5144 5145 throw 'Validator `' + this.name + '` only handles multiple values'; 5146 } 5147 }, 5148 // Parses `requirements` into an array of arguments, 5149 // according to `this.requirementType` 5150 parseRequirements: function parseRequirements(requirements, extraOptionReader) { 5151 if ('string' !== typeof requirements) { 5152 // Assume requirement already parsed 5153 // but make sure we return an array 5154 return Array.isArray(requirements) ? requirements : [requirements]; 5155 } 5156 5157 var type = this.requirementType; 5158 5159 if (Array.isArray(type)) { 5160 var values = convertArrayRequirement(requirements, type.length); 5161 5162 for (var i = 0; i < values.length; i++) { 5163 values[i] = Utils.parseRequirement(type[i], values[i]); 5164 } 5165 5166 return values; 5167 } else if ($.isPlainObject(type)) { 5168 return convertExtraOptionRequirement(type, requirements, extraOptionReader); 5169 } else { 5170 return [Utils.parseRequirement(type, requirements)]; 5171 } 5172 }, 5173 // Defaults: 5174 requirementType: 'string', 5175 priority: 2 5176 }; 5177 5178 var ValidatorRegistry = function ValidatorRegistry(validators, catalog) { 5179 this.__class__ = 'ValidatorRegistry'; // Default Parsley locale is en 5180 5181 this.locale = 'en'; 5182 this.init(validators || {}, catalog || {}); 5183 }; 5184 5185 var typeTesters = { 5186 email: /^((([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-zA-Z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-zA-Z]|\d|-|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-zA-Z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))$/, 5187 // Follow https://www.w3.org/TR/html5/infrastructure.html#floating-point-numbers 5188 number: /^-?(\d*\.)?\d+(e[-+]?\d+)?$/i, 5189 integer: /^-?\d+$/, 5190 digits: /^\d+$/, 5191 alphanum: /^\w+$/i, 5192 date: { 5193 test: function test(value) { 5194 return Utils.parse.date(value) !== null; 5195 } 5196 }, 5197 url: new RegExp("^" + // protocol identifier 5198 "(?:(?:https?|ftp)://)?" + // ** mod: make scheme optional 5199 // user:pass authentication 5200 "(?:\\S+(?::\\S*)?@)?" + "(?:" + // IP address exclusion 5201 // private & local networks 5202 // "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + // ** mod: allow local networks 5203 // "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + // ** mod: allow local networks 5204 // "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" + // ** mod: allow local networks 5205 // IP address dotted notation octets 5206 // excludes loopback network 0.0.0.0 5207 // excludes reserved space >= 224.0.0.0 5208 // excludes network & broacast addresses 5209 // (first & last IP address of each class) 5210 "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + "|" + // host name 5211 "(?:(?:[a-zA-Z\\u00a1-\\uffff0-9]-*)*[a-zA-Z\\u00a1-\\uffff0-9]+)" + // domain name 5212 "(?:\\.(?:[a-zA-Z\\u00a1-\\uffff0-9]-*)*[a-zA-Z\\u00a1-\\uffff0-9]+)*" + // TLD identifier 5213 "(?:\\.(?:[a-zA-Z\\u00a1-\\uffff]{2,}))" + ")" + // port number 5214 "(?::\\d{2,5})?" + // resource path 5215 "(?:/\\S*)?" + "$") 5216 }; 5217 typeTesters.range = typeTesters.number; // See http://stackoverflow.com/a/10454560/8279 5218 5219 var decimalPlaces = function decimalPlaces(num) { 5220 var match = ('' + num).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/); 5221 5222 if (!match) { 5223 return 0; 5224 } 5225 5226 return Math.max(0, // Number of digits right of decimal point. 5227 (match[1] ? match[1].length : 0) - ( // Adjust for scientific notation. 5228 match[2] ? +match[2] : 0)); 5229 }; // parseArguments('number', ['1', '2']) => [1, 2] 5230 5231 5232 var parseArguments = function parseArguments(type, args) { 5233 return args.map(Utils.parse[type]); 5234 }; // operatorToValidator returns a validating function for an operator function, applied to the given type 5235 5236 5237 var operatorToValidator = function operatorToValidator(type, operator) { 5238 return function (value) { 5239 for (var _len = arguments.length, requirementsAndInput = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 5240 requirementsAndInput[_key - 1] = arguments[_key]; 5241 } 5242 5243 requirementsAndInput.pop(); // Get rid of `input` argument 5244 5245 return operator.apply(void 0, [value].concat(_toConsumableArray(parseArguments(type, requirementsAndInput)))); 5246 }; 5247 }; 5248 5249 var comparisonOperator = function comparisonOperator(operator) { 5250 return { 5251 validateDate: operatorToValidator('date', operator), 5252 validateNumber: operatorToValidator('number', operator), 5253 requirementType: operator.length <= 2 ? 'string' : ['string', 'string'], 5254 // Support operators with a 1 or 2 requirement(s) 5255 priority: 30 5256 }; 5257 }; 5258 5259 ValidatorRegistry.prototype = { 5260 init: function init(validators, catalog) { 5261 this.catalog = catalog; // Copy prototype's validators: 5262 5263 this.validators = _extends({}, this.validators); 5264 5265 for (var name in validators) { 5266 this.addValidator(name, validators[name].fn, validators[name].priority); 5267 } 5268 5269 window.Parsley.trigger('parsley:validator:init'); 5270 }, 5271 // Set new messages locale if we have dictionary loaded in ParsleyConfig.i18n 5272 setLocale: function setLocale(locale) { 5273 if ('undefined' === typeof this.catalog[locale]) throw new Error(locale + ' is not available in the catalog'); 5274 this.locale = locale; 5275 return this; 5276 }, 5277 // Add a new messages catalog for a given locale. Set locale for this catalog if set === `true` 5278 addCatalog: function addCatalog(locale, messages, set) { 5279 if ('object' === _typeof(messages)) this.catalog[locale] = messages; 5280 if (true === set) return this.setLocale(locale); 5281 return this; 5282 }, 5283 // Add a specific message for a given constraint in a given locale 5284 addMessage: function addMessage(locale, name, message) { 5285 if ('undefined' === typeof this.catalog[locale]) this.catalog[locale] = {}; 5286 this.catalog[locale][name] = message; 5287 return this; 5288 }, 5289 // Add messages for a given locale 5290 addMessages: function addMessages(locale, nameMessageObject) { 5291 for (var name in nameMessageObject) { 5292 this.addMessage(locale, name, nameMessageObject[name]); 5293 } 5294 5295 return this; 5296 }, 5297 // Add a new validator 5298 // 5299 // addValidator('custom', { 5300 // requirementType: ['integer', 'integer'], 5301 // validateString: function(value, from, to) {}, 5302 // priority: 22, 5303 // messages: { 5304 // en: "Hey, that's no good", 5305 // fr: "Aye aye, pas bon du tout", 5306 // } 5307 // }) 5308 // 5309 // Old API was addValidator(name, function, priority) 5310 // 5311 addValidator: function addValidator(name, arg1, arg2) { 5312 if (this.validators[name]) Utils.warn('Validator "' + name + '" is already defined.');else if (Defaults.hasOwnProperty(name)) { 5313 Utils.warn('"' + name + '" is a restricted keyword and is not a valid validator name.'); 5314 return; 5315 } 5316 return this._setValidator.apply(this, arguments); 5317 }, 5318 hasValidator: function hasValidator(name) { 5319 return !!this.validators[name]; 5320 }, 5321 updateValidator: function updateValidator(name, arg1, arg2) { 5322 if (!this.validators[name]) { 5323 Utils.warn('Validator "' + name + '" is not already defined.'); 5324 return this.addValidator.apply(this, arguments); 5325 } 5326 5327 return this._setValidator.apply(this, arguments); 5328 }, 5329 removeValidator: function removeValidator(name) { 5330 if (!this.validators[name]) Utils.warn('Validator "' + name + '" is not defined.'); 5331 delete this.validators[name]; 5332 return this; 5333 }, 5334 _setValidator: function _setValidator(name, validator, priority) { 5335 if ('object' !== _typeof(validator)) { 5336 // Old style validator, with `fn` and `priority` 5337 validator = { 5338 fn: validator, 5339 priority: priority 5340 }; 5341 } 5342 5343 if (!validator.validate) { 5344 validator = new Validator(validator); 5345 } 5346 5347 this.validators[name] = validator; 5348 5349 for (var locale in validator.messages || {}) { 5350 this.addMessage(locale, name, validator.messages[locale]); 5351 } 5352 5353 return this; 5354 }, 5355 getErrorMessage: function getErrorMessage(constraint) { 5356 var message; // Type constraints are a bit different, we have to match their requirements too to find right error message 5357 5358 if ('type' === constraint.name) { 5359 var typeMessages = this.catalog[this.locale][constraint.name] || {}; 5360 message = typeMessages[constraint.requirements]; 5361 } else message = this.formatMessage(this.catalog[this.locale][constraint.name], constraint.requirements); 5362 5363 return message || this.catalog[this.locale].defaultMessage || this.catalog.en.defaultMessage; 5364 }, 5365 // Kind of light `sprintf()` implementation 5366 formatMessage: function formatMessage(string, parameters) { 5367 if ('object' === _typeof(parameters)) { 5368 for (var i in parameters) { 5369 string = this.formatMessage(string, parameters[i]); 5370 } 5371 5372 return string; 5373 } 5374 5375 return 'string' === typeof string ? string.replace(/%s/i, parameters) : ''; 5376 }, 5377 // Here is the Parsley default validators list. 5378 // A validator is an object with the following key values: 5379 // - priority: an integer 5380 // - requirement: 'string' (default), 'integer', 'number', 'regexp' or an Array of these 5381 // - validateString, validateMultiple, validateNumber: functions returning `true`, `false` or a promise 5382 // Alternatively, a validator can be a function that returns such an object 5383 // 5384 validators: { 5385 notblank: { 5386 validateString: function validateString(value) { 5387 return /\S/.test(value); 5388 }, 5389 priority: 2 5390 }, 5391 required: { 5392 validateMultiple: function validateMultiple(values) { 5393 return values.length > 0; 5394 }, 5395 validateString: function validateString(value) { 5396 return /\S/.test(value); 5397 }, 5398 priority: 512 5399 }, 5400 type: { 5401 validateString: function validateString(value, type) { 5402 var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, 5403 _ref$step = _ref.step, 5404 step = _ref$step === void 0 ? 'any' : _ref$step, 5405 _ref$base = _ref.base, 5406 base = _ref$base === void 0 ? 0 : _ref$base; 5407 5408 var tester = typeTesters[type]; 5409 5410 if (!tester) { 5411 throw new Error('validator type `' + type + '` is not supported'); 5412 } 5413 5414 if (!value) return true; // Builtin validators all accept empty strings, except `required` of course 5415 5416 if (!tester.test(value)) return false; 5417 5418 if ('number' === type) { 5419 if (!/^any$/i.test(step || '')) { 5420 var nb = Number(value); 5421 var decimals = Math.max(decimalPlaces(step), decimalPlaces(base)); 5422 if (decimalPlaces(nb) > decimals) // Value can't have too many decimals 5423 return false; // Be careful of rounding errors by using integers. 5424 5425 var toInt = function toInt(f) { 5426 return Math.round(f * Math.pow(10, decimals)); 5427 }; 5428 5429 if ((toInt(nb) - toInt(base)) % toInt(step) != 0) return false; 5430 } 5431 } 5432 5433 return true; 5434 }, 5435 requirementType: { 5436 '': 'string', 5437 step: 'string', 5438 base: 'number' 5439 }, 5440 priority: 256 5441 }, 5442 pattern: { 5443 validateString: function validateString(value, regexp) { 5444 if (!value) return true; // Builtin validators all accept empty strings, except `required` of course 5445 5446 return regexp.test(value); 5447 }, 5448 requirementType: 'regexp', 5449 priority: 64 5450 }, 5451 minlength: { 5452 validateString: function validateString(value, requirement) { 5453 if (!value) return true; // Builtin validators all accept empty strings, except `required` of course 5454 5455 return value.length >= requirement; 5456 }, 5457 requirementType: 'integer', 5458 priority: 30 5459 }, 5460 maxlength: { 5461 validateString: function validateString(value, requirement) { 5462 return value.length <= requirement; 5463 }, 5464 requirementType: 'integer', 5465 priority: 30 5466 }, 5467 length: { 5468 validateString: function validateString(value, min, max) { 5469 if (!value) return true; // Builtin validators all accept empty strings, except `required` of course 5470 5471 return value.length >= min && value.length <= max; 5472 }, 5473 requirementType: ['integer', 'integer'], 5474 priority: 30 5475 }, 5476 mincheck: { 5477 validateMultiple: function validateMultiple(values, requirement) { 5478 return values.length >= requirement; 5479 }, 5480 requirementType: 'integer', 5481 priority: 30 5482 }, 5483 maxcheck: { 5484 validateMultiple: function validateMultiple(values, requirement) { 5485 return values.length <= requirement; 5486 }, 5487 requirementType: 'integer', 5488 priority: 30 5489 }, 5490 check: { 5491 validateMultiple: function validateMultiple(values, min, max) { 5492 return values.length >= min && values.length <= max; 5493 }, 5494 requirementType: ['integer', 'integer'], 5495 priority: 30 5496 }, 5497 min: comparisonOperator(function (value, requirement) { 5498 return value >= requirement; 5499 }), 5500 max: comparisonOperator(function (value, requirement) { 5501 return value <= requirement; 5502 }), 5503 range: comparisonOperator(function (value, min, max) { 5504 return value >= min && value <= max; 5505 }), 5506 equalto: { 5507 validateString: function validateString(value, refOrValue) { 5508 if (!value) return true; // Builtin validators all accept empty strings, except `required` of course 5509 5510 var $reference = $(refOrValue); 5511 if ($reference.length) return value === $reference.val();else return value === refOrValue; 5512 }, 5513 priority: 256 5514 }, 5515 euvatin: { 5516 validateString: function validateString(value, refOrValue) { 5517 if (!value) { 5518 return true; // Builtin validators all accept empty strings, except `required` of course 5519 } 5520 5521 var re = /^[A-Z][A-Z][A-Za-z0-9 -]{2,}$/; 5522 return re.test(value); 5523 }, 5524 priority: 30 5525 } 5526 } 5527 }; 5528 5529 var UI = {}; 5530 5531 var diffResults = function diffResults(newResult, oldResult, deep) { 5532 var added = []; 5533 var kept = []; 5534 5535 for (var i = 0; i < newResult.length; i++) { 5536 var found = false; 5537 5538 for (var j = 0; j < oldResult.length; j++) { 5539 if (newResult[i].assert.name === oldResult[j].assert.name) { 5540 found = true; 5541 break; 5542 } 5543 } 5544 5545 if (found) kept.push(newResult[i]);else added.push(newResult[i]); 5546 } 5547 5548 return { 5549 kept: kept, 5550 added: added, 5551 removed: !deep ? diffResults(oldResult, newResult, true).added : [] 5552 }; 5553 }; 5554 5555 UI.Form = { 5556 _actualizeTriggers: function _actualizeTriggers() { 5557 var _this = this; 5558 5559 this.$element.on('submit.Parsley', function (evt) { 5560 _this.onSubmitValidate(evt); 5561 }); 5562 this.$element.on('click.Parsley', Utils._SubmitSelector, function (evt) { 5563 _this.onSubmitButton(evt); 5564 }); // UI could be disabled 5565 5566 if (false === this.options.uiEnabled) return; 5567 this.element.setAttribute('novalidate', ''); 5568 }, 5569 focus: function focus() { 5570 this._focusedField = null; 5571 if (true === this.validationResult || 'none' === this.options.focus) return null; 5572 5573 for (var i = 0; i < this.fields.length; i++) { 5574 var field = this.fields[i]; 5575 5576 if (true !== field.validationResult && field.validationResult.length > 0 && 'undefined' === typeof field.options.noFocus) { 5577 this._focusedField = field.$element; 5578 if ('first' === this.options.focus) break; 5579 } 5580 } 5581 5582 if (null === this._focusedField) return null; 5583 return this._focusedField.focus(); 5584 }, 5585 _destroyUI: function _destroyUI() { 5586 // Reset all event listeners 5587 this.$element.off('.Parsley'); 5588 } 5589 }; 5590 UI.Field = { 5591 _reflowUI: function _reflowUI() { 5592 this._buildUI(); // If this field doesn't have an active UI don't bother doing something 5593 5594 5595 if (!this._ui) return; // Diff between two validation results 5596 5597 var diff = diffResults(this.validationResult, this._ui.lastValidationResult); // Then store current validation result for next reflow 5598 5599 this._ui.lastValidationResult = this.validationResult; // Handle valid / invalid / none field class 5600 5601 this._manageStatusClass(); // Add, remove, updated errors messages 5602 5603 5604 this._manageErrorsMessages(diff); // Triggers impl 5605 5606 5607 this._actualizeTriggers(); // If field is not valid for the first time, bind keyup trigger to ease UX and quickly inform user 5608 5609 5610 if ((diff.kept.length || diff.added.length) && !this._failedOnce) { 5611 this._failedOnce = true; 5612 5613 this._actualizeTriggers(); 5614 } 5615 }, 5616 // Returns an array of field's error message(s) 5617 getErrorsMessages: function getErrorsMessages() { 5618 // No error message, field is valid 5619 if (true === this.validationResult) return []; 5620 var messages = []; 5621 5622 for (var i = 0; i < this.validationResult.length; i++) { 5623 messages.push(this.validationResult[i].errorMessage || this._getErrorMessage(this.validationResult[i].assert)); 5624 } 5625 5626 return messages; 5627 }, 5628 // It's a goal of Parsley that this method is no longer required [#1073] 5629 addError: function addError(name) { 5630 var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 5631 message = _ref.message, 5632 assert = _ref.assert, 5633 _ref$updateClass = _ref.updateClass, 5634 updateClass = _ref$updateClass === void 0 ? true : _ref$updateClass; 5635 5636 this._buildUI(); 5637 5638 this._addError(name, { 5639 message: message, 5640 assert: assert 5641 }); 5642 5643 if (updateClass) this._errorClass(); 5644 }, 5645 // It's a goal of Parsley that this method is no longer required [#1073] 5646 updateError: function updateError(name) { 5647 var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 5648 message = _ref2.message, 5649 assert = _ref2.assert, 5650 _ref2$updateClass = _ref2.updateClass, 5651 updateClass = _ref2$updateClass === void 0 ? true : _ref2$updateClass; 5652 5653 this._buildUI(); 5654 5655 this._updateError(name, { 5656 message: message, 5657 assert: assert 5658 }); 5659 5660 if (updateClass) this._errorClass(); 5661 }, 5662 // It's a goal of Parsley that this method is no longer required [#1073] 5663 removeError: function removeError(name) { 5664 var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 5665 _ref3$updateClass = _ref3.updateClass, 5666 updateClass = _ref3$updateClass === void 0 ? true : _ref3$updateClass; 5667 5668 this._buildUI(); 5669 5670 this._removeError(name); // edge case possible here: remove a standard Parsley error that is still failing in this.validationResult 5671 // but highly improbable cuz' manually removing a well Parsley handled error makes no sense. 5672 5673 5674 if (updateClass) this._manageStatusClass(); 5675 }, 5676 _manageStatusClass: function _manageStatusClass() { 5677 if (this.hasConstraints() && this.needsValidation() && true === this.validationResult) this._successClass();else if (this.validationResult.length > 0) this._errorClass();else this._resetClass(); 5678 }, 5679 _manageErrorsMessages: function _manageErrorsMessages(diff) { 5680 if ('undefined' !== typeof this.options.errorsMessagesDisabled) return; // Case where we have errorMessage option that configure an unique field error message, regardless failing validators 5681 5682 if ('undefined' !== typeof this.options.errorMessage) { 5683 if (diff.added.length || diff.kept.length) { 5684 this._insertErrorWrapper(); 5685 5686 if (0 === this._ui.$errorsWrapper.find('.parsley-custom-error-message').length) this._ui.$errorsWrapper.append($(this.options.errorTemplate).addClass('parsley-custom-error-message')); 5687 5688 this._ui.$errorClassHandler.attr('aria-describedby', this._ui.errorsWrapperId); 5689 5690 return this._ui.$errorsWrapper.addClass('filled').attr('aria-hidden', 'false').find('.parsley-custom-error-message').html(this.options.errorMessage); 5691 } 5692 5693 this._ui.$errorClassHandler.removeAttr('aria-describedby'); 5694 5695 return this._ui.$errorsWrapper.removeClass('filled').attr('aria-hidden', 'true').find('.parsley-custom-error-message').remove(); 5696 } // Show, hide, update failing constraints messages 5697 5698 5699 for (var i = 0; i < diff.removed.length; i++) { 5700 this._removeError(diff.removed[i].assert.name); 5701 } 5702 5703 for (i = 0; i < diff.added.length; i++) { 5704 this._addError(diff.added[i].assert.name, { 5705 message: diff.added[i].errorMessage, 5706 assert: diff.added[i].assert 5707 }); 5708 } 5709 5710 for (i = 0; i < diff.kept.length; i++) { 5711 this._updateError(diff.kept[i].assert.name, { 5712 message: diff.kept[i].errorMessage, 5713 assert: diff.kept[i].assert 5714 }); 5715 } 5716 }, 5717 _addError: function _addError(name, _ref4) { 5718 var message = _ref4.message, 5719 assert = _ref4.assert; 5720 5721 this._insertErrorWrapper(); 5722 5723 this._ui.$errorClassHandler.attr('aria-describedby', this._ui.errorsWrapperId); 5724 5725 this._ui.$errorsWrapper.addClass('filled').attr('aria-hidden', 'false').append($(this.options.errorTemplate).addClass('parsley-' + name).html(message || this._getErrorMessage(assert))); 5726 }, 5727 _updateError: function _updateError(name, _ref5) { 5728 var message = _ref5.message, 5729 assert = _ref5.assert; 5730 5731 this._ui.$errorsWrapper.addClass('filled').find('.parsley-' + name).html(message || this._getErrorMessage(assert)); 5732 }, 5733 _removeError: function _removeError(name) { 5734 this._ui.$errorClassHandler.removeAttr('aria-describedby'); 5735 5736 this._ui.$errorsWrapper.removeClass('filled').attr('aria-hidden', 'true').find('.parsley-' + name).remove(); 5737 }, 5738 _getErrorMessage: function _getErrorMessage(constraint) { 5739 var customConstraintErrorMessage = constraint.name + 'Message'; 5740 if ('undefined' !== typeof this.options[customConstraintErrorMessage]) return window.Parsley.formatMessage(this.options[customConstraintErrorMessage], constraint.requirements); 5741 return window.Parsley.getErrorMessage(constraint); 5742 }, 5743 _buildUI: function _buildUI() { 5744 // UI could be already built or disabled 5745 if (this._ui || false === this.options.uiEnabled) return; 5746 var _ui = {}; // Give field its Parsley id in DOM 5747 5748 this.element.setAttribute(this.options.namespace + 'id', this.__id__); 5749 /** Generate important UI elements and store them in this **/ 5750 // $errorClassHandler is the $element that woul have parsley-error and parsley-success classes 5751 5752 _ui.$errorClassHandler = this._manageClassHandler(); // $errorsWrapper is a div that would contain the various field errors, it will be appended into $errorsContainer 5753 5754 _ui.errorsWrapperId = 'parsley-id-' + (this.options.multiple ? 'multiple-' + this.options.multiple : this.__id__); 5755 _ui.$errorsWrapper = $(this.options.errorsWrapper).attr('id', _ui.errorsWrapperId); // ValidationResult UI storage to detect what have changed bwt two validations, and update DOM accordingly 5756 5757 _ui.lastValidationResult = []; 5758 _ui.validationInformationVisible = false; // Store it in this for later 5759 5760 this._ui = _ui; 5761 }, 5762 // Determine which element will have `parsley-error` and `parsley-success` classes 5763 _manageClassHandler: function _manageClassHandler() { 5764 // Class handled could also be determined by function given in Parsley options 5765 if ('string' === typeof this.options.classHandler && $(this.options.classHandler).length) return $(this.options.classHandler); // Class handled could also be determined by function given in Parsley options 5766 5767 var $handlerFunction = this.options.classHandler; // It might also be the function name of a global function 5768 5769 if ('string' === typeof this.options.classHandler && 'function' === typeof window[this.options.classHandler]) $handlerFunction = window[this.options.classHandler]; 5770 5771 if ('function' === typeof $handlerFunction) { 5772 var $handler = $handlerFunction.call(this, this); // If this function returned a valid existing DOM element, go for it 5773 5774 if ('undefined' !== typeof $handler && $handler.length) return $handler; 5775 } else if ('object' === _typeof($handlerFunction) && $handlerFunction instanceof jQuery && $handlerFunction.length) { 5776 return $handlerFunction; 5777 } else if ($handlerFunction) { 5778 Utils.warn('The class handler `' + $handlerFunction + '` does not exist in DOM nor as a global JS function'); 5779 } 5780 5781 return this._inputHolder(); 5782 }, 5783 _inputHolder: function _inputHolder() { 5784 // if simple element (input, texatrea, select...) it will perfectly host the classes and precede the error container 5785 if (!this.options.multiple || this.element.nodeName === 'SELECT') return this.$element; // But if multiple element (radio, checkbox), that would be their parent 5786 5787 return this.$element.parent(); 5788 }, 5789 _insertErrorWrapper: function _insertErrorWrapper() { 5790 var $errorsContainer = this.options.errorsContainer; // Nothing to do if already inserted 5791 5792 if (0 !== this._ui.$errorsWrapper.parent().length) return this._ui.$errorsWrapper.parent(); 5793 5794 if ('string' === typeof $errorsContainer) { 5795 if ($($errorsContainer).length) return $($errorsContainer).append(this._ui.$errorsWrapper);else if ('function' === typeof window[$errorsContainer]) $errorsContainer = window[$errorsContainer];else Utils.warn('The errors container `' + $errorsContainer + '` does not exist in DOM nor as a global JS function'); 5796 } 5797 5798 if ('function' === typeof $errorsContainer) $errorsContainer = $errorsContainer.call(this, this); 5799 if ('object' === _typeof($errorsContainer) && $errorsContainer.length) return $errorsContainer.append(this._ui.$errorsWrapper); 5800 return this._inputHolder().after(this._ui.$errorsWrapper); 5801 }, 5802 _actualizeTriggers: function _actualizeTriggers() { 5803 var _this2 = this; 5804 5805 var $toBind = this._findRelated(); 5806 5807 var trigger; // Remove Parsley events already bound on this field 5808 5809 $toBind.off('.Parsley'); 5810 if (this._failedOnce) $toBind.on(Utils.namespaceEvents(this.options.triggerAfterFailure, 'Parsley'), function () { 5811 _this2._validateIfNeeded(); 5812 });else if (trigger = Utils.namespaceEvents(this.options.trigger, 'Parsley')) { 5813 $toBind.on(trigger, function (event) { 5814 _this2._validateIfNeeded(event); 5815 }); 5816 } 5817 }, 5818 _validateIfNeeded: function _validateIfNeeded(event) { 5819 var _this3 = this; 5820 5821 // For keyup, keypress, keydown, input... events that could be a little bit obstrusive 5822 // do not validate if val length < min threshold on first validation. Once field have been validated once and info 5823 // about success or failure have been displayed, always validate with this trigger to reflect every yalidation change. 5824 if (event && /key|input/.test(event.type)) if (!(this._ui && this._ui.validationInformationVisible) && this.getValue().length <= this.options.validationThreshold) return; 5825 5826 if (this.options.debounce) { 5827 window.clearTimeout(this._debounced); 5828 this._debounced = window.setTimeout(function () { 5829 return _this3.validate(); 5830 }, this.options.debounce); 5831 } else this.validate(); 5832 }, 5833 _resetUI: function _resetUI() { 5834 // Reset all event listeners 5835 this._failedOnce = false; 5836 5837 this._actualizeTriggers(); // Nothing to do if UI never initialized for this field 5838 5839 5840 if ('undefined' === typeof this._ui) return; // Reset all errors' li 5841 5842 this._ui.$errorsWrapper.removeClass('filled').children().remove(); // Reset validation class 5843 5844 5845 this._resetClass(); // Reset validation flags and last validation result 5846 5847 5848 this._ui.lastValidationResult = []; 5849 this._ui.validationInformationVisible = false; 5850 }, 5851 _destroyUI: function _destroyUI() { 5852 this._resetUI(); 5853 5854 if ('undefined' !== typeof this._ui) this._ui.$errorsWrapper.remove(); 5855 delete this._ui; 5856 }, 5857 _successClass: function _successClass() { 5858 this._ui.validationInformationVisible = true; 5859 5860 this._ui.$errorClassHandler.removeClass(this.options.errorClass).addClass(this.options.successClass); 5861 }, 5862 _errorClass: function _errorClass() { 5863 this._ui.validationInformationVisible = true; 5864 5865 this._ui.$errorClassHandler.removeClass(this.options.successClass).addClass(this.options.errorClass); 5866 }, 5867 _resetClass: function _resetClass() { 5868 this._ui.$errorClassHandler.removeClass(this.options.successClass).removeClass(this.options.errorClass); 5869 } 5870 }; 5871 5872 var Form = function Form(element, domOptions, options) { 5873 this.__class__ = 'Form'; 5874 this.element = element; 5875 this.$element = $(element); 5876 this.domOptions = domOptions; 5877 this.options = options; 5878 this.parent = window.Parsley; 5879 this.fields = []; 5880 this.validationResult = null; 5881 }; 5882 5883 var statusMapping = { 5884 pending: null, 5885 resolved: true, 5886 rejected: false 5887 }; 5888 Form.prototype = { 5889 onSubmitValidate: function onSubmitValidate(event) { 5890 var _this = this; 5891 5892 // This is a Parsley generated submit event, do not validate, do not prevent, simply exit and keep normal behavior 5893 if (true === event.parsley) return; // If we didn't come here through a submit button, use the first one in the form 5894 5895 var submitSource = this._submitSource || this.$element.find(Utils._SubmitSelector)[0]; 5896 this._submitSource = null; 5897 this.$element.find('.parsley-synthetic-submit-button').prop('disabled', true); 5898 if (submitSource && null !== submitSource.getAttribute('formnovalidate')) return; 5899 window.Parsley._remoteCache = {}; 5900 var promise = this.whenValidate({ 5901 event: event 5902 }); 5903 5904 if ('resolved' === promise.state() && false !== this._trigger('submit')) ; else { 5905 // Rejected or pending: cancel this submit 5906 event.stopImmediatePropagation(); 5907 event.preventDefault(); 5908 if ('pending' === promise.state()) promise.done(function () { 5909 _this._submit(submitSource); 5910 }); 5911 } 5912 }, 5913 onSubmitButton: function onSubmitButton(event) { 5914 this._submitSource = event.currentTarget; 5915 }, 5916 // internal 5917 // _submit submits the form, this time without going through the validations. 5918 // Care must be taken to "fake" the actual submit button being clicked. 5919 _submit: function _submit(submitSource) { 5920 if (false === this._trigger('submit')) return; // Add submit button's data 5921 5922 if (submitSource) { 5923 var $synthetic = this.$element.find('.parsley-synthetic-submit-button').prop('disabled', false); 5924 if (0 === $synthetic.length) $synthetic = $('<input class="parsley-synthetic-submit-button" type="hidden">').appendTo(this.$element); 5925 $synthetic.attr({ 5926 name: submitSource.getAttribute('name'), 5927 value: submitSource.getAttribute('value') 5928 }); 5929 } 5930 5931 this.$element.trigger(_extends($.Event('submit'), { 5932 parsley: true 5933 })); 5934 }, 5935 // Performs validation on fields while triggering events. 5936 // @returns `true` if all validations succeeds, `false` 5937 // if a failure is immediately detected, or `null` 5938 // if dependant on a promise. 5939 // Consider using `whenValidate` instead. 5940 validate: function validate(options) { 5941 if (arguments.length >= 1 && !$.isPlainObject(options)) { 5942 Utils.warnOnce('Calling validate on a parsley form without passing arguments as an object is deprecated.'); 5943 5944 var _arguments = Array.prototype.slice.call(arguments), 5945 group = _arguments[0], 5946 force = _arguments[1], 5947 event = _arguments[2]; 5948 5949 options = { 5950 group: group, 5951 force: force, 5952 event: event 5953 }; 5954 } 5955 5956 return statusMapping[this.whenValidate(options).state()]; 5957 }, 5958 whenValidate: function whenValidate() { 5959 var _this2 = this, 5960 _Utils$all$done$fail$; 5961 5962 var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 5963 group = _ref.group, 5964 force = _ref.force, 5965 event = _ref.event; 5966 5967 this.submitEvent = event; 5968 5969 if (event) { 5970 this.submitEvent = _extends({}, event, { 5971 preventDefault: function preventDefault() { 5972 Utils.warnOnce("Using `this.submitEvent.preventDefault()` is deprecated; instead, call `this.validationResult = false`"); 5973 _this2.validationResult = false; 5974 } 5975 }); 5976 } 5977 5978 this.validationResult = true; // fire validate event to eventually modify things before every validation 5979 5980 this._trigger('validate'); // Refresh form DOM options and form's fields that could have changed 5981 5982 5983 this._refreshFields(); 5984 5985 var promises = this._withoutReactualizingFormOptions(function () { 5986 return $.map(_this2.fields, function (field) { 5987 return field.whenValidate({ 5988 force: force, 5989 group: group 5990 }); 5991 }); 5992 }); 5993 5994 return (_Utils$all$done$fail$ = Utils.all(promises).done(function () { 5995 _this2._trigger('success'); 5996 }).fail(function () { 5997 _this2.validationResult = false; 5998 5999 _this2.focus(); 6000 6001 _this2._trigger('error'); 6002 }).always(function () { 6003 _this2._trigger('validated'); 6004 })).pipe.apply(_Utils$all$done$fail$, _toConsumableArray(this._pipeAccordingToValidationResult())); 6005 }, 6006 // Iterate over refreshed fields, and stop on first failure. 6007 // Returns `true` if all fields are valid, `false` if a failure is detected 6008 // or `null` if the result depends on an unresolved promise. 6009 // Prefer using `whenValid` instead. 6010 isValid: function isValid(options) { 6011 if (arguments.length >= 1 && !$.isPlainObject(options)) { 6012 Utils.warnOnce('Calling isValid on a parsley form without passing arguments as an object is deprecated.'); 6013 6014 var _arguments2 = Array.prototype.slice.call(arguments), 6015 group = _arguments2[0], 6016 force = _arguments2[1]; 6017 6018 options = { 6019 group: group, 6020 force: force 6021 }; 6022 } 6023 6024 return statusMapping[this.whenValid(options).state()]; 6025 }, 6026 // Iterate over refreshed fields and validate them. 6027 // Returns a promise. 6028 // A validation that immediately fails will interrupt the validations. 6029 whenValid: function whenValid() { 6030 var _this3 = this; 6031 6032 var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 6033 group = _ref2.group, 6034 force = _ref2.force; 6035 6036 this._refreshFields(); 6037 6038 var promises = this._withoutReactualizingFormOptions(function () { 6039 return $.map(_this3.fields, function (field) { 6040 return field.whenValid({ 6041 group: group, 6042 force: force 6043 }); 6044 }); 6045 }); 6046 6047 return Utils.all(promises); 6048 }, 6049 refresh: function refresh() { 6050 this._refreshFields(); 6051 6052 return this; 6053 }, 6054 // Reset UI 6055 reset: function reset() { 6056 // Form case: emit a reset event for each field 6057 for (var i = 0; i < this.fields.length; i++) { 6058 this.fields[i].reset(); 6059 } 6060 6061 this._trigger('reset'); 6062 }, 6063 // Destroy Parsley instance (+ UI) 6064 destroy: function destroy() { 6065 // Field case: emit destroy event to clean UI and then destroy stored instance 6066 this._destroyUI(); // Form case: destroy all its fields and then destroy stored instance 6067 6068 6069 for (var i = 0; i < this.fields.length; i++) { 6070 this.fields[i].destroy(); 6071 } 6072 6073 this.$element.removeData('Parsley'); 6074 6075 this._trigger('destroy'); 6076 }, 6077 _refreshFields: function _refreshFields() { 6078 return this.actualizeOptions()._bindFields(); 6079 }, 6080 _bindFields: function _bindFields() { 6081 var _this4 = this; 6082 6083 var oldFields = this.fields; 6084 this.fields = []; 6085 this.fieldsMappedById = {}; 6086 6087 this._withoutReactualizingFormOptions(function () { 6088 _this4.$element.find(_this4.options.inputs).not(_this4.options.excluded).not("[".concat(_this4.options.namespace, "excluded=true]")).each(function (_, element) { 6089 var fieldInstance = new window.Parsley.Factory(element, {}, _this4); // Only add valid and not excluded `Field` and `FieldMultiple` children 6090 6091 if ('Field' === fieldInstance.__class__ || 'FieldMultiple' === fieldInstance.__class__) { 6092 var uniqueId = fieldInstance.__class__ + '-' + fieldInstance.__id__; 6093 6094 if ('undefined' === typeof _this4.fieldsMappedById[uniqueId]) { 6095 _this4.fieldsMappedById[uniqueId] = fieldInstance; 6096 6097 _this4.fields.push(fieldInstance); 6098 } 6099 } 6100 }); 6101 6102 $.each(Utils.difference(oldFields, _this4.fields), function (_, field) { 6103 field.reset(); 6104 }); 6105 }); 6106 6107 return this; 6108 }, 6109 // Internal only. 6110 // Looping on a form's fields to do validation or similar 6111 // will trigger reactualizing options on all of them, which 6112 // in turn will reactualize the form's options. 6113 // To avoid calling actualizeOptions so many times on the form 6114 // for nothing, _withoutReactualizingFormOptions temporarily disables 6115 // the method actualizeOptions on this form while `fn` is called. 6116 _withoutReactualizingFormOptions: function _withoutReactualizingFormOptions(fn) { 6117 var oldActualizeOptions = this.actualizeOptions; 6118 6119 this.actualizeOptions = function () { 6120 return this; 6121 }; 6122 6123 var result = fn(); 6124 this.actualizeOptions = oldActualizeOptions; 6125 return result; 6126 }, 6127 // Internal only. 6128 // Shortcut to trigger an event 6129 // Returns true iff event is not interrupted and default not prevented. 6130 _trigger: function _trigger(eventName) { 6131 return this.trigger('form:' + eventName); 6132 } 6133 }; 6134 6135 var Constraint = function Constraint(parsleyField, name, requirements, priority, isDomConstraint) { 6136 var validatorSpec = window.Parsley._validatorRegistry.validators[name]; 6137 var validator = new Validator(validatorSpec); 6138 priority = priority || parsleyField.options[name + 'Priority'] || validator.priority; 6139 isDomConstraint = true === isDomConstraint; 6140 6141 _extends(this, { 6142 validator: validator, 6143 name: name, 6144 requirements: requirements, 6145 priority: priority, 6146 isDomConstraint: isDomConstraint 6147 }); 6148 6149 this._parseRequirements(parsleyField.options); 6150 }; 6151 6152 var capitalize = function capitalize(str) { 6153 var cap = str[0].toUpperCase(); 6154 return cap + str.slice(1); 6155 }; 6156 6157 Constraint.prototype = { 6158 validate: function validate(value, instance) { 6159 var _this$validator; 6160 6161 return (_this$validator = this.validator).validate.apply(_this$validator, [value].concat(_toConsumableArray(this.requirementList), [instance])); 6162 }, 6163 _parseRequirements: function _parseRequirements(options) { 6164 var _this = this; 6165 6166 this.requirementList = this.validator.parseRequirements(this.requirements, function (key) { 6167 return options[_this.name + capitalize(key)]; 6168 }); 6169 } 6170 }; 6171 6172 var Field = function Field(field, domOptions, options, parsleyFormInstance) { 6173 this.__class__ = 'Field'; 6174 this.element = field; 6175 this.$element = $(field); // Set parent if we have one 6176 6177 if ('undefined' !== typeof parsleyFormInstance) { 6178 this.parent = parsleyFormInstance; 6179 } 6180 6181 this.options = options; 6182 this.domOptions = domOptions; // Initialize some properties 6183 6184 this.constraints = []; 6185 this.constraintsByName = {}; 6186 this.validationResult = true; // Bind constraints 6187 6188 this._bindConstraints(); 6189 }; 6190 6191 var statusMapping$1 = { 6192 pending: null, 6193 resolved: true, 6194 rejected: false 6195 }; 6196 Field.prototype = { 6197 // # Public API 6198 // Validate field and trigger some events for mainly `UI` 6199 // @returns `true`, an array of the validators that failed, or 6200 // `null` if validation is not finished. Prefer using whenValidate 6201 validate: function validate(options) { 6202 if (arguments.length >= 1 && !$.isPlainObject(options)) { 6203 Utils.warnOnce('Calling validate on a parsley field without passing arguments as an object is deprecated.'); 6204 options = { 6205 options: options 6206 }; 6207 } 6208 6209 var promise = this.whenValidate(options); 6210 if (!promise) // If excluded with `group` option 6211 return true; 6212 6213 switch (promise.state()) { 6214 case 'pending': 6215 return null; 6216 6217 case 'resolved': 6218 return true; 6219 6220 case 'rejected': 6221 return this.validationResult; 6222 } 6223 }, 6224 // Validate field and trigger some events for mainly `UI` 6225 // @returns a promise that succeeds only when all validations do 6226 // or `undefined` if field is not in the given `group`. 6227 whenValidate: function whenValidate() { 6228 var _this$whenValid$alway, 6229 _this = this; 6230 6231 var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 6232 force = _ref.force, 6233 group = _ref.group; 6234 6235 // do not validate a field if not the same as given validation group 6236 this.refresh(); 6237 if (group && !this._isInGroup(group)) return; 6238 this.value = this.getValue(); // Field Validate event. `this.value` could be altered for custom needs 6239 6240 this._trigger('validate'); 6241 6242 return (_this$whenValid$alway = this.whenValid({ 6243 force: force, 6244 value: this.value, 6245 _refreshed: true 6246 }).always(function () { 6247 _this._reflowUI(); 6248 }).done(function () { 6249 _this._trigger('success'); 6250 }).fail(function () { 6251 _this._trigger('error'); 6252 }).always(function () { 6253 _this._trigger('validated'); 6254 })).pipe.apply(_this$whenValid$alway, _toConsumableArray(this._pipeAccordingToValidationResult())); 6255 }, 6256 hasConstraints: function hasConstraints() { 6257 return 0 !== this.constraints.length; 6258 }, 6259 // An empty optional field does not need validation 6260 needsValidation: function needsValidation(value) { 6261 if ('undefined' === typeof value) value = this.getValue(); // If a field is empty and not required, it is valid 6262 // Except if `data-parsley-validate-if-empty` explicitely added, useful for some custom validators 6263 6264 if (!value.length && !this._isRequired() && 'undefined' === typeof this.options.validateIfEmpty) return false; 6265 return true; 6266 }, 6267 _isInGroup: function _isInGroup(group) { 6268 if (Array.isArray(this.options.group)) return -1 !== $.inArray(group, this.options.group); 6269 return this.options.group === group; 6270 }, 6271 // Just validate field. Do not trigger any event. 6272 // Returns `true` iff all constraints pass, `false` if there are failures, 6273 // or `null` if the result can not be determined yet (depends on a promise) 6274 // See also `whenValid`. 6275 isValid: function isValid(options) { 6276 if (arguments.length >= 1 && !$.isPlainObject(options)) { 6277 Utils.warnOnce('Calling isValid on a parsley field without passing arguments as an object is deprecated.'); 6278 6279 var _arguments = Array.prototype.slice.call(arguments), 6280 force = _arguments[0], 6281 value = _arguments[1]; 6282 6283 options = { 6284 force: force, 6285 value: value 6286 }; 6287 } 6288 6289 var promise = this.whenValid(options); 6290 if (!promise) // Excluded via `group` 6291 return true; 6292 return statusMapping$1[promise.state()]; 6293 }, 6294 // Just validate field. Do not trigger any event. 6295 // @returns a promise that succeeds only when all validations do 6296 // or `undefined` if the field is not in the given `group`. 6297 // The argument `force` will force validation of empty fields. 6298 // If a `value` is given, it will be validated instead of the value of the input. 6299 whenValid: function whenValid() { 6300 var _this2 = this; 6301 6302 var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, 6303 _ref2$force = _ref2.force, 6304 force = _ref2$force === void 0 ? false : _ref2$force, 6305 value = _ref2.value, 6306 group = _ref2.group, 6307 _refreshed = _ref2._refreshed; 6308 6309 // Recompute options and rebind constraints to have latest changes 6310 if (!_refreshed) this.refresh(); // do not validate a field if not the same as given validation group 6311 6312 if (group && !this._isInGroup(group)) return; 6313 this.validationResult = true; // A field without constraint is valid 6314 6315 if (!this.hasConstraints()) return $.when(); // Value could be passed as argument, needed to add more power to 'field:validate' 6316 6317 if ('undefined' === typeof value || null === value) value = this.getValue(); 6318 if (!this.needsValidation(value) && true !== force) return $.when(); 6319 6320 var groupedConstraints = this._getGroupedConstraints(); 6321 6322 var promises = []; 6323 $.each(groupedConstraints, function (_, constraints) { 6324 // Process one group of constraints at a time, we validate the constraints 6325 // and combine the promises together. 6326 var promise = Utils.all($.map(constraints, function (constraint) { 6327 return _this2._validateConstraint(value, constraint); 6328 })); 6329 promises.push(promise); 6330 if (promise.state() === 'rejected') return false; // Interrupt processing if a group has already failed 6331 }); 6332 return Utils.all(promises); 6333 }, 6334 // @returns a promise 6335 _validateConstraint: function _validateConstraint(value, constraint) { 6336 var _this3 = this; 6337 6338 var result = constraint.validate(value, this); // Map false to a failed promise 6339 6340 if (false === result) result = $.Deferred().reject(); // Make sure we return a promise and that we record failures 6341 6342 return Utils.all([result]).fail(function (errorMessage) { 6343 if (!(_this3.validationResult instanceof Array)) _this3.validationResult = []; 6344 6345 _this3.validationResult.push({ 6346 assert: constraint, 6347 errorMessage: 'string' === typeof errorMessage && errorMessage 6348 }); 6349 }); 6350 }, 6351 // @returns Parsley field computed value that could be overrided or configured in DOM 6352 getValue: function getValue() { 6353 var value; // Value could be overriden in DOM or with explicit options 6354 6355 if ('function' === typeof this.options.value) value = this.options.value(this);else if ('undefined' !== typeof this.options.value) value = this.options.value;else value = this.$element.val(); // Handle wrong DOM or configurations 6356 6357 if ('undefined' === typeof value || null === value) return ''; 6358 return this._handleWhitespace(value); 6359 }, 6360 // Reset UI 6361 reset: function reset() { 6362 this._resetUI(); 6363 6364 return this._trigger('reset'); 6365 }, 6366 // Destroy Parsley instance (+ UI) 6367 destroy: function destroy() { 6368 // Field case: emit destroy event to clean UI and then destroy stored instance 6369 this._destroyUI(); 6370 6371 this.$element.removeData('Parsley'); 6372 this.$element.removeData('FieldMultiple'); 6373 6374 this._trigger('destroy'); 6375 }, 6376 // Actualize options and rebind constraints 6377 refresh: function refresh() { 6378 this._refreshConstraints(); 6379 6380 return this; 6381 }, 6382 _refreshConstraints: function _refreshConstraints() { 6383 return this.actualizeOptions()._bindConstraints(); 6384 }, 6385 refreshConstraints: function refreshConstraints() { 6386 Utils.warnOnce("Parsley's refreshConstraints is deprecated. Please use refresh"); 6387 return this.refresh(); 6388 }, 6389 6390 /** 6391 * Add a new constraint to a field 6392 * 6393 * @param {String} name 6394 * @param {Mixed} requirements optional 6395 * @param {Number} priority optional 6396 * @param {Boolean} isDomConstraint optional 6397 */ 6398 addConstraint: function addConstraint(name, requirements, priority, isDomConstraint) { 6399 if (window.Parsley._validatorRegistry.validators[name]) { 6400 var constraint = new Constraint(this, name, requirements, priority, isDomConstraint); // if constraint already exist, delete it and push new version 6401 6402 if ('undefined' !== this.constraintsByName[constraint.name]) this.removeConstraint(constraint.name); 6403 this.constraints.push(constraint); 6404 this.constraintsByName[constraint.name] = constraint; 6405 } 6406 6407 return this; 6408 }, 6409 // Remove a constraint 6410 removeConstraint: function removeConstraint(name) { 6411 for (var i = 0; i < this.constraints.length; i++) { 6412 if (name === this.constraints[i].name) { 6413 this.constraints.splice(i, 1); 6414 break; 6415 } 6416 } 6417 6418 delete this.constraintsByName[name]; 6419 return this; 6420 }, 6421 // Update a constraint (Remove + re-add) 6422 updateConstraint: function updateConstraint(name, parameters, priority) { 6423 return this.removeConstraint(name).addConstraint(name, parameters, priority); 6424 }, 6425 // # Internals 6426 // Internal only. 6427 // Bind constraints from config + options + DOM 6428 _bindConstraints: function _bindConstraints() { 6429 var constraints = []; 6430 var constraintsByName = {}; // clean all existing DOM constraints to only keep javascript user constraints 6431 6432 for (var i = 0; i < this.constraints.length; i++) { 6433 if (false === this.constraints[i].isDomConstraint) { 6434 constraints.push(this.constraints[i]); 6435 constraintsByName[this.constraints[i].name] = this.constraints[i]; 6436 } 6437 } 6438 6439 this.constraints = constraints; 6440 this.constraintsByName = constraintsByName; // then re-add Parsley DOM-API constraints 6441 6442 for (var name in this.options) { 6443 this.addConstraint(name, this.options[name], undefined, true); 6444 } // finally, bind special HTML5 constraints 6445 6446 6447 return this._bindHtml5Constraints(); 6448 }, 6449 // Internal only. 6450 // Bind specific HTML5 constraints to be HTML5 compliant 6451 _bindHtml5Constraints: function _bindHtml5Constraints() { 6452 // html5 required 6453 if (null !== this.element.getAttribute('required')) this.addConstraint('required', true, undefined, true); // html5 pattern 6454 6455 if (null !== this.element.getAttribute('pattern')) this.addConstraint('pattern', this.element.getAttribute('pattern'), undefined, true); // range 6456 6457 var min = this.element.getAttribute('min'); 6458 var max = this.element.getAttribute('max'); 6459 if (null !== min && null !== max) this.addConstraint('range', [min, max], undefined, true); // HTML5 min 6460 else if (null !== min) this.addConstraint('min', min, undefined, true); // HTML5 max 6461 else if (null !== max) this.addConstraint('max', max, undefined, true); // length 6462 6463 if (null !== this.element.getAttribute('minlength') && null !== this.element.getAttribute('maxlength')) this.addConstraint('length', [this.element.getAttribute('minlength'), this.element.getAttribute('maxlength')], undefined, true); // HTML5 minlength 6464 else if (null !== this.element.getAttribute('minlength')) this.addConstraint('minlength', this.element.getAttribute('minlength'), undefined, true); // HTML5 maxlength 6465 else if (null !== this.element.getAttribute('maxlength')) this.addConstraint('maxlength', this.element.getAttribute('maxlength'), undefined, true); // html5 types 6466 6467 var type = Utils.getType(this.element); // Small special case here for HTML5 number: integer validator if step attribute is undefined or an integer value, number otherwise 6468 6469 if ('number' === type) { 6470 return this.addConstraint('type', ['number', { 6471 step: this.element.getAttribute('step') || '1', 6472 base: min || this.element.getAttribute('value') 6473 }], undefined, true); // Regular other HTML5 supported types 6474 } else if (/^(email|url|range|date)$/i.test(type)) { 6475 return this.addConstraint('type', type, undefined, true); 6476 } 6477 6478 return this; 6479 }, 6480 // Internal only. 6481 // Field is required if have required constraint without `false` value 6482 _isRequired: function _isRequired() { 6483 if ('undefined' === typeof this.constraintsByName.required) return false; 6484 return false !== this.constraintsByName.required.requirements; 6485 }, 6486 // Internal only. 6487 // Shortcut to trigger an event 6488 _trigger: function _trigger(eventName) { 6489 return this.trigger('field:' + eventName); 6490 }, 6491 // Internal only 6492 // Handles whitespace in a value 6493 // Use `data-parsley-whitespace="squish"` to auto squish input value 6494 // Use `data-parsley-whitespace="trim"` to auto trim input value 6495 _handleWhitespace: function _handleWhitespace(value) { 6496 if (true === this.options.trimValue) Utils.warnOnce('data-parsley-trim-value="true" is deprecated, please use data-parsley-whitespace="trim"'); 6497 if ('squish' === this.options.whitespace) value = value.replace(/\s{2,}/g, ' '); 6498 if ('trim' === this.options.whitespace || 'squish' === this.options.whitespace || true === this.options.trimValue) value = Utils.trimString(value); 6499 return value; 6500 }, 6501 _isDateInput: function _isDateInput() { 6502 var c = this.constraintsByName.type; 6503 return c && c.requirements === 'date'; 6504 }, 6505 // Internal only. 6506 // Returns the constraints, grouped by descending priority. 6507 // The result is thus an array of arrays of constraints. 6508 _getGroupedConstraints: function _getGroupedConstraints() { 6509 if (false === this.options.priorityEnabled) return [this.constraints]; 6510 var groupedConstraints = []; 6511 var index = {}; // Create array unique of priorities 6512 6513 for (var i = 0; i < this.constraints.length; i++) { 6514 var p = this.constraints[i].priority; 6515 if (!index[p]) groupedConstraints.push(index[p] = []); 6516 index[p].push(this.constraints[i]); 6517 } // Sort them by priority DESC 6518 6519 6520 groupedConstraints.sort(function (a, b) { 6521 return b[0].priority - a[0].priority; 6522 }); 6523 return groupedConstraints; 6524 } 6525 }; 6526 6527 var Multiple = function Multiple() { 6528 this.__class__ = 'FieldMultiple'; 6529 }; 6530 6531 Multiple.prototype = { 6532 // Add new `$element` sibling for multiple field 6533 addElement: function addElement($element) { 6534 this.$elements.push($element); 6535 return this; 6536 }, 6537 // See `Field._refreshConstraints()` 6538 _refreshConstraints: function _refreshConstraints() { 6539 var fieldConstraints; 6540 this.constraints = []; // Select multiple special treatment 6541 6542 if (this.element.nodeName === 'SELECT') { 6543 this.actualizeOptions()._bindConstraints(); 6544 6545 return this; 6546 } // Gather all constraints for each input in the multiple group 6547 6548 6549 for (var i = 0; i < this.$elements.length; i++) { 6550 // Check if element have not been dynamically removed since last binding 6551 if (!$('html').has(this.$elements[i]).length) { 6552 this.$elements.splice(i, 1); 6553 continue; 6554 } 6555 6556 fieldConstraints = this.$elements[i].data('FieldMultiple')._refreshConstraints().constraints; 6557 6558 for (var j = 0; j < fieldConstraints.length; j++) { 6559 this.addConstraint(fieldConstraints[j].name, fieldConstraints[j].requirements, fieldConstraints[j].priority, fieldConstraints[j].isDomConstraint); 6560 } 6561 } 6562 6563 return this; 6564 }, 6565 // See `Field.getValue()` 6566 getValue: function getValue() { 6567 // Value could be overriden in DOM 6568 if ('function' === typeof this.options.value) return this.options.value(this);else if ('undefined' !== typeof this.options.value) return this.options.value; // Radio input case 6569 6570 if (this.element.nodeName === 'INPUT') { 6571 var type = Utils.getType(this.element); 6572 if (type === 'radio') return this._findRelated().filter(':checked').val() || ''; // checkbox input case 6573 6574 if (type === 'checkbox') { 6575 var values = []; 6576 6577 this._findRelated().filter(':checked').each(function () { 6578 values.push($(this).val()); 6579 }); 6580 6581 return values; 6582 } 6583 } // Select multiple case 6584 6585 6586 if (this.element.nodeName === 'SELECT' && null === this.$element.val()) return []; // Default case that should never happen 6587 6588 return this.$element.val(); 6589 }, 6590 _init: function _init() { 6591 this.$elements = [this.$element]; 6592 return this; 6593 } 6594 }; 6595 6596 var Factory = function Factory(element, options, parsleyFormInstance) { 6597 this.element = element; 6598 this.$element = $(element); // If the element has already been bound, returns its saved Parsley instance 6599 6600 var savedparsleyFormInstance = this.$element.data('Parsley'); 6601 6602 if (savedparsleyFormInstance) { 6603 // If the saved instance has been bound without a Form parent and there is one given in this call, add it 6604 if ('undefined' !== typeof parsleyFormInstance && savedparsleyFormInstance.parent === window.Parsley) { 6605 savedparsleyFormInstance.parent = parsleyFormInstance; 6606 6607 savedparsleyFormInstance._resetOptions(savedparsleyFormInstance.options); 6608 } 6609 6610 if ('object' === _typeof(options)) { 6611 _extends(savedparsleyFormInstance.options, options); 6612 } 6613 6614 return savedparsleyFormInstance; 6615 } // Parsley must be instantiated with a DOM element or jQuery $element 6616 6617 6618 if (!this.$element.length) throw new Error('You must bind Parsley on an existing element.'); 6619 if ('undefined' !== typeof parsleyFormInstance && 'Form' !== parsleyFormInstance.__class__) throw new Error('Parent instance must be a Form instance'); 6620 this.parent = parsleyFormInstance || window.Parsley; 6621 return this.init(options); 6622 }; 6623 6624 Factory.prototype = { 6625 init: function init(options) { 6626 this.__class__ = 'Parsley'; 6627 this.__version__ = '2.9.2'; 6628 this.__id__ = Utils.generateID(); // Pre-compute options 6629 6630 this._resetOptions(options); // A Form instance is obviously a `<form>` element but also every node that is not an input and has the `data-parsley-validate` attribute 6631 6632 6633 if (this.element.nodeName === 'FORM' || Utils.checkAttr(this.element, this.options.namespace, 'validate') && !this.$element.is(this.options.inputs)) return this.bind('parsleyForm'); // Every other element is bound as a `Field` or `FieldMultiple` 6634 6635 return this.isMultiple() ? this.handleMultiple() : this.bind('parsleyField'); 6636 }, 6637 isMultiple: function isMultiple() { 6638 var type = Utils.getType(this.element); 6639 return type === 'radio' || type === 'checkbox' || this.element.nodeName === 'SELECT' && null !== this.element.getAttribute('multiple'); 6640 }, 6641 // Multiples fields are a real nightmare :( 6642 // Maybe some refactoring would be appreciated here... 6643 handleMultiple: function handleMultiple() { 6644 var _this = this; 6645 6646 var name; 6647 var parsleyMultipleInstance; // Handle multiple name 6648 6649 this.options.multiple = this.options.multiple || (name = this.element.getAttribute('name')) || this.element.getAttribute('id'); // Special select multiple input 6650 6651 if (this.element.nodeName === 'SELECT' && null !== this.element.getAttribute('multiple')) { 6652 this.options.multiple = this.options.multiple || this.__id__; 6653 return this.bind('parsleyFieldMultiple'); // Else for radio / checkboxes, we need a `name` or `data-parsley-multiple` to properly bind it 6654 } else if (!this.options.multiple) { 6655 Utils.warn('To be bound by Parsley, a radio, a checkbox and a multiple select input must have either a name or a multiple option.', this.$element); 6656 return this; 6657 } // Remove special chars 6658 6659 6660 this.options.multiple = this.options.multiple.replace(/(:|\.|\[|\]|\{|\}|\$)/g, ''); // Add proper `data-parsley-multiple` to siblings if we have a valid multiple name 6661 6662 if (name) { 6663 $('input[name="' + name + '"]').each(function (i, input) { 6664 var type = Utils.getType(input); 6665 if (type === 'radio' || type === 'checkbox') input.setAttribute(_this.options.namespace + 'multiple', _this.options.multiple); 6666 }); 6667 } // Check here if we don't already have a related multiple instance saved 6668 6669 6670 var $previouslyRelated = this._findRelated(); 6671 6672 for (var i = 0; i < $previouslyRelated.length; i++) { 6673 parsleyMultipleInstance = $($previouslyRelated.get(i)).data('Parsley'); 6674 6675 if ('undefined' !== typeof parsleyMultipleInstance) { 6676 if (!this.$element.data('FieldMultiple')) { 6677 parsleyMultipleInstance.addElement(this.$element); 6678 } 6679 6680 break; 6681 } 6682 } // Create a secret Field instance for every multiple field. It will be stored in `data('FieldMultiple')` 6683 // And will be useful later to access classic `Field` stuff while being in a `FieldMultiple` instance 6684 6685 6686 this.bind('parsleyField', true); 6687 return parsleyMultipleInstance || this.bind('parsleyFieldMultiple'); 6688 }, 6689 // Return proper `Form`, `Field` or `FieldMultiple` 6690 bind: function bind(type, doNotStore) { 6691 var parsleyInstance; 6692 6693 switch (type) { 6694 case 'parsleyForm': 6695 parsleyInstance = $.extend(new Form(this.element, this.domOptions, this.options), new Base(), window.ParsleyExtend)._bindFields(); 6696 break; 6697 6698 case 'parsleyField': 6699 parsleyInstance = $.extend(new Field(this.element, this.domOptions, this.options, this.parent), new Base(), window.ParsleyExtend); 6700 break; 6701 6702 case 'parsleyFieldMultiple': 6703 parsleyInstance = $.extend(new Field(this.element, this.domOptions, this.options, this.parent), new Multiple(), new Base(), window.ParsleyExtend)._init(); 6704 break; 6705 6706 default: 6707 throw new Error(type + 'is not a supported Parsley type'); 6708 } 6709 6710 if (this.options.multiple) Utils.setAttr(this.element, this.options.namespace, 'multiple', this.options.multiple); 6711 6712 if ('undefined' !== typeof doNotStore) { 6713 this.$element.data('FieldMultiple', parsleyInstance); 6714 return parsleyInstance; 6715 } // Store the freshly bound instance in a DOM element for later access using jQuery `data()` 6716 6717 6718 this.$element.data('Parsley', parsleyInstance); // Tell the world we have a new Form or Field instance! 6719 6720 parsleyInstance._actualizeTriggers(); 6721 6722 parsleyInstance._trigger('init'); 6723 6724 return parsleyInstance; 6725 } 6726 }; 6727 6728 var vernums = $.fn.jquery.split('.'); 6729 6730 if (parseInt(vernums[0]) <= 1 && parseInt(vernums[1]) < 8) { 6731 throw "The loaded version of jQuery is too old. Please upgrade to 1.8.x or better."; 6732 } 6733 6734 if (!vernums.forEach) { 6735 Utils.warn('Parsley requires ES5 to run properly. Please include https://github.com/es-shims/es5-shim'); 6736 } // Inherit `on`, `off` & `trigger` to Parsley: 6737 6738 6739 var Parsley = _extends(new Base(), { 6740 element: document, 6741 $element: $(document), 6742 actualizeOptions: null, 6743 _resetOptions: null, 6744 Factory: Factory, 6745 version: '2.9.2' 6746 }); // Supplement Field and Form with Base 6747 // This way, the constructors will have access to those methods 6748 6749 6750 _extends(Field.prototype, UI.Field, Base.prototype); 6751 6752 _extends(Form.prototype, UI.Form, Base.prototype); // Inherit actualizeOptions and _resetOptions: 6753 6754 6755 _extends(Factory.prototype, Base.prototype); // ### jQuery API 6756 // `$('.elem').parsley(options)` or `$('.elem').psly(options)` 6757 6758 6759 $.fn.parsley = $.fn.psly = function (options) { 6760 if (this.length > 1) { 6761 var instances = []; 6762 this.each(function () { 6763 instances.push($(this).parsley(options)); 6764 }); 6765 return instances; 6766 } // Return undefined if applied to non existing DOM element 6767 6768 6769 if (this.length == 0) { 6770 return; 6771 } 6772 6773 return new Factory(this[0], options); 6774 }; // ### Field and Form extension 6775 // Ensure the extension is now defined if it wasn't previously 6776 6777 6778 if ('undefined' === typeof window.ParsleyExtend) window.ParsleyExtend = {}; // ### Parsley config 6779 // Inherit from ParsleyDefault, and copy over any existing values 6780 6781 Parsley.options = _extends(Utils.objectCreate(Defaults), window.ParsleyConfig); 6782 window.ParsleyConfig = Parsley.options; // Old way of accessing global options 6783 // ### Globals 6784 6785 window.Parsley = window.psly = Parsley; 6786 Parsley.Utils = Utils; 6787 window.ParsleyUtils = {}; 6788 $.each(Utils, function (key, value) { 6789 if ('function' === typeof value) { 6790 window.ParsleyUtils[key] = function () { 6791 Utils.warnOnce('Accessing `window.ParsleyUtils` is deprecated. Use `window.Parsley.Utils` instead.'); 6792 return Utils[key].apply(Utils, arguments); 6793 }; 6794 } 6795 }); // ### Define methods that forward to the registry, and deprecate all access except through window.Parsley 6796 6797 var registry = window.Parsley._validatorRegistry = new ValidatorRegistry(window.ParsleyConfig.validators, window.ParsleyConfig.i18n); 6798 window.ParsleyValidator = {}; 6799 $.each('setLocale addCatalog addMessage addMessages getErrorMessage formatMessage addValidator updateValidator removeValidator hasValidator'.split(' '), function (i, method) { 6800 window.Parsley[method] = function () { 6801 return registry[method].apply(registry, arguments); 6802 }; 6803 6804 window.ParsleyValidator[method] = function () { 6805 var _window$Parsley; 6806 6807 Utils.warnOnce("Accessing the method '".concat(method, "' through Validator is deprecated. Simply call 'window.Parsley.").concat(method, "(...)'")); 6808 return (_window$Parsley = window.Parsley)[method].apply(_window$Parsley, arguments); 6809 }; 6810 }); // ### UI 6811 // Deprecated global object 6812 6813 window.Parsley.UI = UI; 6814 window.ParsleyUI = { 6815 removeError: function removeError(instance, name, doNotUpdateClass) { 6816 var updateClass = true !== doNotUpdateClass; 6817 Utils.warnOnce("Accessing UI is deprecated. Call 'removeError' on the instance directly. Please comment in issue 1073 as to your need to call this method."); 6818 return instance.removeError(name, { 6819 updateClass: updateClass 6820 }); 6821 }, 6822 getErrorsMessages: function getErrorsMessages(instance) { 6823 Utils.warnOnce("Accessing UI is deprecated. Call 'getErrorsMessages' on the instance directly."); 6824 return instance.getErrorsMessages(); 6825 } 6826 }; 6827 $.each('addError updateError'.split(' '), function (i, method) { 6828 window.ParsleyUI[method] = function (instance, name, message, assert, doNotUpdateClass) { 6829 var updateClass = true !== doNotUpdateClass; 6830 Utils.warnOnce("Accessing UI is deprecated. Call '".concat(method, "' on the instance directly. Please comment in issue 1073 as to your need to call this method.")); 6831 return instance[method](name, { 6832 message: message, 6833 assert: assert, 6834 updateClass: updateClass 6835 }); 6836 }; 6837 }); // ### PARSLEY auto-binding 6838 // Prevent it by setting `ParsleyConfig.autoBind` to `false` 6839 6840 if (false !== window.ParsleyConfig.autoBind) { 6841 $(function () { 6842 // Works only on `data-parsley-validate`. 6843 if ($('[data-parsley-validate]').length) $('[data-parsley-validate]').parsley(); 6844 }); 6845 } 6846 6847 var o = $({}); 6848 6849 var deprecated = function deprecated() { 6850 Utils.warnOnce("Parsley's pubsub module is deprecated; use the 'on' and 'off' methods on parsley instances or window.Parsley"); 6851 }; // Returns an event handler that calls `fn` with the arguments it expects 6852 6853 6854 function adapt(fn, context) { 6855 // Store to allow unbinding 6856 if (!fn.parsleyAdaptedCallback) { 6857 fn.parsleyAdaptedCallback = function () { 6858 var args = Array.prototype.slice.call(arguments, 0); 6859 args.unshift(this); 6860 fn.apply(context || o, args); 6861 }; 6862 } 6863 6864 return fn.parsleyAdaptedCallback; 6865 } 6866 6867 var eventPrefix = 'parsley:'; // Converts 'parsley:form:validate' into 'form:validate' 6868 6869 function eventName(name) { 6870 if (name.lastIndexOf(eventPrefix, 0) === 0) return name.substr(eventPrefix.length); 6871 return name; 6872 } // $.listen is deprecated. Use Parsley.on instead. 6873 6874 6875 $.listen = function (name, callback) { 6876 var context; 6877 deprecated(); 6878 6879 if ('object' === _typeof(arguments[1]) && 'function' === typeof arguments[2]) { 6880 context = arguments[1]; 6881 callback = arguments[2]; 6882 } 6883 6884 if ('function' !== typeof callback) throw new Error('Wrong parameters'); 6885 window.Parsley.on(eventName(name), adapt(callback, context)); 6886 }; 6887 6888 $.listenTo = function (instance, name, fn) { 6889 deprecated(); 6890 if (!(instance instanceof Field) && !(instance instanceof Form)) throw new Error('Must give Parsley instance'); 6891 if ('string' !== typeof name || 'function' !== typeof fn) throw new Error('Wrong parameters'); 6892 instance.on(eventName(name), adapt(fn)); 6893 }; 6894 6895 $.unsubscribe = function (name, fn) { 6896 deprecated(); 6897 if ('string' !== typeof name || 'function' !== typeof fn) throw new Error('Wrong arguments'); 6898 window.Parsley.off(eventName(name), fn.parsleyAdaptedCallback); 6899 }; 6900 6901 $.unsubscribeTo = function (instance, name) { 6902 deprecated(); 6903 if (!(instance instanceof Field) && !(instance instanceof Form)) throw new Error('Must give Parsley instance'); 6904 instance.off(eventName(name)); 6905 }; 6906 6907 $.unsubscribeAll = function (name) { 6908 deprecated(); 6909 window.Parsley.off(eventName(name)); 6910 $('form,input,textarea,select').each(function () { 6911 var instance = $(this).data('Parsley'); 6912 6913 if (instance) { 6914 instance.off(eventName(name)); 6915 } 6916 }); 6917 }; // $.emit is deprecated. Use jQuery events instead. 6918 6919 6920 $.emit = function (name, instance) { 6921 var _instance; 6922 6923 deprecated(); 6924 var instanceGiven = instance instanceof Field || instance instanceof Form; 6925 var args = Array.prototype.slice.call(arguments, instanceGiven ? 2 : 1); 6926 args.unshift(eventName(name)); 6927 6928 if (!instanceGiven) { 6929 instance = window.Parsley; 6930 } 6931 6932 (_instance = instance).trigger.apply(_instance, _toConsumableArray(args)); 6933 }; 6934 6935 $.extend(true, Parsley, { 6936 asyncValidators: { 6937 'default': { 6938 fn: function fn(xhr) { 6939 // By default, only status 2xx are deemed successful. 6940 // Note: we use status instead of state() because responses with status 200 6941 // but invalid messages (e.g. an empty body for content type set to JSON) will 6942 // result in state() === 'rejected'. 6943 return xhr.status >= 200 && xhr.status < 300; 6944 }, 6945 url: false 6946 }, 6947 reverse: { 6948 fn: function fn(xhr) { 6949 // If reverse option is set, a failing ajax request is considered successful 6950 return xhr.status < 200 || xhr.status >= 300; 6951 }, 6952 url: false 6953 } 6954 }, 6955 addAsyncValidator: function addAsyncValidator(name, fn, url, options) { 6956 Parsley.asyncValidators[name] = { 6957 fn: fn, 6958 url: url || false, 6959 options: options || {} 6960 }; 6961 return this; 6962 } 6963 }); 6964 Parsley.addValidator('remote', { 6965 requirementType: { 6966 '': 'string', 6967 'validator': 'string', 6968 'reverse': 'boolean', 6969 'options': 'object' 6970 }, 6971 validateString: function validateString(value, url, options, instance) { 6972 var data = {}; 6973 var ajaxOptions; 6974 var csr; 6975 var validator = options.validator || (true === options.reverse ? 'reverse' : 'default'); 6976 if ('undefined' === typeof Parsley.asyncValidators[validator]) throw new Error('Calling an undefined async validator: `' + validator + '`'); 6977 url = Parsley.asyncValidators[validator].url || url; // Fill current value 6978 6979 if (url.indexOf('{value}') > -1) { 6980 url = url.replace('{value}', encodeURIComponent(value)); 6981 } else { 6982 data[instance.element.getAttribute('name') || instance.element.getAttribute('id')] = value; 6983 } // Merge options passed in from the function with the ones in the attribute 6984 6985 6986 var remoteOptions = $.extend(true, options.options || {}, Parsley.asyncValidators[validator].options); // All `$.ajax(options)` could be overridden or extended directly from DOM in `data-parsley-remote-options` 6987 6988 ajaxOptions = $.extend(true, {}, { 6989 url: url, 6990 data: data, 6991 type: 'GET' 6992 }, remoteOptions); // Generate store key based on ajax options 6993 6994 instance.trigger('field:ajaxoptions', instance, ajaxOptions); 6995 csr = $.param(ajaxOptions); // Initialise querry cache 6996 6997 if ('undefined' === typeof Parsley._remoteCache) Parsley._remoteCache = {}; // Try to retrieve stored xhr 6998 6999 var xhr = Parsley._remoteCache[csr] = Parsley._remoteCache[csr] || $.ajax(ajaxOptions); 7000 7001 var handleXhr = function handleXhr() { 7002 var result = Parsley.asyncValidators[validator].fn.call(instance, xhr, url, options); 7003 if (!result) // Map falsy results to rejected promise 7004 result = $.Deferred().reject(); 7005 return $.when(result); 7006 }; 7007 7008 return xhr.then(handleXhr, handleXhr); 7009 }, 7010 priority: -1 7011 }); 7012 Parsley.on('form:submit', function () { 7013 Parsley._remoteCache = {}; 7014 }); 7015 7016 Base.prototype.addAsyncValidator = function () { 7017 Utils.warnOnce('Accessing the method `addAsyncValidator` through an instance is deprecated. Simply call `Parsley.addAsyncValidator(...)`'); 7018 return Parsley.addAsyncValidator.apply(Parsley, arguments); 7019 }; 7020 7021 // This is included with the Parsley library itself, 7022 Parsley.addMessages('en', { 7023 defaultMessage: "This value seems to be invalid.", 7024 type: { 7025 email: "This value should be a valid email.", 7026 url: "This value should be a valid url.", 7027 number: "This value should be a valid number.", 7028 integer: "This value should be a valid integer.", 7029 digits: "This value should be digits.", 7030 alphanum: "This value should be alphanumeric." 7031 }, 7032 notblank: "This value should not be blank.", 7033 required: "This value is required.", 7034 pattern: "This value seems to be invalid.", 7035 min: "This value should be greater than or equal to %s.", 7036 max: "This value should be lower than or equal to %s.", 7037 range: "This value should be between %s and %s.", 7038 minlength: "This value is too short. It should have %s characters or more.", 7039 maxlength: "This value is too long. It should have %s characters or fewer.", 7040 length: "This value length is invalid. It should be between %s and %s characters long.", 7041 mincheck: "You must select at least %s choices.", 7042 maxcheck: "You must select %s choices or fewer.", 7043 check: "You must select between %s and %s choices.", 7044 equalto: "This value should be the same.", 7045 euvatin: "It's not a valid VAT Identification Number." 7046 }); 7047 Parsley.setLocale('en'); 7048 7049 function InputEvent() { 7050 var _this = this; 7051 7052 var globals = window || global; // Slightly odd way construct our object. This way methods are force bound. 7053 // Used to test for duplicate library. 7054 7055 _extends(this, { 7056 // For browsers that do not support isTrusted, assumes event is native. 7057 isNativeEvent: function isNativeEvent(evt) { 7058 return evt.originalEvent && evt.originalEvent.isTrusted !== false; 7059 }, 7060 fakeInputEvent: function fakeInputEvent(evt) { 7061 if (_this.isNativeEvent(evt)) { 7062 $(evt.target).trigger('input'); 7063 } 7064 }, 7065 misbehaves: function misbehaves(evt) { 7066 if (_this.isNativeEvent(evt)) { 7067 _this.behavesOk(evt); 7068 7069 $(document).on('change.inputevent', evt.data.selector, _this.fakeInputEvent); 7070 7071 _this.fakeInputEvent(evt); 7072 } 7073 }, 7074 behavesOk: function behavesOk(evt) { 7075 if (_this.isNativeEvent(evt)) { 7076 $(document) // Simply unbinds the testing handler 7077 .off('input.inputevent', evt.data.selector, _this.behavesOk).off('change.inputevent', evt.data.selector, _this.misbehaves); 7078 } 7079 }, 7080 // Bind the testing handlers 7081 install: function install() { 7082 if (globals.inputEventPatched) { 7083 return; 7084 } 7085 7086 globals.inputEventPatched = '0.0.3'; 7087 7088 for (var _i = 0, _arr = ['select', 'input[type="checkbox"]', 'input[type="radio"]', 'input[type="file"]']; _i < _arr.length; _i++) { 7089 var selector = _arr[_i]; 7090 $(document).on('input.inputevent', selector, { 7091 selector: selector 7092 }, _this.behavesOk).on('change.inputevent', selector, { 7093 selector: selector 7094 }, _this.misbehaves); 7095 } 7096 }, 7097 uninstall: function uninstall() { 7098 delete globals.inputEventPatched; 7099 $(document).off('.inputevent'); 7100 } 7101 });
vendor: 4,668 bytes, lines 7102-7213
7102 } 7103 var inputevent = new InputEvent(); 7104 7105 inputevent.install(); 7106 7107 return Parsley; 7108 7109}))); 7110//# sourceMappingURL=parsley.js.map 7111 7112/** 7113 * FingerprintJS v5.1.0 - Copyright (c) FingerprintJS, Inc, 2026 (https://fingerprint.com) 7114 * 7115 * Licensed under MIT License 7116 * 7117 * Copyright (c) 2025 FingerprintJS, Inc 7118 * 7119 * Permission is hereby granted, free of charge, to any person obtaining a copy 7120 * of this software and associated documentation files (the "Software"), to deal 7121 * in the Software without restriction, including without limitation the rights 7122 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7123 * copies of the Software, and to permit persons to whom the Software is 7124 * furnished to do so, subject to the following conditions: 7125 * 7126 * The above copyright notice and this permission notice shall be included in all 7127 * copies or substantial portions of the Software. 7128 * 7129 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 7130 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 7131 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 7132 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 7133 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 7134 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 7135 * SOFTWARE. 7136 */ 7137 7138var FingerprintJS = (function (exports) { 7139 'use strict'; 7140 7141 var version = "5.1.0"; 7142 7143 function wait(durationMs, resolveWith) { 7144 return new Promise((resolve) => setTimeout(resolve, durationMs, resolveWith)); 7145 } 7146 /** 7147 * Allows asynchronous actions and microtasks to happen. 7148 */ 7149 function releaseEventLoop() { 7150 // Don't use setTimeout because Chrome throttles it in some cases causing very long agent execution: 7151 // https://stackoverflow.com/a/6032591/1118709 7152 // https://github.com/chromium/chromium/commit/0295dd09496330f3a9103ef7e543fa9b6050409b 7153 // Reusing a MessageChannel object gives no noticeable benefits 7154 return new Promise((resolve) => { 7155 const channel = new MessageChannel(); 7156 channel.port1.onmessage = () => resolve(); 7157 channel.port2.postMessage(null); 7158 }); 7159 } 7160 function requestIdleCallbackIfAvailable(fallbackTimeout, deadlineTimeout = Infinity) { 7161 const { requestIdleCallback } = window; 7162 if (requestIdleCallback) { 7163 // The function `requestIdleCallback` loses the binding to `window` here. 7164 // `globalThis` isn't always equal `window` (see https://github.com/fingerprintjs/fingerprintjs/issues/683). 7165 // Therefore, an error can occur. `call(window,` prevents the error. 7166 return new Promise((resolve) => requestIdleCallback.call(window, () => resolve(), { timeout: deadlineTimeout })); 7167 } 7168 else { 7169 return wait(Math.min(fallbackTimeout, deadlineTimeout)); 7170 } 7171 } 7172 function isPromise(value) { 7173 return !!value && typeof value.then === 'function'; 7174 } 7175 /** 7176 * Calls a maybe asynchronous function without creating microtasks when the function is synchronous. 7177 * Catches errors in both cases. 7178 * 7179 * If just you run a code like this: 7180 * ``` 7181 * console.time('Action duration') 7182 * await action() 7183 * console.timeEnd('Action duration') 7184 * ``` 7185 * The synchronous function time can be measured incorrectly because another microtask may run before the `await` 7186 * returns the control back to the code. 7187 */ 7188 function awaitIfAsync(action, callback) { 7189 try { 7190 const returnedValue = action(); 7191 if (isPromise(returnedValue)) { 7192 returnedValue.then((result) => callback(true, result), (error) => callback(false, error)); 7193 } 7194 else { 7195 callback(true, returnedValue); 7196 } 7197 } 7198 catch (error) { 7199 callback(false, error); 7200 } 7201 } 7202 /** 7203 * If you run many synchronous tasks without using this function, the JS main loop will be busy and asynchronous tasks 7204 * (e.g. completing a network request, rendering the page) won't be able to happen. 7205 * This function allows running many synchronous tasks such way that asynchronous tasks can run too in background. 7206 */ 7207 async function mapWithBreaks(items, callback, loopReleaseInterval = 16) { 7208 const results = Array(items.length); 7209 let lastLoopReleaseTime = Date.now(); 7210 for (let i = 0; i < items.length; ++i) { 7211 results[i] = callback(items[i], i); 7212 const now = Date.now(); 7213 if (now >= lastLoopReleaseTime + loopReleaseInterval) {
vendor: 72,509 bytes, lines 7214-8982
7214 lastLoopReleaseTime = now; 7215 await releaseEventLoop(); 7216 } 7217 } 7218 return results; 7219 } 7220 /** 7221 * Makes the given promise never emit an unhandled promise rejection console warning. 7222 * The promise will still pass errors to the next promises. 7223 * Returns the input promise for convenience. 7224 * 7225 * Otherwise, promise emits a console warning unless it has a `catch` listener. 7226 */ 7227 function suppressUnhandledRejectionWarning(promise) { 7228 promise.then(undefined, () => undefined); 7229 return promise; 7230 } 7231 7232 /* 7233 * This file contains functions to work with pure data only (no browser features, DOM, side effects, etc). 7234 */ 7235 /** 7236 * Does the same as Array.prototype.includes but has better typing 7237 */ 7238 function includes(haystack, needle) { 7239 for (let i = 0, l = haystack.length; i < l; ++i) { 7240 if (haystack[i] === needle) { 7241 return true; 7242 } 7243 } 7244 return false; 7245 } 7246 /** 7247 * Like `!includes()` but with proper typing 7248 */ 7249 function excludes(haystack, needle) { 7250 return !includes(haystack, needle); 7251 } 7252 /** 7253 * Be careful, NaN can return 7254 */ 7255 function toInt(value) { 7256 return parseInt(value); 7257 } 7258 /** 7259 * Be careful, NaN can return 7260 */ 7261 function toFloat(value) { 7262 return parseFloat(value); 7263 } 7264 function replaceNaN(value, replacement) { 7265 return typeof value === 'number' && isNaN(value) ? replacement : value; 7266 } 7267 function countTruthy(values) { 7268 return values.reduce((sum, value) => sum + (value ? 1 : 0), 0); 7269 } 7270 function round(value, base = 1) { 7271 if (Math.abs(base) >= 1) { 7272 return Math.round(value / base) * base; 7273 } 7274 else { 7275 // Sometimes when a number is multiplied by a small number, precision is lost, 7276 // for example 1234 * 0.0001 === 0.12340000000000001, and it's more precise divide: 1234 / (1 / 0.0001) === 0.1234. 7277 const counterBase = 1 / base; 7278 return Math.round(value * counterBase) / counterBase; 7279 } 7280 } 7281 /** 7282 * Parses a CSS selector into tag name with HTML attributes. 7283 * Only single element selector are supported (without operators like space, +, >, etc). 7284 * 7285 * Multiple values can be returned for each attribute. You decide how to handle them. 7286 */ 7287 function parseSimpleCssSelector(selector) { 7288 var _a, _b; 7289 const errorMessage = `Unexpected syntax '${selector}'`; 7290 const tagMatch = /^\s*([a-z-]*)(.*)$/i.exec(selector); 7291 const tag = tagMatch[1] || undefined; 7292 const attributes = {}; 7293 const partsRegex = /([.:#][\w-]+|\[.+?\])/gi; 7294 const addAttribute = (name, value) => { 7295 attributes[name] = attributes[name] || []; 7296 attributes[name].push(value); 7297 }; 7298 for (;;) { 7299 const match = partsRegex.exec(tagMatch[2]); 7300 if (!match) { 7301 break; 7302 } 7303 const part = match[0]; 7304 switch (part[0]) { 7305 case '.': 7306 addAttribute('class', part.slice(1)); 7307 break; 7308 case '#': 7309 addAttribute('id', part.slice(1)); 7310 break; 7311 case '[': { 7312 const attributeMatch = /^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(part); 7313 if (attributeMatch) { 7314 addAttribute(attributeMatch[1], (_b = (_a = attributeMatch[4]) !== null && _a !== void 0 ? _a : attributeMatch[5]) !== null && _b !== void 0 ? _b : ''); 7315 } 7316 else { 7317 throw new Error(errorMessage); 7318 } 7319 break; 7320 } 7321 default: 7322 throw new Error(errorMessage); 7323 } 7324 } 7325 return [tag, attributes]; 7326 } 7327 /** 7328 * Converts a string to UTF8 bytes 7329 */ 7330 function getUTF8Bytes(input) { 7331 // Benchmark: https://jsbench.me/b6klaaxgwq/1 7332 // If you want to just count bytes, see solutions at https://jsbench.me/ehklab415e/1 7333 const result = new Uint8Array(input.length); 7334 for (let i = 0; i < input.length; i++) { 7335 // `charCode` is faster than encoding, so we prefer that when it's possible 7336 const charCode = input.charCodeAt(i); 7337 // In case of non-ASCII symbols we use proper encoding 7338 if (charCode > 127) { 7339 return new TextEncoder().encode(input); 7340 } 7341 result[i] = charCode; 7342 } 7343 return result; 7344 } 7345 7346 /* 7347 * Based on https://github.com/karanlyons/murmurHash3.js/blob/a33d0723127e2e5415056c455f8aed2451ace208/murmurHash3.js 7348 */ 7349 /** 7350 * Adds two 64-bit values (provided as tuples of 32-bit values) 7351 * and updates (mutates) first value to write the result 7352 */ 7353 function x64Add(m, n) { 7354 const m0 = m[0] >>> 16, m1 = m[0] & 0xffff, m2 = m[1] >>> 16, m3 = m[1] & 0xffff; 7355 const n0 = n[0] >>> 16, n1 = n[0] & 0xffff, n2 = n[1] >>> 16, n3 = n[1] & 0xffff; 7356 let o0 = 0, o1 = 0, o2 = 0, o3 = 0; 7357 o3 += m3 + n3; 7358 o2 += o3 >>> 16; 7359 o3 &= 0xffff; 7360 o2 += m2 + n2; 7361 o1 += o2 >>> 16; 7362 o2 &= 0xffff; 7363 o1 += m1 + n1; 7364 o0 += o1 >>> 16; 7365 o1 &= 0xffff; 7366 o0 += m0 + n0; 7367 o0 &= 0xffff; 7368 m[0] = (o0 << 16) | o1; 7369 m[1] = (o2 << 16) | o3; 7370 } 7371 /** 7372 * Multiplies two 64-bit values (provided as tuples of 32-bit values) 7373 * and updates (mutates) first value to write the result 7374 */ 7375 function x64Multiply(m, n) { 7376 const m0 = m[0] >>> 16, m1 = m[0] & 0xffff, m2 = m[1] >>> 16, m3 = m[1] & 0xffff; 7377 const n0 = n[0] >>> 16, n1 = n[0] & 0xffff, n2 = n[1] >>> 16, n3 = n[1] & 0xffff; 7378 let o0 = 0, o1 = 0, o2 = 0, o3 = 0; 7379 o3 += m3 * n3; 7380 o2 += o3 >>> 16; 7381 o3 &= 0xffff; 7382 o2 += m2 * n3; 7383 o1 += o2 >>> 16; 7384 o2 &= 0xffff; 7385 o2 += m3 * n2; 7386 o1 += o2 >>> 16; 7387 o2 &= 0xffff; 7388 o1 += m1 * n3; 7389 o0 += o1 >>> 16; 7390 o1 &= 0xffff; 7391 o1 += m2 * n2; 7392 o0 += o1 >>> 16; 7393 o1 &= 0xffff; 7394 o1 += m3 * n1; 7395 o0 += o1 >>> 16; 7396 o1 &= 0xffff; 7397 o0 += m0 * n3 + m1 * n2 + m2 * n1 + m3 * n0; 7398 o0 &= 0xffff; 7399 m[0] = (o0 << 16) | o1; 7400 m[1] = (o2 << 16) | o3; 7401 } 7402 /** 7403 * Provides left rotation of the given int64 value (provided as tuple of two int32) 7404 * by given number of bits. Result is written back to the value 7405 */ 7406 function x64Rotl(m, bits) { 7407 const m0 = m[0]; 7408 bits %= 64; 7409 if (bits === 32) { 7410 m[0] = m[1]; 7411 m[1] = m0; 7412 } 7413 else if (bits < 32) { 7414 m[0] = (m0 << bits) | (m[1] >>> (32 - bits)); 7415 m[1] = (m[1] << bits) | (m0 >>> (32 - bits)); 7416 } 7417 else { 7418 bits -= 32; 7419 m[0] = (m[1] << bits) | (m0 >>> (32 - bits)); 7420 m[1] = (m0 << bits) | (m[1] >>> (32 - bits)); 7421 } 7422 } 7423 /** 7424 * Provides a left shift of the given int32 value (provided as tuple of [0, int32]) 7425 * by given number of bits. Result is written back to the value 7426 */ 7427 function x64LeftShift(m, bits) { 7428 bits %= 64; 7429 if (bits === 0) { 7430 return; 7431 } 7432 else if (bits < 32) { 7433 m[0] = m[1] >>> (32 - bits); 7434 m[1] = m[1] << bits; 7435 } 7436 else { 7437 m[0] = m[1] << (bits - 32); 7438 m[1] = 0; 7439 } 7440 } 7441 /** 7442 * Provides a XOR of the given int64 values(provided as tuple of two int32). 7443 * Result is written back to the first value 7444 */ 7445 function x64Xor(m, n) { 7446 m[0] ^= n[0]; 7447 m[1] ^= n[1]; 7448 } 7449 const F1 = [0xff51afd7, 0xed558ccd]; 7450 const F2 = [0xc4ceb9fe, 0x1a85ec53]; 7451 /** 7452 * Calculates murmurHash3's final x64 mix of that block and writes result back to the input value. 7453 * (`[0, h[0] >>> 1]` is a 33 bit unsigned right shift. This is the 7454 * only place where we need to right shift 64bit ints.) 7455 */ 7456 function x64Fmix(h) { 7457 const shifted = [0, h[0] >>> 1]; 7458 x64Xor(h, shifted); 7459 x64Multiply(h, F1); 7460 shifted[1] = h[0] >>> 1; 7461 x64Xor(h, shifted); 7462 x64Multiply(h, F2); 7463 shifted[1] = h[0] >>> 1; 7464 x64Xor(h, shifted); 7465 } 7466 const C1 = [0x87c37b91, 0x114253d5]; 7467 const C2 = [0x4cf5ad43, 0x2745937f]; 7468 const M$1 = [0, 5]; 7469 const N1 = [0, 0x52dce729]; 7470 const N2 = [0, 0x38495ab5]; 7471 /** 7472 * Given a string and an optional seed as an int, returns a 128 bit 7473 * hash using the x64 flavor of MurmurHash3, as an unsigned hex. 7474 * All internal functions mutates passed value to achieve minimal memory allocations and GC load 7475 * 7476 * Benchmark https://jsbench.me/p4lkpaoabi/1 7477 */ 7478 function x64hash128(input, seed) { 7479 const key = getUTF8Bytes(input); 7480 seed = seed || 0; 7481 const length = [0, key.length]; 7482 const remainder = length[1] % 16; 7483 const bytes = length[1] - remainder; 7484 const h1 = [0, seed]; 7485 const h2 = [0, seed]; 7486 const k1 = [0, 0]; 7487 const k2 = [0, 0]; 7488 let i; 7489 for (i = 0; i < bytes; i = i + 16) { 7490 k1[0] = key[i + 4] | (key[i + 5] << 8) | (key[i + 6] << 16) | (key[i + 7] << 24); 7491 k1[1] = key[i] | (key[i + 1] << 8) | (key[i + 2] << 16) | (key[i + 3] << 24); 7492 k2[0] = key[i + 12] | (key[i + 13] << 8) | (key[i + 14] << 16) | (key[i + 15] << 24); 7493 k2[1] = key[i + 8] | (key[i + 9] << 8) | (key[i + 10] << 16) | (key[i + 11] << 24); 7494 x64Multiply(k1, C1); 7495 x64Rotl(k1, 31); 7496 x64Multiply(k1, C2); 7497 x64Xor(h1, k1); 7498 x64Rotl(h1, 27); 7499 x64Add(h1, h2); 7500 x64Multiply(h1, M$1); 7501 x64Add(h1, N1); 7502 x64Multiply(k2, C2); 7503 x64Rotl(k2, 33); 7504 x64Multiply(k2, C1); 7505 x64Xor(h2, k2); 7506 x64Rotl(h2, 31); 7507 x64Add(h2, h1); 7508 x64Multiply(h2, M$1); 7509 x64Add(h2, N2); 7510 } 7511 k1[0] = 0; 7512 k1[1] = 0; 7513 k2[0] = 0; 7514 k2[1] = 0; 7515 const val = [0, 0]; 7516 switch (remainder) { 7517 case 15: 7518 val[1] = key[i + 14]; 7519 x64LeftShift(val, 48); 7520 x64Xor(k2, val); 7521 // fallthrough 7522 case 14: 7523 val[1] = key[i + 13]; 7524 x64LeftShift(val, 40); 7525 x64Xor(k2, val); 7526 // fallthrough 7527 case 13: 7528 val[1] = key[i + 12]; 7529 x64LeftShift(val, 32); 7530 x64Xor(k2, val); 7531 // fallthrough 7532 case 12: 7533 val[1] = key[i + 11]; 7534 x64LeftShift(val, 24); 7535 x64Xor(k2, val); 7536 // fallthrough 7537 case 11: 7538 val[1] = key[i + 10]; 7539 x64LeftShift(val, 16); 7540 x64Xor(k2, val); 7541 // fallthrough 7542 case 10: 7543 val[1] = key[i + 9]; 7544 x64LeftShift(val, 8); 7545 x64Xor(k2, val); 7546 // fallthrough 7547 case 9: 7548 val[1] = key[i + 8]; 7549 x64Xor(k2, val); 7550 x64Multiply(k2, C2); 7551 x64Rotl(k2, 33); 7552 x64Multiply(k2, C1); 7553 x64Xor(h2, k2); 7554 // fallthrough 7555 case 8: 7556 val[1] = key[i + 7]; 7557 x64LeftShift(val, 56); 7558 x64Xor(k1, val); 7559 // fallthrough 7560 case 7: 7561 val[1] = key[i + 6]; 7562 x64LeftShift(val, 48); 7563 x64Xor(k1, val); 7564 // fallthrough 7565 case 6: 7566 val[1] = key[i + 5]; 7567 x64LeftShift(val, 40); 7568 x64Xor(k1, val); 7569 // fallthrough 7570 case 5: 7571 val[1] = key[i + 4]; 7572 x64LeftShift(val, 32); 7573 x64Xor(k1, val); 7574 // fallthrough 7575 case 4: 7576 val[1] = key[i + 3]; 7577 x64LeftShift(val, 24); 7578 x64Xor(k1, val); 7579 // fallthrough 7580 case 3: 7581 val[1] = key[i + 2]; 7582 x64LeftShift(val, 16); 7583 x64Xor(k1, val); 7584 // fallthrough 7585 case 2: 7586 val[1] = key[i + 1]; 7587 x64LeftShift(val, 8); 7588 x64Xor(k1, val); 7589 // fallthrough 7590 case 1: 7591 val[1] = key[i]; 7592 x64Xor(k1, val); 7593 x64Multiply(k1, C1); 7594 x64Rotl(k1, 31); 7595 x64Multiply(k1, C2); 7596 x64Xor(h1, k1); 7597 // fallthrough 7598 } 7599 x64Xor(h1, length); 7600 x64Xor(h2, length); 7601 x64Add(h1, h2); 7602 x64Add(h2, h1); 7603 x64Fmix(h1); 7604 x64Fmix(h2); 7605 x64Add(h1, h2); 7606 x64Add(h2, h1); 7607 return (('00000000' + (h1[0] >>> 0).toString(16)).slice(-8) + 7608 ('00000000' + (h1[1] >>> 0).toString(16)).slice(-8) + 7609 ('00000000' + (h2[0] >>> 0).toString(16)).slice(-8) + 7610 ('00000000' + (h2[1] >>> 0).toString(16)).slice(-8)); 7611 } 7612 7613 /** 7614 * Converts an error object to a plain object that can be used with `JSON.stringify`. 7615 * If you just run `JSON.stringify(error)`, you'll get `'{}'`. 7616 */ 7617 function errorToObject(error) { 7618 var _a; 7619 return { 7620 name: error.name, 7621 message: error.message, 7622 stack: (_a = error.stack) === null || _a === void 0 ? void 0 : _a.split('\n'), 7623 // The fields are not enumerable, so TS is wrong saying that they will be overridden 7624 ...error, 7625 }; 7626 } 7627 function isFunctionNative(func) { 7628 return /^function\s.*?\{\s*\[native code]\s*}$/.test(String(func)); 7629 } 7630 7631 /* eslint-disable @typescript-eslint/no-explicit-any */ 7632 function isFinalResultLoaded(loadResult) { 7633 return typeof loadResult !== 'function'; 7634 } 7635 /** 7636 * Loads the given entropy source. Returns a function that gets an entropy component from the source. 7637 * 7638 * The result is returned synchronously to prevent `loadSources` from 7639 * waiting for one source to load before getting the components from the other sources. 7640 */ 7641 function loadSource(source, sourceOptions) { 7642 const sourceLoadPromise = suppressUnhandledRejectionWarning(new Promise((resolveLoad) => { 7643 const loadStartTime = Date.now(); 7644 // `awaitIfAsync` is used instead of just `await` in order to measure the duration of synchronous sources 7645 // correctly (other microtasks won't affect the duration). 7646 awaitIfAsync(source.bind(null, sourceOptions), (...loadArgs) => { 7647 const loadDuration = Date.now() - loadStartTime; 7648 // Source loading failed 7649 if (!loadArgs[0]) { 7650 return resolveLoad(() => ({ error: loadArgs[1], duration: loadDuration })); 7651 } 7652 const loadResult = loadArgs[1]; 7653 // Source loaded with the final result 7654 if (isFinalResultLoaded(loadResult)) { 7655 return resolveLoad(() => ({ value: loadResult, duration: loadDuration })); 7656 } 7657 // Source loaded with "get" stage 7658 resolveLoad(() => new Promise((resolveGet) => { 7659 const getStartTime = Date.now(); 7660 awaitIfAsync(loadResult, (...getArgs) => { 7661 const duration = loadDuration + Date.now() - getStartTime; 7662 // Source getting failed 7663 if (!getArgs[0]) { 7664 return resolveGet({ error: getArgs[1], duration }); 7665 } 7666 // Source getting succeeded 7667 resolveGet({ value: getArgs[1], duration }); 7668 }); 7669 })); 7670 }); 7671 })); 7672 return function getComponent() { 7673 return sourceLoadPromise.then((finalizeSource) => finalizeSource()); 7674 }; 7675 } 7676 /** 7677 * Loads the given entropy sources. Returns a function that collects the entropy components. 7678 * 7679 * The result is returned synchronously in order to allow start getting the components 7680 * before the sources are loaded completely. 7681 * 7682 * Warning for package users: 7683 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7684 */ 7685 function loadSources(sources, sourceOptions, excludeSources, loopReleaseInterval) { 7686 const includedSources = Object.keys(sources).filter((sourceKey) => excludes(excludeSources, sourceKey)); 7687 // Using `mapWithBreaks` allows asynchronous sources to complete between synchronous sources 7688 // and measure the duration correctly 7689 const sourceGettersPromise = suppressUnhandledRejectionWarning(mapWithBreaks(includedSources, (sourceKey) => loadSource(sources[sourceKey], sourceOptions), loopReleaseInterval)); 7690 return async function getComponents() { 7691 const sourceGetters = await sourceGettersPromise; 7692 const componentPromises = await mapWithBreaks(sourceGetters, (sourceGetter) => suppressUnhandledRejectionWarning(sourceGetter()), loopReleaseInterval); 7693 const componentArray = await Promise.all(componentPromises); 7694 // Keeping the component keys order the same as the source keys order 7695 const components = {}; 7696 for (let index = 0; index < includedSources.length; ++index) { 7697 components[includedSources[index]] = componentArray[index]; 7698 } 7699 return components; 7700 }; 7701 } 7702 /** 7703 * Modifies an entropy source by transforming its returned value with the given function. 7704 * Keeps the source properties: sync/async, 1/2 stages. 7705 * 7706 * Warning for package users: 7707 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7708 */ 7709 function transformSource(source, transformValue) { 7710 const transformLoadResult = (loadResult) => { 7711 if (isFinalResultLoaded(loadResult)) { 7712 return transformValue(loadResult); 7713 } 7714 return () => { 7715 const getResult = loadResult(); 7716 if (isPromise(getResult)) { 7717 return getResult.then(transformValue); 7718 } 7719 return transformValue(getResult); 7720 }; 7721 }; 7722 return (options) => { 7723 const loadResult = source(options); 7724 if (isPromise(loadResult)) { 7725 return loadResult.then(transformLoadResult); 7726 } 7727 return transformLoadResult(loadResult); 7728 }; 7729 } 7730 7731 /* 7732 * Functions to help with features that vary through browsers 7733 */ 7734 /** 7735 * Checks whether the browser is based on Trident (the Internet Explorer engine) without using user-agent. 7736 * 7737 * Warning for package users: 7738 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7739 */ 7740 function isTrident() { 7741 const w = window; 7742 const n = navigator; 7743 // The properties are checked to be in IE 10, IE 11 and not to be in other browsers in October 2020 7744 return (countTruthy([ 7745 'MSCSSMatrix' in w, 7746 'msSetImmediate' in w, 7747 'msIndexedDB' in w, 7748 'msMaxTouchPoints' in n, 7749 'msPointerEnabled' in n, 7750 ]) >= 4); 7751 } 7752 /** 7753 * Checks whether the browser is based on EdgeHTML (the pre-Chromium Edge engine) without using user-agent. 7754 * 7755 * Warning for package users: 7756 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7757 */ 7758 function isEdgeHTML() { 7759 // Based on research in October 2020 7760 const w = window; 7761 const n = navigator; 7762 return (countTruthy(['msWriteProfilerMark' in w, 'MSStream' in w, 'msLaunchUri' in n, 'msSaveBlob' in n]) >= 3 && 7763 !isTrident()); 7764 } 7765 /** 7766 * Checks whether the browser is based on Chromium without using user-agent. 7767 * 7768 * Warning for package users: 7769 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7770 */ 7771 function isChromium() { 7772 // Based on research in October 2020. Tested to detect Chromium 42-86. 7773 const w = window; 7774 const n = navigator; 7775 return (countTruthy([ 7776 'webkitPersistentStorage' in n, 7777 'webkitTemporaryStorage' in n, 7778 (n.vendor || '').indexOf('Google') === 0, 7779 'webkitResolveLocalFileSystemURL' in w, 7780 'BatteryManager' in w, 7781 'webkitMediaStream' in w, 7782 'webkitSpeechGrammar' in w, 7783 ]) >= 5); 7784 } 7785 /** 7786 * Checks whether the browser is based on mobile or desktop Safari without using user-agent. 7787 * All iOS browsers use WebKit (the Safari engine). 7788 * 7789 * Warning for package users: 7790 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7791 */ 7792 function isWebKit() { 7793 // Based on research in August 2024 7794 const w = window; 7795 const n = navigator; 7796 return (countTruthy([ 7797 'ApplePayError' in w, 7798 'CSSPrimitiveValue' in w, 7799 'Counter' in w, 7800 n.vendor.indexOf('Apple') === 0, 7801 'RGBColor' in w, 7802 'WebKitMediaKeys' in w, 7803 ]) >= 4); 7804 } 7805 /** 7806 * Checks whether this WebKit browser is a desktop browser. 7807 * It doesn't check that the browser is based on WebKit, there is a separate function for this. 7808 * 7809 * Warning for package users: 7810 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7811 */ 7812 function isDesktopWebKit() { 7813 // Checked in Safari and DuckDuckGo 7814 const w = window; 7815 const { HTMLElement, Document } = w; 7816 return (countTruthy([ 7817 'safari' in w, 7818 !('ongestureend' in w), 7819 !('TouchEvent' in w), 7820 !('orientation' in w), 7821 HTMLElement && !('autocapitalize' in HTMLElement.prototype), 7822 Document && 'pointerLockElement' in Document.prototype, 7823 ]) >= 4); 7824 } 7825 /** 7826 * Checks whether this WebKit browser is Safari. 7827 * It doesn't check that the browser is based on WebKit, there is a separate function for this. 7828 * 7829 * Warning! The function works properly only for Safari version 15.4 and newer. 7830 */ 7831 function isSafariWebKit() { 7832 // Checked in Safari, Chrome, Firefox, Yandex, UC Browser, Opera, Edge and DuckDuckGo. 7833 // iOS Safari and Chrome were checked on iOS 11-18. DuckDuckGo was checked on iOS 17-18 and macOS 14-15. 7834 // Desktop Safari versions 12-18 were checked. 7835 // The other browsers were checked on iOS 17 and 18; there was no chance to check them on the other OS versions. 7836 const w = window; 7837 return ( 7838 // Filters-out Chrome, Yandex, DuckDuckGo (macOS and iOS), Edge 7839 isFunctionNative(w.print) && 7840 // Doesn't work in Safari < 15.4 7841 String(w.browser) === '[object WebPageNamespace]'); 7842 } 7843 /** 7844 * Checks whether the browser is based on Gecko (Firefox engine) without using user-agent. 7845 * 7846 * Warning for package users: 7847 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 7848 */ 7849 function isGecko() { 7850 var _a, _b; 7851 const w = window; 7852 // Based on research in September 2020 7853 return (countTruthy([ 7854 'buildID' in navigator, 7855 'MozAppearance' in ((_b = (_a = document.documentElement) === null || _a === void 0 ? void 0 : _a.style) !== null && _b !== void 0 ? _b : {}), 7856 'onmozfullscreenchange' in w, 7857 'mozInnerScreenX' in w, 7858 'CSSMozDocumentRule' in w, 7859 'CanvasCaptureMediaStream' in w, 7860 ]) >= 4); 7861 } 7862 /** 7863 * Checks whether the browser is based on Gecko version â¥120 (Firefox â¥120) without using user-agent. 7864 * It doesn't check that the browser is based on Gecko; there is a separate function for this. 7865 * 7866 * @see https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/120 Firefox 120 release notes 7867 */ 7868 function isGecko120OrNewer() { 7869 // Checked in Firefox 119 vs. Firefox 120+ 7870 const w = window; 7871 const n = navigator; 7872 const { CSS } = w; 7873 // We use a threshold of 3 out of 4 because `globalPrivacyControl` was added in Firefox 120 on desktop, 7874 // but only in Firefox 122 on mobile. Using >= 3 ensures detection works on both platforms for Firefox 120+. 7875 // @see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/globalPrivacyControl#browser_compatibility 7876 return (countTruthy([ 7877 // User Activation API - added in Firefox 120 7878 'userActivation' in n, 7879 // CSS light-dark() function - added in Firefox 120 7880 CSS.supports('color', 'light-dark(#000, #fff)'), 7881 // CSS lh unit - added in Firefox 120 7882 CSS.supports('height', '1lh'), 7883 // Global Privacy Control - added in Firefox 120 (desktop) / Firefox 122 (mobile) 7884 'globalPrivacyControl' in n, 7885 ]) >= 3); 7886 } 7887 /** 7888 * Checks whether the browser is based on Gecko version â¥143 (Firefox â¥143) without using user-agent. 7889 * It doesn't check that the browser is based on Gecko; there is a separate function for this. 7890 * 7891 * Firefox 143 shipped Phase 2 fingerprinting protections (screen resolution, processor count, touch points) 7892 * in Private Browsing and ETP Strict mode. 7893 * 7894 * @see https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/143 Firefox 143 release notes 7895 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1978414 Bug that shipped these protections 7896 */ 7897 function isGecko143OrNewer() { 7898 // Checked in Firefox 142 vs. Firefox 143+ 7899 const { CSS } = window; 7900 return (countTruthy([ 7901 // CSS ::details-content pseudo-element - added in Firefox 143 7902 CSS.supports('selector(::details-content)'), 7903 // CSS ::before::marker nested pseudo-element - added in Firefox 143 7904 CSS.supports('selector(::before::marker)'), 7905 // CSS ::after::marker nested pseudo-element - added in Firefox 143 7906 CSS.supports('selector(::after::marker)'), 7907 // CompositionEvent.locale property - removed in Firefox 143 (Bug 1700969) 7908 !('locale' in CompositionEvent.prototype), 7909 ]) >= 3); 7910 } 7911 /** 7912 * Checks whether the browser is based on Chromium version â¥86 without using user-agent. 7913 * It doesn't check that the browser is based on Chromium, there is a separate function for this. 7914 */ 7915 function isChromium86OrNewer() { 7916 // Checked in Chrome 85 vs Chrome 86 both on desktop and Android. Checked in macOS Chrome 128, Android Chrome 127. 7917 const w = window; 7918 return (countTruthy([ 7919 !('MediaSettingsRange' in w), 7920 'RTCEncodedAudioFrame' in w, 7921 '' + w.Intl === '[object Intl]', 7922 '' + w.Reflect === '[object Reflect]', 7923 ]) >= 3); 7924 } 7925 /** 7926 * Checks whether the browser is based on Chromium version â¥122 without using user-agent. 7927 * It doesn't check that the browser is based on Chromium, there is a separate function for this. 7928 */ 7929 function isChromium122OrNewer() { 7930 // Checked in Chrome 121 vs Chrome 122 and 129 both on desktop and Android 7931 const w = window; 7932 const { URLPattern } = w; 7933 return (countTruthy([ 7934 'union' in Set.prototype, 7935 'Iterator' in w, 7936 URLPattern && 'hasRegExpGroups' in URLPattern.prototype, 7937 'RGB8' in WebGLRenderingContext.prototype, 7938 ]) >= 3); 7939 } 7940 /** 7941 * Checks whether the browser is based on WebKit version â¥606 (Safari â¥12) without using user-agent. 7942 * It doesn't check that the browser is based on WebKit, there is a separate function for this. 7943 * 7944 * @see https://en.wikipedia.org/wiki/Safari_version_history#Release_history Safari-WebKit versions map 7945 */ 7946 function isWebKit606OrNewer() { 7947 // Checked in Safari 9â18 7948 const w = window; 7949 return (countTruthy([ 7950 'DOMRectList' in w, 7951 'RTCPeerConnectionIceEvent' in w, 7952 'SVGGeometryElement' in w, 7953 'ontransitioncancel' in w, 7954 ]) >= 3); 7955 } 7956 /** 7957 * Checks whether the browser is based on WebKit version â¥616 (Safari â¥17) without using user-agent. 7958 * It doesn't check that the browser is based on WebKit, there is a separate function for this. 7959 * 7960 * @see https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes Safari 17 release notes 7961 * @see https://tauri.app/v1/references/webview-versions/#webkit-versions-in-safari Safari-WebKit versions map 7962 */ 7963 function isWebKit616OrNewer() { 7964 const w = window; 7965 const n = navigator; 7966 const { CSS, HTMLButtonElement } = w; 7967 return (countTruthy([ 7968 !('getStorageUpdates' in n), 7969 HTMLButtonElement && 'popover' in HTMLButtonElement.prototype, 7970 'CSSCounterStyleRule' in w, 7971 CSS.supports('font-size-adjust: ex-height 0.5'), 7972 CSS.supports('text-transform: full-width'), 7973 ]) >= 4); 7974 } 7975 /** 7976 * Checks whether the device is an iPad. 7977 * It doesn't check that the engine is WebKit and that the WebKit isn't desktop. 7978 */ 7979 function isIPad() { 7980 // Checked on: 7981 // Safari on iPadOS (both mobile and desktop modes): 8, 11-18 7982 // Chrome on iPadOS (both mobile and desktop modes): 11-18 7983 // Safari on iOS (both mobile and desktop modes): 9-18 7984 // Chrome on iOS (both mobile and desktop modes): 9-18 7985 // Before iOS 13. Safari tampers the value in "request desktop site" mode since iOS 13. 7986 if (navigator.platform === 'iPad') { 7987 return true; 7988 } 7989 const s = screen; 7990 const screenRatio = s.width / s.height; 7991 return (countTruthy([ 7992 // Since iOS 13. Doesn't work in Chrome on iPadOS <15, but works in desktop mode. 7993 'MediaSource' in window, 7994 // Since iOS 12. Doesn't work in Chrome on iPadOS. 7995 !!Element.prototype.webkitRequestFullscreen, 7996 // iPhone 4S that runs iOS 9 matches this, but it is not supported 7997 // Doesn't work in incognito mode of Safari â¥17 with split screen because of tracking prevention 7998 screenRatio > 0.65 && screenRatio < 1.53, 7999 ]) >= 2); 8000 } 8001 /** 8002 * Warning for package users: 8003 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8004 */ 8005 function getFullscreenElement() { 8006 const d = document; 8007 return d.fullscreenElement || d.msFullscreenElement || d.mozFullScreenElement || d.webkitFullscreenElement || null; 8008 } 8009 function exitFullscreen() { 8010 const d = document; 8011 // `call` is required because the function throws an error without a proper "this" context 8012 return (d.exitFullscreen || d.msExitFullscreen || d.mozCancelFullScreen || d.webkitExitFullscreen).call(d); 8013 } 8014 /** 8015 * Checks whether the device runs on Android without using user-agent. 8016 * 8017 * Warning for package users: 8018 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8019 */ 8020 function isAndroid() { 8021 const isItChromium = isChromium(); 8022 const isItGecko = isGecko(); 8023 const w = window; 8024 const n = navigator; 8025 const c = 'connection'; 8026 // Chrome removes all words "Android" from `navigator` when desktop version is requested 8027 // Firefox keeps "Android" in `navigator.appVersion` when desktop version is requested 8028 if (isItChromium) { 8029 return (countTruthy([ 8030 !('SharedWorker' in w), 8031 // `typechange` is deprecated, but it's still present on Android (tested on Chrome Mobile 117) 8032 // Removal proposal https://bugs.chromium.org/p/chromium/issues/detail?id=699892 8033 // Note: this expression returns true on ChromeOS, so additional detectors are required to avoid false-positives 8034 n[c] && 'ontypechange' in n[c], 8035 !('sinkId' in new Audio()), 8036 ]) >= 2); 8037 } 8038 else if (isItGecko) { 8039 return countTruthy(['onorientationchange' in w, 'orientation' in w, /android/i.test(n.appVersion)]) >= 2; 8040 } 8041 else { 8042 // Only 2 browser engines are presented on Android. 8043 // Actually, there is also Android 4.1 browser, but it's not worth detecting it at the moment. 8044 return false; 8045 } 8046 } 8047 /** 8048 * Checks whether the browser is Samsung Internet without using user-agent. 8049 * It doesn't check that the browser is based on Chromium, please use `isChromium` before using this function. 8050 * 8051 * Warning for package users: 8052 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8053 */ 8054 function isSamsungInternet() { 8055 // Checked in Samsung Internet 21, 25 and 27 8056 const n = navigator; 8057 const w = window; 8058 const audioPrototype = Audio.prototype; 8059 const { visualViewport } = w; 8060 return (countTruthy([ 8061 'srLatency' in audioPrototype, 8062 'srChannelCount' in audioPrototype, 8063 'devicePosture' in n, 8064 visualViewport && 'segments' in visualViewport, 8065 'getTextInformation' in Image.prototype, // Not available in Samsung Internet 21 8066 ]) >= 3); 8067 } 8068 8069 /** 8070 * A deep description: https://fingerprint.com/blog/audio-fingerprinting/ 8071 * Inspired by and based on https://github.com/cozylife/audio-fingerprint 8072 * 8073 * A version of the entropy source with stabilization to make it suitable for static fingerprinting. 8074 * Audio signal is noised in private mode of Safari 17, so audio fingerprinting is skipped in Safari 17. 8075 */ 8076 function getAudioFingerprint() { 8077 if (doesBrowserPerformAntifingerprinting()) { 8078 return -4 /* SpecialFingerprint.KnownForAntifingerprinting */; 8079 } 8080 return getUnstableAudioFingerprint(); 8081 } 8082 /** 8083 * A version of the entropy source without stabilization. 8084 * 8085 * Warning for package users: 8086 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8087 */ 8088 function getUnstableAudioFingerprint() { 8089 const w = window; 8090 const AudioContext = w.OfflineAudioContext || w.webkitOfflineAudioContext; 8091 if (!AudioContext) { 8092 return -2 /* SpecialFingerprint.NotSupported */; 8093 } 8094 // In some browsers, audio context always stays suspended unless the context is started in response to a user action 8095 // (e.g. a click or a tap). It prevents audio fingerprint from being taken at an arbitrary moment of time. 8096 // Such browsers are old and unpopular, so the audio fingerprinting is just skipped in them. 8097 // See a similar case explanation at https://stackoverflow.com/questions/46363048/onaudioprocess-not-called-on-ios11#46534088 8098 if (doesBrowserSuspendAudioContext()) { 8099 return -1 /* SpecialFingerprint.KnownForSuspending */; 8100 } 8101 const hashFromIndex = 4500; 8102 const hashToIndex = 5000; 8103 const context = new AudioContext(1, hashToIndex, 44100); 8104 const oscillator = context.createOscillator(); 8105 oscillator.type = 'triangle'; 8106 oscillator.frequency.value = 10000; 8107 const compressor = context.createDynamicsCompressor(); 8108 compressor.threshold.value = -50; 8109 compressor.knee.value = 40; 8110 compressor.ratio.value = 12; 8111 compressor.attack.value = 0; 8112 compressor.release.value = 0.25; 8113 oscillator.connect(compressor); 8114 compressor.connect(context.destination); 8115 oscillator.start(0); 8116 const [renderPromise, finishRendering] = startRenderingAudio(context); 8117 // Suppresses the console error message in case when the fingerprint fails before requested 8118 const fingerprintPromise = suppressUnhandledRejectionWarning(renderPromise.then((buffer) => getHash(buffer.getChannelData(0).subarray(hashFromIndex)), (error) => { 8119 if (error.name === "timeout" /* InnerErrorName.Timeout */ || error.name === "suspended" /* InnerErrorName.Suspended */) { 8120 return -3 /* SpecialFingerprint.Timeout */; 8121 } 8122 throw error; 8123 })); 8124 return () => { 8125 finishRendering(); 8126 return fingerprintPromise; 8127 }; 8128 } 8129 /** 8130 * Checks if the current browser is known for always suspending audio context 8131 */ 8132 function doesBrowserSuspendAudioContext() { 8133 // Mobile Safari 11 and older 8134 return isWebKit() && !isDesktopWebKit() && !isWebKit606OrNewer(); 8135 } 8136 /** 8137 * Checks if the current browser is known for applying anti-fingerprinting measures in all or some critical modes 8138 */ 8139 function doesBrowserPerformAntifingerprinting() { 8140 return ( 8141 // Safari â¥17 8142 (isWebKit() && isWebKit616OrNewer() && isSafariWebKit()) || 8143 // Samsung Internet â¥26 8144 (isChromium() && isSamsungInternet() && isChromium122OrNewer())); 8145 } 8146 /** 8147 * Starts rendering the audio context. 8148 * When the returned function is called, the render process starts finishing. 8149 */ 8150 function startRenderingAudio(context) { 8151 const renderTryMaxCount = 3; 8152 const renderRetryDelay = 500; 8153 const runningMaxAwaitTime = 500; 8154 const runningSufficientTime = 5000; 8155 let finalize = () => undefined; 8156 const resultPromise = new Promise((resolve, reject) => { 8157 let isFinalized = false; 8158 let renderTryCount = 0; 8159 let startedRunningAt = 0; 8160 context.oncomplete = (event) => resolve(event.renderedBuffer); 8161 const startRunningTimeout = () => { 8162 setTimeout(() => reject(makeInnerError("timeout" /* InnerErrorName.Timeout */)), Math.min(runningMaxAwaitTime, startedRunningAt + runningSufficientTime - Date.now())); 8163 }; 8164 const tryRender = () => { 8165 try { 8166 const renderingPromise = context.startRendering(); 8167 // `context.startRendering` has two APIs: Promise and callback, we check that it's really a promise just in case 8168 if (isPromise(renderingPromise)) { 8169 // Suppresses all unhandled rejections in case of scheduled redundant retries after successful rendering 8170 suppressUnhandledRejectionWarning(renderingPromise); 8171 } 8172 switch (context.state) { 8173 case 'running': 8174 startedRunningAt = Date.now(); 8175 if (isFinalized) { 8176 startRunningTimeout(); 8177 } 8178 break; 8179 // Sometimes the audio context doesn't start after calling `startRendering` (in addition to the cases where 8180 // audio context doesn't start at all). A known case is starting an audio context when the browser tab is in 8181 // background on iPhone. Retries usually help in this case. 8182 case 'suspended': 8183 // The audio context can reject starting until the tab is in foreground. Long fingerprint duration 8184 // in background isn't a problem, therefore the retry attempts don't count in background. It can lead to 8185 // a situation when a fingerprint takes very long time and finishes successfully. FYI, the audio context 8186 // can be suspended when `document.hidden === false` and start running after a retry. 8187 if (!document.hidden) { 8188 renderTryCount++; 8189 } 8190 if (isFinalized && renderTryCount >= renderTryMaxCount) { 8191 reject(makeInnerError("suspended" /* InnerErrorName.Suspended */)); 8192 } 8193 else { 8194 setTimeout(tryRender, renderRetryDelay); 8195 } 8196 break; 8197 } 8198 } 8199 catch (error) { 8200 reject(error); 8201 } 8202 }; 8203 tryRender(); 8204 finalize = () => { 8205 if (!isFinalized) { 8206 isFinalized = true; 8207 if (startedRunningAt > 0) { 8208 startRunningTimeout(); 8209 } 8210 } 8211 }; 8212 }); 8213 return [resultPromise, finalize]; 8214 } 8215 function getHash(signal) { 8216 let hash = 0; 8217 for (let i = 0; i < signal.length; ++i) { 8218 hash += Math.abs(signal[i]); 8219 } 8220 return hash; 8221 } 8222 function makeInnerError(name) { 8223 const error = new Error(name); 8224 error.name = name; 8225 return error; 8226 } 8227 8228 /** 8229 * Creates and keeps an invisible iframe while the given function runs. 8230 * The given function is called when the iframe is loaded and has a body. 8231 * The iframe allows to measure DOM sizes inside itself. 8232 * 8233 * Notice: passing an initial HTML code doesn't work in IE. 8234 * 8235 * Warning for package users: 8236 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8237 */ 8238 async function withIframe(action, initialHtml, domPollInterval = 50) { 8239 var _a, _b, _c; 8240 const d = document; 8241 // document.body can be null while the page is loading 8242 while (!d.body) { 8243 await wait(domPollInterval); 8244 } 8245 const iframe = d.createElement('iframe'); 8246 try { 8247 await new Promise((_resolve, _reject) => { 8248 let isComplete = false; 8249 const resolve = () => { 8250 isComplete = true; 8251 _resolve(); 8252 }; 8253 const reject = (error) => { 8254 isComplete = true; 8255 _reject(error); 8256 }; 8257 iframe.onload = resolve; 8258 iframe.onerror = reject; 8259 const { style } = iframe; 8260 style.setProperty('display', 'block', 'important'); // Required for browsers to calculate the layout 8261 style.position = 'absolute'; 8262 style.top = '0'; 8263 style.left = '0'; 8264 style.visibility = 'hidden'; 8265 if (initialHtml && 'srcdoc' in iframe) { 8266 iframe.srcdoc = initialHtml; 8267 } 8268 else { 8269 iframe.src = 'about:blank'; 8270 } 8271 d.body.appendChild(iframe); 8272 // WebKit in WeChat doesn't fire the iframe's `onload` for some reason. 8273 // This code checks for the loading state manually. 8274 // See https://github.com/fingerprintjs/fingerprintjs/issues/645 8275 const checkReadyState = () => { 8276 var _a, _b; 8277 // The ready state may never become 'complete' in Firefox despite the 'load' event being fired. 8278 // So an infinite setTimeout loop can happen without this check. 8279 // See https://github.com/fingerprintjs/fingerprintjs/pull/716#issuecomment-986898796 8280 if (isComplete) { 8281 return; 8282 } 8283 // Make sure iframe.contentWindow and iframe.contentWindow.document are both loaded 8284 // The contentWindow.document can miss in JSDOM (https://github.com/jsdom/jsdom). 8285 if (((_b = (_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document) === null || _b === void 0 ? void 0 : _b.readyState) === 'complete') { 8286 resolve(); 8287 } 8288 else { 8289 setTimeout(checkReadyState, 10); 8290 } 8291 }; 8292 checkReadyState(); 8293 }); 8294 while (!((_b = (_a = iframe.contentWindow) === null || _a === void 0 ? void 0 : _a.document) === null || _b === void 0 ? void 0 : _b.body)) { 8295 await wait(domPollInterval); 8296 } 8297 return await action(iframe, iframe.contentWindow); 8298 } 8299 finally { 8300 (_c = iframe.parentNode) === null || _c === void 0 ? void 0 : _c.removeChild(iframe); 8301 } 8302 } 8303 /** 8304 * Creates a DOM element that matches the given selector. 8305 * Only single element selector are supported (without operators like space, +, >, etc). 8306 */ 8307 function selectorToElement(selector) { 8308 const [tag, attributes] = parseSimpleCssSelector(selector); 8309 const element = document.createElement(tag !== null && tag !== void 0 ? tag : 'div'); 8310 for (const name of Object.keys(attributes)) { 8311 const value = attributes[name].join(' '); 8312 // Changing the `style` attribute can cause a CSP error, therefore we change the `style.cssText` property. 8313 // https://github.com/fingerprintjs/fingerprintjs/issues/733 8314 if (name === 'style') { 8315 addStyleString(element.style, value); 8316 } 8317 else { 8318 element.setAttribute(name, value); 8319 } 8320 } 8321 return element; 8322 } 8323 /** 8324 * Adds CSS styles from a string in such a way that doesn't trigger a CSP warning (unsafe-inline or unsafe-eval) 8325 */ 8326 function addStyleString(style, source) { 8327 // We don't use `style.cssText` because browsers must block it when no `unsafe-eval` CSP is presented: https://csplite.com/csp145/#w3c_note 8328 // Even though the browsers ignore this standard, we don't use `cssText` just in case. 8329 for (const property of source.split(';')) { 8330 const match = /^\s*([\w-]+)\s*:\s*(.+?)(\s*!([\w-]+))?\s*$/.exec(property); 8331 if (match) { 8332 const [, name, value, , priority] = match; 8333 style.setProperty(name, value, priority || ''); // The last argument can't be undefined in IE11 8334 } 8335 } 8336 } 8337 /** 8338 * Returns true if the code runs in an iframe, and any parent page's origin doesn't match the current origin 8339 */ 8340 function isAnyParentCrossOrigin() { 8341 let currentWindow = window; 8342 for (;;) { 8343 const parentWindow = currentWindow.parent; 8344 if (!parentWindow || parentWindow === currentWindow) { 8345 return false; // The top page is reached 8346 } 8347 try { 8348 if (parentWindow.location.origin !== currentWindow.location.origin) { 8349 return true; 8350 } 8351 } 8352 catch (error) { 8353 // The error is thrown when `origin` is accessed on `parentWindow.location` when the parent is cross-origin 8354 if (error instanceof Error && error.name === 'SecurityError') { 8355 return true; 8356 } 8357 throw error; 8358 } 8359 currentWindow = parentWindow; 8360 } 8361 } 8362 8363 // We use m or w because these two characters take up the maximum width. 8364 // And we use a LLi so that the same matching fonts can get separated. 8365 const testString = 'mmMwWLliI0O&1'; 8366 // We test using 48px font size, we may use any size. I guess larger the better. 8367 const textSize = '48px'; 8368 // A font will be compared against all the three default fonts. 8369 // And if for any default fonts it doesn't match, then that font is available. 8370 const baseFonts = ['monospace', 'sans-serif', 'serif']; 8371 const fontList = [ 8372 // This is android-specific font from "Roboto" family 8373 'sans-serif-thin', 8374 'ARNO PRO', 8375 'Agency FB', 8376 'Arabic Typesetting', 8377 'Arial Unicode MS', 8378 'AvantGarde Bk BT', 8379 'BankGothic Md BT', 8380 'Batang', 8381 'Bitstream Vera Sans Mono', 8382 'Calibri', 8383 'Century', 8384 'Century Gothic', 8385 'Clarendon', 8386 'EUROSTILE', 8387 'Franklin Gothic', 8388 'Futura Bk BT', 8389 'Futura Md BT', 8390 'GOTHAM', 8391 'Gill Sans', 8392 'HELV', 8393 'Haettenschweiler', 8394 'Helvetica Neue', 8395 'Humanst521 BT', 8396 'Leelawadee', 8397 'Letter Gothic', 8398 'Levenim MT', 8399 'Lucida Bright', 8400 'Lucida Sans', 8401 'Menlo', 8402 'MS Mincho', 8403 'MS Outlook', 8404 'MS Reference Specialty', 8405 'MS UI Gothic', 8406 'MT Extra', 8407 'MYRIAD PRO', 8408 'Marlett', 8409 'Meiryo UI', 8410 'Microsoft Uighur', 8411 'Minion Pro', 8412 'Monotype Corsiva', 8413 'PMingLiU', 8414 'Pristina', 8415 'SCRIPTINA', 8416 'Segoe UI Light', 8417 'Serifa', 8418 'SimHei', 8419 'Small Fonts', 8420 'Staccato222 BT', 8421 'TRAJAN PRO', 8422 'Univers CE 55 Medium', 8423 'Vrinda', 8424 'ZWAdobeF', 8425 ]; 8426 // kudos to http://www.lalit.org/lab/javascript-css-font-detect/ 8427 function getFonts() { 8428 // Running the script in an iframe makes it not affect the page look and not be affected by the page CSS. See: 8429 // https://github.com/fingerprintjs/fingerprintjs/issues/592 8430 // https://github.com/fingerprintjs/fingerprintjs/issues/628 8431 return withIframe(async (_, { document }) => { 8432 const holder = document.body; 8433 holder.style.fontSize = textSize; 8434 // div to load spans for the default fonts and the fonts to detect 8435 const spansContainer = document.createElement('div'); 8436 spansContainer.style.setProperty('visibility', 'hidden', 'important'); 8437 const defaultWidth = {}; 8438 const defaultHeight = {}; 8439 // creates a span where the fonts will be loaded 8440 const createSpan = (fontFamily) => { 8441 const span = document.createElement('span'); 8442 const { style } = span; 8443 style.position = 'absolute'; 8444 style.top = '0'; 8445 style.left = '0'; 8446 style.fontFamily = fontFamily; 8447 span.textContent = testString; 8448 spansContainer.appendChild(span); 8449 return span; 8450 }; 8451 // creates a span and load the font to detect and a base font for fallback 8452 const createSpanWithFonts = (fontToDetect, baseFont) => { 8453 return createSpan(`'${fontToDetect}',${baseFont}`); 8454 }; 8455 // creates spans for the base fonts and adds them to baseFontsDiv 8456 const initializeBaseFontsSpans = () => { 8457 return baseFonts.map(createSpan); 8458 }; 8459 // creates spans for the fonts to detect and adds them to fontsDiv 8460 const initializeFontsSpans = () => { 8461 // Stores {fontName : [spans for that font]} 8462 const spans = {}; 8463 for (const font of fontList) { 8464 spans[font] = baseFonts.map((baseFont) => createSpanWithFonts(font, baseFont)); 8465 } 8466 return spans; 8467 }; 8468 // checks if a font is available 8469 const isFontAvailable = (fontSpans) => { 8470 return baseFonts.some((baseFont, baseFontIndex) => fontSpans[baseFontIndex].offsetWidth !== defaultWidth[baseFont] || 8471 fontSpans[baseFontIndex].offsetHeight !== defaultHeight[baseFont]); 8472 }; 8473 // create spans for base fonts 8474 const baseFontsSpans = initializeBaseFontsSpans(); 8475 // create spans for fonts to detect 8476 const fontsSpans = initializeFontsSpans(); 8477 // add all the spans to the DOM 8478 holder.appendChild(spansContainer); 8479 // get the default width for the three base fonts 8480 for (let index = 0; index < baseFonts.length; index++) { 8481 defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth; // width for the default font 8482 defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight; // height for the default font 8483 } 8484 // check available fonts 8485 return fontList.filter((font) => isFontAvailable(fontsSpans[font])); 8486 }); 8487 } 8488 8489 function getPlugins() { 8490 const rawPlugins = navigator.plugins; 8491 if (!rawPlugins) { 8492 return undefined; 8493 } 8494 const plugins = []; 8495 // Safari 10 doesn't support iterating navigator.plugins with for...of 8496 for (let i = 0; i < rawPlugins.length; ++i) { 8497 const plugin = rawPlugins[i]; 8498 if (!plugin) { 8499 continue; 8500 } 8501 const mimeTypes = []; 8502 for (let j = 0; j < plugin.length; ++j) { 8503 const mimeType = plugin[j]; 8504 mimeTypes.push({ 8505 type: mimeType.type, 8506 suffixes: mimeType.suffixes, 8507 }); 8508 } 8509 plugins.push({ 8510 name: plugin.name, 8511 description: plugin.description, 8512 mimeTypes, 8513 }); 8514 } 8515 return plugins; 8516 } 8517 8518 /** 8519 * A version of the entropy source with stabilization to make it suitable for static fingerprinting. 8520 * 8521 * Canvas image is noised in private mode of Safari 17, so image rendering is skipped in Safari 17. 8522 * Firefox 120+ randomizes canvas data in private browsing and strict ETP mode, 8523 * so image rendering is skipped in Firefox 120+. 8524 * 8525 * @see https://www.browserleaks.com/canvas#how-does-it-work 8526 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1816189 Firefox canvas randomization 8527 */ 8528 function getCanvasFingerprint() { 8529 return getUnstableCanvasFingerprint(doesBrowserPerformAntiFingerprinting()); 8530 } 8531 /** 8532 * A version of the entropy source without stabilization. 8533 * 8534 * Warning for package users: 8535 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8536 */ 8537 function getUnstableCanvasFingerprint(skipImages) { 8538 let winding = false; 8539 let geometry; 8540 let text; 8541 const [canvas, context] = makeCanvasContext(); 8542 if (!isSupported(canvas, context)) { 8543 geometry = text = "unsupported" /* ImageStatus.Unsupported */; 8544 } 8545 else { 8546 winding = doesSupportWinding(context); 8547 if (skipImages) { 8548 geometry = text = "skipped" /* ImageStatus.Skipped */; 8549 } 8550 else { 8551 [geometry, text] = renderImages(canvas, context); 8552 } 8553 } 8554 return { winding, geometry, text }; 8555 } 8556 function makeCanvasContext() { 8557 const canvas = document.createElement('canvas'); 8558 canvas.width = 1; 8559 canvas.height = 1; 8560 return [canvas, canvas.getContext('2d')]; 8561 } 8562 function isSupported(canvas, context) { 8563 return !!(context && canvas.toDataURL); 8564 } 8565 function doesSupportWinding(context) { 8566 // https://web.archive.org/web/20170825024655/http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/ 8567 // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/canvas/winding.js 8568 context.rect(0, 0, 10, 10); 8569 context.rect(2, 2, 6, 6); 8570 return !context.isPointInPath(5, 5, 'evenodd'); 8571 } 8572 function renderImages(canvas, context) { 8573 renderTextImage(canvas, context); 8574 const textImage1 = canvasToString(canvas); 8575 const textImage2 = canvasToString(canvas); // It's slightly faster to double-encode the text image 8576 // Some browsers add a noise to the canvas: https://github.com/fingerprintjs/fingerprintjs/issues/791 8577 // The canvas is excluded from the fingerprint in this case 8578 if (textImage1 !== textImage2) { 8579 return ["unstable" /* ImageStatus.Unstable */, "unstable" /* ImageStatus.Unstable */]; 8580 } 8581 // Text is unstable: 8582 // https://github.com/fingerprintjs/fingerprintjs/issues/583 8583 // https://github.com/fingerprintjs/fingerprintjs/issues/103 8584 // Therefore it's extracted into a separate image. 8585 renderGeometryImage(canvas, context); 8586 const geometryImage = canvasToString(canvas); 8587 return [geometryImage, textImage1]; 8588 } 8589 function renderTextImage(canvas, context) { 8590 // Resizing the canvas cleans it 8591 canvas.width = 240; 8592 canvas.height = 60; 8593 context.textBaseline = 'alphabetic'; 8594 context.fillStyle = '#f60'; 8595 context.fillRect(100, 1, 62, 20); 8596 context.fillStyle = '#069'; 8597 // It's important to use explicit built-in fonts in order to exclude the affect of font preferences 8598 // (there is a separate entropy source for them). 8599 context.font = '11pt "Times New Roman"'; 8600 // The choice of emojis has a gigantic impact on rendering performance (especially in FF). 8601 // Some newer emojis cause it to slow down 50-200 times. 8602 // There must be no text to the right of the emoji, see https://github.com/fingerprintjs/fingerprintjs/issues/574 8603 // A bare emoji shouldn't be used because the canvas will change depending on the script encoding: 8604 // https://github.com/fingerprintjs/fingerprintjs/issues/66 8605 // Escape sequence shouldn't be used too because Terser will turn it into a bare unicode. 8606 const printedText = `Cwm fjordbank gly ${String.fromCharCode(55357, 56835) /* ð */}`; 8607 context.fillText(printedText, 2, 15); 8608 context.fillStyle = 'rgba(102, 204, 0, 0.2)'; 8609 context.font = '18pt Arial'; 8610 context.fillText(printedText, 4, 45); 8611 } 8612 function renderGeometryImage(canvas, context) { 8613 // Resizing the canvas cleans it 8614 canvas.width = 122; 8615 canvas.height = 110; 8616 // Canvas blending 8617 // https://web.archive.org/web/20170826194121/http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/ 8618 // http://jsfiddle.net/NDYV8/16/ 8619 context.globalCompositeOperation = 'multiply'; 8620 for (const [color, x, y] of [ 8621 ['#f2f', 40, 40], 8622 ['#2ff', 80, 40], 8623 ['#ff2', 60, 80], 8624 ]) { 8625 context.fillStyle = color; 8626 context.beginPath(); 8627 context.arc(x, y, 40, 0, Math.PI * 2, true); 8628 context.closePath(); 8629 context.fill(); 8630 } 8631 // Canvas winding 8632 // https://web.archive.org/web/20130913061632/http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/ 8633 // http://jsfiddle.net/NDYV8/19/ 8634 context.fillStyle = '#f9c'; 8635 context.arc(60, 60, 60, 0, Math.PI * 2, true); 8636 context.arc(60, 60, 20, 0, Math.PI * 2, true); 8637 context.fill('evenodd'); 8638 } 8639 function canvasToString(canvas) { 8640 return canvas.toDataURL(); 8641 } 8642 /** 8643 * Checks if the current browser is known for applying anti-fingerprinting measures in all or some critical modes: 8644 * - Safari 17+: noises canvas image in private mode 8645 * - Firefox 120+: randomizes canvas data in private browsing and strict ETP mode (CanvasRandomization) 8646 * 8647 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1816189 Firefox canvas randomization 8648 */ 8649 function doesBrowserPerformAntiFingerprinting() { 8650 const isSafari17OrAbove = isWebKit() && isWebKit616OrNewer() && isSafariWebKit(); 8651 const isFirefox120OrAbove = isGecko() && isGecko120OrNewer(); 8652 return isSafari17OrAbove || isFirefox120OrAbove; 8653 } 8654 8655 /** 8656 * This is a crude and primitive touch screen detection. It's not possible to currently reliably detect the availability 8657 * of a touch screen with a JS, without actually subscribing to a touch event. 8658 * 8659 * @see http://www.stucox.com/blog/you-cant-detect-a-touchscreen/ 8660 * @see https://github.com/Modernizr/Modernizr/issues/548 8661 */ 8662 function getTouchSupport() { 8663 const n = navigator; 8664 let maxTouchPoints = 0; 8665 let touchEvent; 8666 if (n.maxTouchPoints !== undefined) { 8667 maxTouchPoints = toInt(n.maxTouchPoints); 8668 } 8669 else if (n.msMaxTouchPoints !== undefined) { 8670 maxTouchPoints = n.msMaxTouchPoints; 8671 } 8672 try { 8673 document.createEvent('TouchEvent'); 8674 touchEvent = true; 8675 } 8676 catch (_a) { 8677 touchEvent = false; 8678 } 8679 const touchStart = 'ontouchstart' in window; 8680 return { 8681 maxTouchPoints, 8682 touchEvent, 8683 touchStart, 8684 }; 8685 } 8686 8687 function getOsCpu() { 8688 return navigator.oscpu; 8689 } 8690 8691 function getLanguages() { 8692 const n = navigator; 8693 const result = []; 8694 const language = n.language || n.userLanguage || n.browserLanguage || n.systemLanguage; 8695 if (language !== undefined) { 8696 result.push([language]); 8697 } 8698 if (Array.isArray(n.languages)) { 8699 // Starting from Chromium 86, there is only a single value in `navigator.language` in Incognito mode: 8700 // the value of `navigator.language`. Therefore the value is ignored in this browser. 8701 if (!(isChromium() && isChromium86OrNewer())) { 8702 result.push(n.languages); 8703 } 8704 } 8705 else if (typeof n.languages === 'string') { 8706 const languages = n.languages; 8707 if (languages) { 8708 result.push(languages.split(',')); 8709 } 8710 } 8711 return result; 8712 } 8713 8714 function getColorDepth() { 8715 return window.screen.colorDepth; 8716 } 8717 8718 function getDeviceMemory() { 8719 // `navigator.deviceMemory` is a string containing a number in some unidentified cases 8720 return replaceNaN(toFloat(navigator.deviceMemory), undefined); 8721 } 8722 8723 /** 8724 * A version of the entropy source with stabilization to make it suitable for static fingerprinting. 8725 * The window resolution is always the document size in private mode of Safari 17, 8726 * so the window resolution is not used in Safari 17. 8727 */ 8728 function getScreenResolution() { 8729 if (isWebKit() && isWebKit616OrNewer() && isSafariWebKit()) { 8730 return undefined; 8731 } 8732 return getUnstableScreenResolution(); 8733 } 8734 /** 8735 * A version of the entropy source without stabilization. 8736 * 8737 * Warning for package users: 8738 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8739 */ 8740 function getUnstableScreenResolution() { 8741 const s = screen; 8742 // Some browsers return screen resolution as strings, e.g. "1200", instead of a number, e.g. 1200. 8743 // I suspect it's done by certain plugins that randomize browser properties to prevent fingerprinting. 8744 // Some browsers even return screen resolution as not numbers. 8745 const parseDimension = (value) => replaceNaN(toInt(value), null); 8746 const dimensions = [parseDimension(s.width), parseDimension(s.height)]; 8747 dimensions.sort().reverse(); 8748 return dimensions; 8749 } 8750 8751 const screenFrameCheckInterval = 2500; 8752 const roundingPrecision = 10; 8753 // The type is readonly to protect from unwanted mutations 8754 let screenFrameBackup; 8755 let screenFrameSizeTimeoutId; 8756 /** 8757 * Starts watching the screen frame size. When a non-zero size appears, the size is saved and the watch is stopped. 8758 * Later, when `getScreenFrame` runs, it will return the saved non-zero size if the current size is null. 8759 * 8760 * This trick is required to mitigate the fact that the screen frame turns null in some cases. 8761 * See more on this at https://github.com/fingerprintjs/fingerprintjs/issues/568 8762 */ 8763 function watchScreenFrame() { 8764 if (screenFrameSizeTimeoutId !== undefined) { 8765 return; 8766 } 8767 const checkScreenFrame = () => { 8768 const frameSize = getCurrentScreenFrame(); 8769 if (isFrameSizeNull(frameSize)) { 8770 screenFrameSizeTimeoutId = setTimeout(checkScreenFrame, screenFrameCheckInterval); 8771 } 8772 else { 8773 screenFrameBackup = frameSize; 8774 screenFrameSizeTimeoutId = undefined; 8775 } 8776 }; 8777 checkScreenFrame(); 8778 } 8779 /** 8780 * A version of the entropy source without stabilization. 8781 * 8782 * Warning for package users: 8783 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8784 */ 8785 function getUnstableScreenFrame() { 8786 watchScreenFrame(); 8787 return async () => { 8788 let frameSize = getCurrentScreenFrame(); 8789 if (isFrameSizeNull(frameSize)) { 8790 if (screenFrameBackup) { 8791 return [...screenFrameBackup]; 8792 } 8793 if (getFullscreenElement()) { 8794 // Some browsers set the screen frame to zero when programmatic fullscreen is on. 8795 // There is a chance of getting a non-zero frame after exiting the fullscreen. 8796 // See more on this at https://github.com/fingerprintjs/fingerprintjs/issues/568 8797 await exitFullscreen(); 8798 frameSize = getCurrentScreenFrame(); 8799 } 8800 } 8801 if (!isFrameSizeNull(frameSize)) { 8802 screenFrameBackup = frameSize; 8803 } 8804 return frameSize; 8805 }; 8806 } 8807 /** 8808 * A version of the entropy source with stabilization to make it suitable for static fingerprinting. 8809 * 8810 * Sometimes the available screen resolution changes a bit, e.g. 1900x1440 â 1900x1439. A possible reason: macOS Dock 8811 * shrinks to fit more icons when there is too little space. The rounding is used to mitigate the difference. 8812 * 8813 * The frame width is always 0 in the private mode of Safari 17, so the frame is not used in Safari 17. 8814 * Firefox 143+ spoofs screen frame (ScreenAvailRect) in private browsing and strict ETP mode, 8815 * so the frame is not used in Firefox 143+. 8816 * 8817 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1978414 Firefox screen resolution spoofing 8818 */ 8819 function getScreenFrame() { 8820 const isSafari17OrAbove = isWebKit() && isWebKit616OrNewer() && isSafariWebKit(); 8821 const isFirefox143OrAbove = isGecko() && isGecko143OrNewer(); 8822 if (isSafari17OrAbove || isFirefox143OrAbove) { 8823 return () => Promise.resolve(undefined); 8824 } 8825 const screenFrameGetter = getUnstableScreenFrame(); 8826 return async () => { 8827 const frameSize = await screenFrameGetter(); 8828 const processSize = (sideSize) => (sideSize === null ? null : round(sideSize, roundingPrecision)); 8829 // It might look like I don't know about `for` and `map`. 8830 // In fact, such code is used to avoid TypeScript issues without using `as`. 8831 return [processSize(frameSize[0]), processSize(frameSize[1]), processSize(frameSize[2]), processSize(frameSize[3])]; 8832 }; 8833 } 8834 function getCurrentScreenFrame() { 8835 const s = screen; 8836 // Some browsers return screen resolution as strings, e.g. "1200", instead of a number, e.g. 1200. 8837 // I suspect it's done by certain plugins that randomize browser properties to prevent fingerprinting. 8838 // 8839 // Some browsers (IE, Edge â¤18) don't provide `screen.availLeft` and `screen.availTop`. The property values are 8840 // replaced with 0 in such cases to not lose the entropy from `screen.availWidth` and `screen.availHeight`. 8841 return [ 8842 replaceNaN(toFloat(s.availTop), null), 8843 replaceNaN(toFloat(s.width) - toFloat(s.availWidth) - replaceNaN(toFloat(s.availLeft), 0), null), 8844 replaceNaN(toFloat(s.height) - toFloat(s.availHeight) - replaceNaN(toFloat(s.availTop), 0), null), 8845 replaceNaN(toFloat(s.availLeft), null), 8846 ]; 8847 } 8848 function isFrameSizeNull(frameSize) { 8849 for (let i = 0; i < 4; ++i) { 8850 if (frameSize[i]) { 8851 return false; 8852 } 8853 } 8854 return true; 8855 } 8856 8857 /** 8858 * A version of the entropy source with stabilization to make it suitable for static fingerprinting. 8859 * 8860 * Firefox 143+ spoofs hardwareConcurrency (NavigatorHWConcurrency) in private browsing and strict ETP mode 8861 * using tiered values: â¤4 cores -> 4, >4 cores -> 8. We apply the same tiering to stabilize the fingerprint 8862 * across all browsing modes, since the tiering function is idempotent. 8863 * 8864 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=1978414 Firefox processor count spoofing 8865 * @see https://searchfox.org/firefox-main/source/dom/workers/RuntimeService.cpp#2057:~:text=if%20(MOZ_UNLIKELY(aRFPTiered))%20%7B 8866 */ 8867 function getHardwareConcurrency() { 8868 const value = getUnstableHardwareConcurrency(); 8869 if (value !== undefined && isGecko() && isGecko143OrNewer()) { 8870 return value >= 8 ? 8 : 4; 8871 } 8872 return value; 8873 } 8874 /** 8875 * A version of the entropy source without stabilization. 8876 * 8877 * Warning for package users: 8878 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 8879 */ 8880 function getUnstableHardwareConcurrency() { 8881 // sometimes hardware concurrency is a string 8882 return replaceNaN(toInt(navigator.hardwareConcurrency), undefined); 8883 } 8884 8885 function getTimezone() { 8886 var _a; 8887 const DateTimeFormat = (_a = window.Intl) === null || _a === void 0 ? void 0 : _a.DateTimeFormat; 8888 if (DateTimeFormat) { 8889 const timezone = new DateTimeFormat().resolvedOptions().timeZone; 8890 if (timezone) { 8891 return timezone; 8892 } 8893 } 8894 // For browsers that don't support timezone names 8895 // The minus is intentional because the JS offset is opposite to the real offset 8896 const offset = -getTimezoneOffset(); 8897 return `UTC${offset >= 0 ? '+' : ''}${offset}`; 8898 } 8899 function getTimezoneOffset() { 8900 const currentYear = new Date().getFullYear(); 8901 // The timezone offset may change over time due to daylight saving time (DST) shifts. 8902 // The non-DST timezone offset is used as the result timezone offset. 8903 // Since the DST season differs in the northern and the southern hemispheres, 8904 // both January and July timezones offsets are considered. 8905 return Math.max( 8906 // `getTimezoneOffset` returns a number as a string in some unidentified cases 8907 toFloat(new Date(currentYear, 0, 1).getTimezoneOffset()), toFloat(new Date(currentYear, 6, 1).getTimezoneOffset())); 8908 } 8909 8910 function getSessionStorage() { 8911 try { 8912 return !!window.sessionStorage; 8913 } 8914 catch (error) { 8915 /* SecurityError when referencing it means it exists */ 8916 return true; 8917 } 8918 } 8919 8920 // https://bugzilla.mozilla.org/show_bug.cgi?id=781447 8921 function getLocalStorage() { 8922 try { 8923 return !!window.localStorage; 8924 } 8925 catch (e) { 8926 /* SecurityError when referencing it means it exists */ 8927 return true; 8928 } 8929 } 8930 8931 function getIndexedDB() { 8932 // IE and Edge don't allow accessing indexedDB in private mode, therefore IE and Edge will have different 8933 // visitor identifier in normal and private modes. 8934 if (isTrident() || isEdgeHTML()) { 8935 return undefined; 8936 } 8937 try { 8938 return !!window.indexedDB; 8939 } 8940 catch (e) { 8941 /* SecurityError when referencing it means it exists */ 8942 return true; 8943 } 8944 } 8945 8946 function getOpenDatabase() { 8947 return !!window.openDatabase; 8948 } 8949 8950 function getCpuClass() { 8951 return navigator.cpuClass; 8952 } 8953 8954 function getPlatform() { 8955 // Android Chrome 86 and 87 and Android Firefox 80 and 84 don't mock the platform value when desktop mode is requested 8956 const { platform } = navigator; 8957 // iOS mocks the platform value when desktop version is requested: https://github.com/fingerprintjs/fingerprintjs/issues/514 8958 // iPad uses desktop mode by default since iOS 13 8959 // The value is 'MacIntel' on M1 Macs 8960 // The value is 'iPhone' on iPod Touch 8961 if (platform === 'MacIntel') { 8962 if (isWebKit() && !isDesktopWebKit()) { 8963 return isIPad() ? 'iPad' : 'iPhone'; 8964 } 8965 } 8966 return platform; 8967 } 8968 8969 function getVendor() { 8970 return navigator.vendor || ''; 8971 } 8972 8973 /** 8974 * Checks for browser-specific (not engine specific) global variables to tell browsers with the same engine apart. 8975 * Only somewhat popular browsers are considered. 8976 */ 8977 function getVendorFlavors() { 8978 const flavors = []; 8979 for (const key of [ 8980 // Blink and some browsers on iOS 8981 'chrome', 8982 // Safari on macOS
8983 'safari', 8984 // Chrome on iOS (checked in 85 on 13 and 87 on 14) 8985 '__crWeb', 8986 '__gCrWeb', 8987 // Yandex Browser on iOS, macOS and Android (checked in 21.2 on iOS 14, macOS and Android) 8988 'yandex', 8989 // Yandex Browser on iOS (checked in 21.2 on 14) 8990 '__yb', 8991 '__ybro', 8992 // Firefox on iOS (checked in 32 on 14) 8993 '__firefox__', 8994 // Edge on iOS (checked in 46 on 14) 8995 '__edgeTrackingPreventionStatistics', 8996 'webkit', 8997 // Opera Touch on iOS (checked in 2.6 on 14) 8998 'oprt', 8999 // Samsung Internet on Android (checked in 11.1) 9000 'samsungAr', 9001 // UC Browser on Android (checked in 12.10 and 13.0) 9002 'ucweb', 9003 'UCShellJava', 9004 // Puffin on Android (checked in 9.0) 9005 'puffinDevice', 9006 // UC on iOS and Opera on Android have no specific global variables 9007 // Edge for Android isn't checked 9008 ]) { 9009 const value = window[key]; 9010 if (value && typeof value === 'object') { 9011 flavors.push(key); 9012 } 9013 } 9014 return flavors.sort(); 9015 } 9016 9017 /** 9018 * navigator.cookieEnabled cannot detect custom or nuanced cookie blocking configurations. For example, when blocking 9019 * cookies via the Advanced Privacy Settings in IE9, it always returns true. And there have been issues in the past with 9020 * site-specific exceptions. Don't rely on it. 9021 * 9022 * @see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cookies.js Taken from here 9023 */ 9024 function areCookiesEnabled() { 9025 const d = document; 9026 // Taken from here: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cookies.js 9027 // navigator.cookieEnabled cannot detect custom or nuanced cookie blocking configurations. For example, when blocking 9028 // cookies via the Advanced Privacy Settings in IE9, it always returns true. And there have been issues in the past 9029 // with site-specific exceptions. Don't rely on it. 9030 // try..catch because some in situations `document.cookie` is exposed but throws a 9031 // SecurityError if you try to access it; e.g. documents created from data URIs 9032 // or in sandboxed iframes (depending on flags/context)
vendor: 8,553 bytes, lines 9033-9228
9033 try { 9034 // Create cookie 9035 d.cookie = 'cookietest=1; SameSite=Strict;'; 9036 const result = d.cookie.indexOf('cookietest=') !== -1; 9037 // Delete cookie 9038 d.cookie = 'cookietest=1; SameSite=Strict; expires=Thu, 01-Jan-1970 00:00:01 GMT'; 9039 return result; 9040 } 9041 catch (e) { 9042 return false; 9043 } 9044 } 9045 9046 /** 9047 * Only single element selector are supported (no operators like space, +, >, etc). 9048 * `embed` and `position: fixed;` will be considered as blocked anyway because it always has no offsetParent. 9049 * Avoid `iframe` and anything with `[src=]` because they produce excess HTTP requests. 9050 * 9051 * The "inappropriate" selectors are obfuscated. See https://github.com/fingerprintjs/fingerprintjs/issues/734. 9052 * A function is used instead of a plain object to help tree-shaking. 9053 * 9054 * The function code is generated automatically. See docs/content_blockers.md to learn how to make the list. 9055 */ 9056 function getFilters() { 9057 const fromB64 = atob; // Just for better minification 9058 return { 9059 abpIndo: [ 9060 '#Iklan-Melayang', 9061 '#Kolom-Iklan-728', 9062 '#SidebarIklan-wrapper', 9063 '[title="ALIENBOLA" i]', 9064 fromB64('I0JveC1CYW5uZXItYWRz'), 9065 ], 9066 abpvn: ['.quangcao', '#mobileCatfish', fromB64('LmNsb3NlLWFkcw=='), '[id^="bn_bottom_fixed_"]', '#pmadv'], 9067 adBlockFinland: [ 9068 '.mainostila', 9069 fromB64('LnNwb25zb3JpdA=='), 9070 '.ylamainos', 9071 fromB64('YVtocmVmKj0iL2NsaWNrdGhyZ2guYXNwPyJd'), 9072 fromB64('YVtocmVmXj0iaHR0cHM6Ly9hcHAucmVhZHBlYWsuY29tL2FkcyJd'), 9073 ], 9074 adBlockPersian: [ 9075 '#navbar_notice_50', 9076 '.kadr', 9077 'TABLE[width="140px"]', 9078 '#divAgahi', 9079 fromB64('YVtocmVmXj0iaHR0cDovL2cxLnYuZndtcm0ubmV0L2FkLyJd'), 9080 ], 9081 adBlockWarningRemoval: [ 9082 '#adblock-honeypot', 9083 '.adblocker-root', 9084 '.wp_adblock_detect', 9085 fromB64('LmhlYWRlci1ibG9ja2VkLWFk'), 9086 fromB64('I2FkX2Jsb2NrZXI='), 9087 ], 9088 adGuardAnnoyances: [ 9089 '.hs-sosyal', 9090 '#cookieconsentdiv', 9091 'div[class^="app_gdpr"]', 9092 '.as-oil', 9093 '[data-cypress="soft-push-notification-modal"]', 9094 ], 9095 adGuardBase: [ 9096 '.BetterJsPopOverlay', 9097 fromB64('I2FkXzMwMFgyNTA='), 9098 fromB64('I2Jhbm5lcmZsb2F0MjI='), 9099 fromB64('I2NhbXBhaWduLWJhbm5lcg=='), 9100 fromB64('I0FkLUNvbnRlbnQ='), 9101 ], 9102 adGuardChinese: [ 9103 fromB64('LlppX2FkX2FfSA=='), 9104 fromB64('YVtocmVmKj0iLmh0aGJldDM0LmNvbSJd'), 9105 '#widget-quan', 9106 fromB64('YVtocmVmKj0iLzg0OTkyMDIwLnh5eiJd'), 9107 fromB64('YVtocmVmKj0iLjE5NTZobC5jb20vIl0='), 9108 ], 9109 adGuardFrench: [ 9110 '#pavePub', 9111 fromB64('LmFkLWRlc2t0b3AtcmVjdGFuZ2xl'), 9112 '.mobile_adhesion', 9113 '.widgetadv', 9114 fromB64('LmFkc19iYW4='), 9115 ], 9116 adGuardGerman: ['aside[data-portal-id="leaderboard"]'], 9117 adGuardJapanese: [ 9118 '#kauli_yad_1', 9119 fromB64('YVtocmVmXj0iaHR0cDovL2FkMi50cmFmZmljZ2F0ZS5uZXQvIl0='), 9120 fromB64('Ll9wb3BJbl9pbmZpbml0ZV9hZA=='), 9121 fromB64('LmFkZ29vZ2xl'), 9122 fromB64('Ll9faXNib29zdFJldHVybkFk'), 9123 ], 9124 adGuardMobile: [ 9125 fromB64('YW1wLWF1dG8tYWRz'), 9126 fromB64('LmFtcF9hZA=='), 9127 'amp-embed[type="24smi"]', 9128 '#mgid_iframe1', 9129 fromB64('I2FkX2ludmlld19hcmVh'), 9130 ], 9131 adGuardRussian: [ 9132 fromB64('YVtocmVmXj0iaHR0cHM6Ly9hZC5sZXRtZWFkcy5jb20vIl0='), 9133 fromB64('LnJlY2xhbWE='), 9134 'div[id^="smi2adblock"]', 9135 fromB64('ZGl2W2lkXj0iQWRGb3hfYmFubmVyXyJd'), 9136 '#psyduckpockeball', 9137 ], 9138 adGuardSocial: [ 9139 fromB64('YVtocmVmXj0iLy93d3cuc3R1bWJsZXVwb24uY29tL3N1Ym1pdD91cmw9Il0='), 9140 fromB64('YVtocmVmXj0iLy90ZWxlZ3JhbS5tZS9zaGFyZS91cmw/Il0='), 9141 '.etsy-tweet', 9142 '#inlineShare', 9143 '.popup-social', 9144 ], 9145 adGuardSpanishPortuguese: ['#barraPublicidade', '#Publicidade', '#publiEspecial', '#queTooltip', '.cnt-publi'], 9146 adGuardTrackingProtection: [ 9147 '#qoo-counter', 9148 fromB64('YVtocmVmXj0iaHR0cDovL2NsaWNrLmhvdGxvZy5ydS8iXQ=='), 9149 fromB64('YVtocmVmXj0iaHR0cDovL2hpdGNvdW50ZXIucnUvdG9wL3N0YXQucGhwIl0='), 9150 fromB64('YVtocmVmXj0iaHR0cDovL3RvcC5tYWlsLnJ1L2p1bXAiXQ=='), 9151 '#top100counter', 9152 ], 9153 adGuardTurkish: [ 9154 '#backkapat', 9155 fromB64('I3Jla2xhbWk='), 9156 fromB64('YVtocmVmXj0iaHR0cDovL2Fkc2Vydi5vbnRlay5jb20udHIvIl0='), 9157 fromB64('YVtocmVmXj0iaHR0cDovL2l6bGVuemkuY29tL2NhbXBhaWduLyJd'), 9158 fromB64('YVtocmVmXj0iaHR0cDovL3d3dy5pbnN0YWxsYWRzLm5ldC8iXQ=='), 9159 ], 9160 bulgarian: [fromB64('dGQjZnJlZW5ldF90YWJsZV9hZHM='), '#ea_intext_div', '.lapni-pop-over', '#xenium_hot_offers'], 9161 easyList: [ 9162 '.yb-floorad', 9163 fromB64('LndpZGdldF9wb19hZHNfd2lkZ2V0'), 9164 fromB64('LnRyYWZmaWNqdW5reS1hZA=='), 9165 '.textad_headline', 9166 fromB64('LnNwb25zb3JlZC10ZXh0LWxpbmtz'), 9167 ], 9168 easyListChina: [ 9169 fromB64('LmFwcGd1aWRlLXdyYXBbb25jbGljayo9ImJjZWJvcy5jb20iXQ=='), 9170 fromB64('LmZyb250cGFnZUFkdk0='), 9171 '#taotaole', 9172 '#aafoot.top_box', 9173 '.cfa_popup', 9174 ], 9175 easyListCookie: [ 9176 '.ezmob-footer', 9177 '.cc-CookieWarning', 9178 '[data-cookie-number]', 9179 fromB64('LmF3LWNvb2tpZS1iYW5uZXI='), 9180 '.sygnal24-gdpr-modal-wrap', 9181 ], 9182 easyListCzechSlovak: [ 9183 '#onlajny-stickers', 9184 fromB64('I3Jla2xhbW5pLWJveA=='), 9185 fromB64('LnJla2xhbWEtbWVnYWJvYXJk'), 9186 '.sklik', 9187 fromB64('W2lkXj0ic2tsaWtSZWtsYW1hIl0='), 9188 ], 9189 easyListDutch: [ 9190 fromB64('I2FkdmVydGVudGll'), 9191 fromB64('I3ZpcEFkbWFya3RCYW5uZXJCbG9jaw=='), 9192 '.adstekst', 9193 fromB64('YVtocmVmXj0iaHR0cHM6Ly94bHR1YmUubmwvY2xpY2svIl0='), 9194 '#semilo-lrectangle', 9195 ], 9196 easyListGermany: [ 9197 '#SSpotIMPopSlider', 9198 fromB64('LnNwb25zb3JsaW5rZ3J1ZW4='), 9199 fromB64('I3dlcmJ1bmdza3k='), 9200 fromB64('I3Jla2xhbWUtcmVjaHRzLW1pdHRl'), 9201 fromB64('YVtocmVmXj0iaHR0cHM6Ly9iZDc0Mi5jb20vIl0='), 9202 ], 9203 easyListItaly: [ 9204 fromB64('LmJveF9hZHZfYW5udW5jaQ=='), 9205 '.sb-box-pubbliredazionale', 9206 fromB64('YVtocmVmXj0iaHR0cDovL2FmZmlsaWF6aW9uaWFkcy5zbmFpLml0LyJd'), 9207 fromB64('YVtocmVmXj0iaHR0cHM6Ly9hZHNlcnZlci5odG1sLml0LyJd'), 9208 fromB64('YVtocmVmXj0iaHR0cHM6Ly9hZmZpbGlhemlvbmlhZHMuc25haS5pdC8iXQ=='), 9209 ], 9210 easyListLithuania: [ 9211 fromB64('LnJla2xhbW9zX3RhcnBhcw=='), 9212 fromB64('LnJla2xhbW9zX251b3JvZG9z'), 9213 fromB64('aW1nW2FsdD0iUmVrbGFtaW5pcyBza3lkZWxpcyJd'), 9214 fromB64('aW1nW2FsdD0iRGVkaWt1b3RpLmx0IHNlcnZlcmlhaSJd'), 9215 fromB64('aW1nW2FsdD0iSG9zdGluZ2FzIFNlcnZlcmlhaS5sdCJd'), 9216 ], 9217 estonian: [fromB64('QVtocmVmKj0iaHR0cDovL3BheTRyZXN1bHRzMjQuZXUiXQ==')], 9218 fanboyAnnoyances: ['#ac-lre-player', '.navigate-to-top', '#subscribe_popup', '.newsletter_holder', '#back-top'], 9219 fanboyAntiFacebook: ['.util-bar-module-firefly-visible'], 9220 fanboyEnhancedTrackers: [ 9221 '.open.pushModal', 9222 '#issuem-leaky-paywall-articles-zero-remaining-nag', 9223 '#sovrn_container', 9224 'div[class$="-hide"][zoompage-fontsize][style="display: block;"]', 9225 '.BlockNag__Card', 9226 ], 9227 fanboySocial: ['#FollowUs', '#meteored_share', '#social_follow', '.article-sharer', '.community__social-desc'], 9228 frellwitSwedish: [
9229 fromB64('YVtocmVmKj0iY2FzaW5vcHJvLnNlIl1bdGFyZ2V0PSJfYmxhbmsiXQ=='), 9230 fromB64('YVtocmVmKj0iZG9rdG9yLXNlLm9uZWxpbmsubWUiXQ=='), 9231 'article.category-samarbete', 9232 fromB64('ZGl2LmhvbGlkQWRz'), 9233 'ul.adsmodern', 9234 ], 9235 greekAdBlock: [ 9236 fromB64('QVtocmVmKj0iYWRtYW4ub3RlbmV0LmdyL2NsaWNrPyJd'), 9237 fromB64('QVtocmVmKj0iaHR0cDovL2F4aWFiYW5uZXJzLmV4b2R1cy5nci8iXQ=='), 9238 fromB64('QVtocmVmKj0iaHR0cDovL2ludGVyYWN0aXZlLmZvcnRobmV0LmdyL2NsaWNrPyJd'), 9239 'DIV.agores300', 9240 'TABLE.advright', 9241 ], 9242 hungarian: [ 9243 '#cemp_doboz', 9244 '.optimonk-iframe-container', 9245 fromB64('LmFkX19tYWlu'), 9246 fromB64('W2NsYXNzKj0iR29vZ2xlQWRzIl0='), 9247 '#hirdetesek_box', 9248 ], 9249 iDontCareAboutCookies: [ 9250 '.alert-info[data-block-track*="CookieNotice"]', 9251 '.ModuleTemplateCookieIndicator', 9252 '.o--cookies--container', 9253 '#cookies-policy-sticky', 9254 '#stickyCookieBar', 9255 ], 9256 icelandicAbp: [fromB64('QVtocmVmXj0iL2ZyYW1ld29yay9yZXNvdXJjZXMvZm9ybXMvYWRzLmFzcHgiXQ==')], 9257 latvian: [ 9258 fromB64('YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiAxMjBweDsgaGVpZ2h0O' + 9259 'iA0MHB4OyBvdmVyZmxvdzogaGlkZGVuOyBwb3NpdGlvbjogcmVsYXRpdmU7Il0='), 9260 fromB64('YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiA4OHB4OyBoZWlnaHQ6I' + 9261 'DMxcHg7IG92ZXJmbG93OiBoaWRkZW47IHBvc2l0aW9uOiByZWxhdGl2ZTsiXQ=='), 9262 ], 9263 listKr: [ 9264 fromB64('YVtocmVmKj0iLy9hZC5wbGFuYnBsdXMuY28ua3IvIl0='), 9265 fromB64('I2xpdmVyZUFkV3JhcHBlcg=='), 9266 fromB64('YVtocmVmKj0iLy9hZHYuaW1hZHJlcC5jby5rci8iXQ=='), 9267 fromB64('aW5zLmZhc3R2aWV3LWFk'), 9268 '.revenue_unit_item.dable',
vendor: 15,412 bytes, lines 9269-9661
9269 ], 9270 listeAr: [ 9271 fromB64('LmdlbWluaUxCMUFk'), 9272 '.right-and-left-sponsers', 9273 fromB64('YVtocmVmKj0iLmFmbGFtLmluZm8iXQ=='), 9274 fromB64('YVtocmVmKj0iYm9vcmFxLm9yZyJd'), 9275 fromB64('YVtocmVmKj0iZHViaXp6bGUuY29tL2FyLz91dG1fc291cmNlPSJd'), 9276 ], 9277 listeFr: [ 9278 fromB64('YVtocmVmXj0iaHR0cDovL3Byb21vLnZhZG9yLmNvbS8iXQ=='), 9279 fromB64('I2FkY29udGFpbmVyX3JlY2hlcmNoZQ=='), 9280 fromB64('YVtocmVmKj0id2Vib3JhbWEuZnIvZmNnaS1iaW4vIl0='), 9281 '.site-pub-interstitiel', 9282 'div[id^="crt-"][data-criteo-id]', 9283 ], 9284 officialPolish: [ 9285 '#ceneo-placeholder-ceneo-12', 9286 fromB64('W2hyZWZePSJodHRwczovL2FmZi5zZW5kaHViLnBsLyJd'), 9287 fromB64('YVtocmVmXj0iaHR0cDovL2Fkdm1hbmFnZXIudGVjaGZ1bi5wbC9yZWRpcmVjdC8iXQ=='), 9288 fromB64('YVtocmVmXj0iaHR0cDovL3d3dy50cml6ZXIucGwvP3V0bV9zb3VyY2UiXQ=='), 9289 fromB64('ZGl2I3NrYXBpZWNfYWQ='), 9290 ], 9291 ro: [ 9292 fromB64('YVtocmVmXj0iLy9hZmZ0cmsuYWx0ZXgucm8vQ291bnRlci9DbGljayJd'), 9293 fromB64('YVtocmVmXj0iaHR0cHM6Ly9ibGFja2ZyaWRheXNhbGVzLnJvL3Ryay9zaG9wLyJd'), 9294 fromB64('YVtocmVmXj0iaHR0cHM6Ly9ldmVudC4ycGVyZm9ybWFudC5jb20vZXZlbnRzL2NsaWNrIl0='), 9295 fromB64('YVtocmVmXj0iaHR0cHM6Ly9sLnByb2ZpdHNoYXJlLnJvLyJd'), 9296 'a[href^="/url/"]', 9297 ], 9298 ruAd: [ 9299 fromB64('YVtocmVmKj0iLy9mZWJyYXJlLnJ1LyJd'), 9300 fromB64('YVtocmVmKj0iLy91dGltZy5ydS8iXQ=='), 9301 fromB64('YVtocmVmKj0iOi8vY2hpa2lkaWtpLnJ1Il0='), 9302 '#pgeldiz', 9303 '.yandex-rtb-block', 9304 ], 9305 thaiAds: [ 9306 'a[href*=macau-uta-popup]', 9307 fromB64('I2Fkcy1nb29nbGUtbWlkZGxlX3JlY3RhbmdsZS1ncm91cA=='), 9308 fromB64('LmFkczMwMHM='), 9309 '.bumq', 9310 '.img-kosana', 9311 ], 9312 webAnnoyancesUltralist: [ 9313 '#mod-social-share-2', 9314 '#social-tools', 9315 fromB64('LmN0cGwtZnVsbGJhbm5lcg=='), 9316 '.zergnet-recommend', 9317 '.yt.btn-link.btn-md.btn', 9318 ], 9319 }; 9320 } 9321 /** 9322 * The order of the returned array means nothing (it's always sorted alphabetically). 9323 * 9324 * Notice that the source is slightly unstable. 9325 * Safari provides a 2-taps way to disable all content blockers on a page temporarily. 9326 * Also content blockers can be disabled permanently for a domain, but it requires 4 taps. 9327 * So empty array shouldn't be treated as "no blockers", it should be treated as "no signal". 9328 * If you are a website owner, don't make your visitors want to disable content blockers. 9329 */ 9330 async function getDomBlockers({ debug } = {}) { 9331 if (!isApplicable()) { 9332 return undefined; 9333 } 9334 const filters = getFilters(); 9335 const filterNames = Object.keys(filters); 9336 const allSelectors = [].concat(...filterNames.map((filterName) => filters[filterName])); 9337 const blockedSelectors = await getBlockedSelectors(allSelectors); 9338 if (debug) { 9339 printDebug(filters, blockedSelectors); 9340 } 9341 const activeBlockers = filterNames.filter((filterName) => { 9342 const selectors = filters[filterName]; 9343 const blockedCount = countTruthy(selectors.map((selector) => blockedSelectors[selector])); 9344 return blockedCount > selectors.length * 0.6; 9345 }); 9346 activeBlockers.sort(); 9347 return activeBlockers; 9348 } 9349 function isApplicable() { 9350 // Safari (desktop and mobile) and all Android browsers keep content blockers in both regular and private mode 9351 return isWebKit() || isAndroid(); 9352 } 9353 async function getBlockedSelectors(selectors) { 9354 var _a; 9355 const d = document; 9356 const root = d.createElement('div'); 9357 const elements = new Array(selectors.length); 9358 const blockedSelectors = {}; // Set() isn't used just in case somebody need older browser support 9359 forceShow(root); 9360 // First create all elements that can be blocked. If the DOM steps below are done in a single cycle, 9361 // browser will alternate tree modification and layout reading, that is very slow. 9362 for (let i = 0; i < selectors.length; ++i) { 9363 const element = selectorToElement(selectors[i]); 9364 if (element.tagName === 'DIALOG') { 9365 element.show(); 9366 } 9367 const holder = d.createElement('div'); // Protects from unwanted effects of `+` and `~` selectors of filters 9368 forceShow(holder); 9369 holder.appendChild(element); 9370 root.appendChild(holder); 9371 elements[i] = element; 9372 } 9373 // document.body can be null while the page is loading 9374 while (!d.body) { 9375 await wait(50); 9376 } 9377 d.body.appendChild(root); 9378 try { 9379 // Then check which of the elements are blocked 9380 for (let i = 0; i < selectors.length; ++i) { 9381 if (!elements[i].offsetParent) { 9382 blockedSelectors[selectors[i]] = true; 9383 } 9384 } 9385 } 9386 finally { 9387 // Then remove the elements 9388 (_a = root.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(root); 9389 } 9390 return blockedSelectors; 9391 } 9392 function forceShow(element) { 9393 element.style.setProperty('visibility', 'hidden', 'important'); 9394 element.style.setProperty('display', 'block', 'important'); 9395 } 9396 function printDebug(filters, blockedSelectors) { 9397 let message = 'DOM blockers debug:\n```'; 9398 for (const filterName of Object.keys(filters)) { 9399 message += `\n${filterName}:`; 9400 for (const selector of filters[filterName]) { 9401 message += `\n ${blockedSelectors[selector] ? 'ð«' : 'â¡ï¸'} ${selector}`; 9402 } 9403 } 9404 // console.log is ok here because it's under a debug clause 9405 // eslint-disable-next-line no-console 9406 console.log(`${message}\n\`\`\``); 9407 } 9408 9409 /** 9410 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/color-gamut 9411 */ 9412 function getColorGamut() { 9413 // rec2020 includes p3 and p3 includes srgb 9414 for (const gamut of ['rec2020', 'p3', 'srgb']) { 9415 if (matchMedia(`(color-gamut: ${gamut})`).matches) { 9416 return gamut; 9417 } 9418 } 9419 return undefined; 9420 } 9421 9422 /** 9423 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/inverted-colors 9424 */ 9425 function areColorsInverted() { 9426 if (doesMatch$5('inverted')) { 9427 return true; 9428 } 9429 if (doesMatch$5('none')) { 9430 return false; 9431 } 9432 return undefined; 9433 } 9434 function doesMatch$5(value) { 9435 return matchMedia(`(inverted-colors: ${value})`).matches; 9436 } 9437 9438 /** 9439 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors 9440 */ 9441 function areColorsForced() { 9442 if (doesMatch$4('active')) { 9443 return true; 9444 } 9445 if (doesMatch$4('none')) { 9446 return false; 9447 } 9448 return undefined; 9449 } 9450 function doesMatch$4(value) { 9451 return matchMedia(`(forced-colors: ${value})`).matches; 9452 } 9453 9454 const maxValueToCheck = 100; 9455 /** 9456 * If the display is monochrome (e.g. black&white), the value will be â¥0 and will mean the number of bits per pixel. 9457 * If the display is not monochrome, the returned value will be 0. 9458 * If the browser doesn't support this feature, the returned value will be undefined. 9459 * 9460 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/monochrome 9461 */ 9462 function getMonochromeDepth() { 9463 if (!matchMedia('(min-monochrome: 0)').matches) { 9464 // The media feature isn't supported by the browser 9465 return undefined; 9466 } 9467 // A variation of binary search algorithm can be used here. 9468 // But since expected values are very small (â¤10), there is no sense in adding the complexity. 9469 for (let i = 0; i <= maxValueToCheck; ++i) { 9470 if (matchMedia(`(max-monochrome: ${i})`).matches) { 9471 return i; 9472 } 9473 } 9474 throw new Error('Too high value'); 9475 } 9476 9477 /** 9478 * @see https://www.w3.org/TR/mediaqueries-5/#prefers-contrast 9479 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast 9480 */ 9481 function getContrastPreference() { 9482 if (doesMatch$3('no-preference')) { 9483 return 0 /* ContrastPreference.None */; 9484 } 9485 // The sources contradict on the keywords. Probably 'high' and 'low' will never be implemented. 9486 // Need to check it when all browsers implement the feature. 9487 if (doesMatch$3('high') || doesMatch$3('more')) { 9488 return 1 /* ContrastPreference.More */; 9489 } 9490 if (doesMatch$3('low') || doesMatch$3('less')) { 9491 return -1 /* ContrastPreference.Less */; 9492 } 9493 if (doesMatch$3('forced')) { 9494 return 10 /* ContrastPreference.ForcedColors */; 9495 } 9496 return undefined; 9497 } 9498 function doesMatch$3(value) { 9499 return matchMedia(`(prefers-contrast: ${value})`).matches; 9500 } 9501 9502 /** 9503 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion 9504 */ 9505 function isMotionReduced() { 9506 if (doesMatch$2('reduce')) { 9507 return true; 9508 } 9509 if (doesMatch$2('no-preference')) { 9510 return false; 9511 } 9512 return undefined; 9513 } 9514 function doesMatch$2(value) { 9515 return matchMedia(`(prefers-reduced-motion: ${value})`).matches; 9516 } 9517 9518 /** 9519 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-transparency 9520 */ 9521 function isTransparencyReduced() { 9522 if (doesMatch$1('reduce')) { 9523 return true; 9524 } 9525 if (doesMatch$1('no-preference')) { 9526 return false; 9527 } 9528 return undefined; 9529 } 9530 function doesMatch$1(value) { 9531 return matchMedia(`(prefers-reduced-transparency: ${value})`).matches; 9532 } 9533 9534 /** 9535 * @see https://www.w3.org/TR/mediaqueries-5/#dynamic-range 9536 */ 9537 function isHDR() { 9538 if (doesMatch('high')) { 9539 return true; 9540 } 9541 if (doesMatch('standard')) { 9542 return false; 9543 } 9544 return undefined; 9545 } 9546 function doesMatch(value) { 9547 return matchMedia(`(dynamic-range: ${value})`).matches; 9548 } 9549 9550 const M = Math; // To reduce the minified code size 9551 const fallbackFn = () => 0; 9552 /** 9553 * @see https://gitlab.torproject.org/legacy/trac/-/issues/13018 9554 * @see https://bugzilla.mozilla.org/show_bug.cgi?id=531915 9555 */ 9556 function getMathFingerprint() { 9557 // Native operations 9558 const acos = M.acos || fallbackFn; 9559 const acosh = M.acosh || fallbackFn; 9560 const asin = M.asin || fallbackFn; 9561 const asinh = M.asinh || fallbackFn; 9562 const atanh = M.atanh || fallbackFn; 9563 const atan = M.atan || fallbackFn; 9564 const sin = M.sin || fallbackFn; 9565 const sinh = M.sinh || fallbackFn; 9566 const cos = M.cos || fallbackFn; 9567 const cosh = M.cosh || fallbackFn; 9568 const tan = M.tan || fallbackFn; 9569 const tanh = M.tanh || fallbackFn; 9570 const exp = M.exp || fallbackFn; 9571 const expm1 = M.expm1 || fallbackFn; 9572 const log1p = M.log1p || fallbackFn; 9573 // Operation polyfills 9574 const powPI = (value) => M.pow(M.PI, value); 9575 const acoshPf = (value) => M.log(value + M.sqrt(value * value - 1)); 9576 const asinhPf = (value) => M.log(value + M.sqrt(value * value + 1)); 9577 const atanhPf = (value) => M.log((1 + value) / (1 - value)) / 2; 9578 const sinhPf = (value) => M.exp(value) - 1 / M.exp(value) / 2; 9579 const coshPf = (value) => (M.exp(value) + 1 / M.exp(value)) / 2; 9580 const expm1Pf = (value) => M.exp(value) - 1; 9581 const tanhPf = (value) => (M.exp(2 * value) - 1) / (M.exp(2 * value) + 1); 9582 const log1pPf = (value) => M.log(1 + value); 9583 // Note: constant values are empirical 9584 return { 9585 acos: acos(0.123124234234234242), 9586 acosh: acosh(1e308), 9587 acoshPf: acoshPf(1e154), 9588 asin: asin(0.123124234234234242), 9589 asinh: asinh(1), 9590 asinhPf: asinhPf(1), 9591 atanh: atanh(0.5), 9592 atanhPf: atanhPf(0.5), 9593 atan: atan(0.5), 9594 sin: sin(-1e300), 9595 sinh: sinh(1), 9596 sinhPf: sinhPf(1), 9597 cos: cos(10.000000000123), 9598 cosh: cosh(1), 9599 coshPf: coshPf(1), 9600 tan: tan(-1e300), 9601 tanh: tanh(1), 9602 tanhPf: tanhPf(1), 9603 exp: exp(1), 9604 expm1: expm1(1), 9605 expm1Pf: expm1Pf(1), 9606 log1p: log1p(10), 9607 log1pPf: log1pPf(10), 9608 powPI: powPI(-100), 9609 }; 9610 } 9611 9612 /** 9613 * We use m or w because these two characters take up the maximum width. 9614 * Also there are a couple of ligatures. 9615 */ 9616 const defaultText = 'mmMwWLliI0fiflO&1'; 9617 /** 9618 * Settings of text blocks to measure. The keys are random but persistent words. 9619 */ 9620 const presets = { 9621 /** 9622 * The default font. User can change it in desktop Chrome, desktop Firefox, IE 11, 9623 * Android Chrome (but only when the size is ⥠than the default) and Android Firefox. 9624 */ 9625 default: [], 9626 /** OS font on macOS. User can change its size and weight. Applies after Safari restart. */ 9627 apple: [{ font: '-apple-system-body' }], 9628 /** User can change it in desktop Chrome and desktop Firefox. */ 9629 serif: [{ fontFamily: 'serif' }], 9630 /** User can change it in desktop Chrome and desktop Firefox. */ 9631 sans: [{ fontFamily: 'sans-serif' }], 9632 /** User can change it in desktop Chrome and desktop Firefox. */ 9633 mono: [{ fontFamily: 'monospace' }], 9634 /** 9635 * Check the smallest allowed font size. User can change it in desktop Chrome, desktop Firefox and desktop Safari. 9636 * The height can be 0 in Chrome on a retina display. 9637 */ 9638 min: [{ fontSize: '1px' }], 9639 /** Tells one OS from another in desktop Chrome. */ 9640 system: [{ fontFamily: 'system-ui' }], 9641 }; 9642 /** 9643 * The result is a dictionary of the width of the text samples. 9644 * Heights aren't included because they give no extra entropy and are unstable. 9645 * 9646 * The result is very stable in IE 11, Edge 18 and Safari 14. 9647 * The result changes when the OS pixel density changes in Chromium 87. The real pixel density is required to solve, 9648 * but seems like it's impossible: https://stackoverflow.com/q/1713771/1118709. 9649 * The "min" and the "mono" (only on Windows) value may change when the page is zoomed in Firefox 87. 9650 */ 9651 function getFontPreferences() { 9652 return withNaturalFonts((document, container) => { 9653 const elements = {}; 9654 const sizes = {}; 9655 // First create all elements to measure. If the DOM steps below are done in a single cycle, 9656 // browser will alternate tree modification and layout reading, that is very slow. 9657 for (const key of Object.keys(presets)) { 9658 const [style = {}, text = defaultText] = presets[key]; 9659 const element = document.createElement('span'); 9660 element.textContent = text; 9661 element.style.whiteSpace = 'nowrap';
vendor: 6,570 bytes, lines 9662-9780
9662 for (const name of Object.keys(style)) { 9663 const value = style[name]; 9664 if (value !== undefined) { 9665 element.style[name] = value; 9666 } 9667 } 9668 elements[key] = element; 9669 container.append(document.createElement('br'), element); 9670 } 9671 // Then measure the created elements 9672 for (const key of Object.keys(presets)) { 9673 sizes[key] = elements[key].getBoundingClientRect().width; 9674 } 9675 return sizes; 9676 }); 9677 } 9678 /** 9679 * Creates a DOM environment that provides the most natural font available, including Android OS font. 9680 * Measurements of the elements are zoom-independent. 9681 * Don't put a content to measure inside an absolutely positioned element. 9682 */ 9683 function withNaturalFonts(action, containerWidthPx = 4000) { 9684 /* 9685 * Requirements for Android Chrome to apply the system font size to a text inside an iframe: 9686 * - The iframe mustn't have a `display: none;` style; 9687 * - The text mustn't be positioned absolutely; 9688 * - The text block must be wide enough. 9689 * 2560px on some devices in portrait orientation for the biggest font size option (32px); 9690 * - There must be much enough text to form a few lines (I don't know the exact numbers); 9691 * - The text must have the `text-size-adjust: none` style. Otherwise the text will scale in "Desktop site" mode; 9692 * 9693 * Requirements for Android Firefox to apply the system font size to a text inside an iframe: 9694 * - The iframe document must have a header: `<meta name="viewport" content="width=device-width, initial-scale=1" />`. 9695 * The only way to set it is to use the `srcdoc` attribute of the iframe; 9696 * - The iframe content must get loaded before adding extra content with JavaScript; 9697 * 9698 * https://example.com as the iframe target always inherits Android font settings so it can be used as a reference. 9699 * 9700 * Observations on how page zoom affects the measurements: 9701 * - macOS Safari 11.1, 12.1, 13.1, 14.0: zoom reset + offsetWidth = 100% reliable; 9702 * - macOS Safari 11.1, 12.1, 13.1, 14.0: zoom reset + getBoundingClientRect = 100% reliable; 9703 * - macOS Safari 14.0: offsetWidth = 5% fluctuation; 9704 * - macOS Safari 14.0: getBoundingClientRect = 5% fluctuation; 9705 * - iOS Safari 9, 10, 11.0, 12.0: haven't found a way to zoom a page (pinch doesn't change layout); 9706 * - iOS Safari 13.1, 14.0: zoom reset + offsetWidth = 100% reliable; 9707 * - iOS Safari 13.1, 14.0: zoom reset + getBoundingClientRect = 100% reliable; 9708 * - iOS Safari 14.0: offsetWidth = 100% reliable; 9709 * - iOS Safari 14.0: getBoundingClientRect = 100% reliable; 9710 * - Chrome 42, 65, 80, 87: zoom 1/devicePixelRatio + offsetWidth = 1px fluctuation; 9711 * - Chrome 42, 65, 80, 87: zoom 1/devicePixelRatio + getBoundingClientRect = 100% reliable; 9712 * - Chrome 87: offsetWidth = 1px fluctuation; 9713 * - Chrome 87: getBoundingClientRect = 0.7px fluctuation; 9714 * - Firefox 48, 51: offsetWidth = 10% fluctuation; 9715 * - Firefox 48, 51: getBoundingClientRect = 10% fluctuation; 9716 * - Firefox 52, 53, 57, 62, 66, 67, 68, 71, 75, 80, 84: offsetWidth = width 100% reliable, height 10% fluctuation; 9717 * - Firefox 52, 53, 57, 62, 66, 67, 68, 71, 75, 80, 84: getBoundingClientRect = width 100% reliable, height 10% 9718 * fluctuation; 9719 * - Android Chrome 86: haven't found a way to zoom a page (pinch doesn't change layout); 9720 * - Android Firefox 84: font size in accessibility settings changes all the CSS sizes, but offsetWidth and 9721 * getBoundingClientRect keep measuring with regular units, so the size reflects the font size setting and doesn't 9722 * fluctuate; 9723 * - IE 11, Edge 18: zoom 1/devicePixelRatio + offsetWidth = 100% reliable; 9724 * - IE 11, Edge 18: zoom 1/devicePixelRatio + getBoundingClientRect = reflects the zoom level; 9725 * - IE 11, Edge 18: offsetWidth = 100% reliable; 9726 * - IE 11, Edge 18: getBoundingClientRect = 100% reliable; 9727 */ 9728 return withIframe((_, iframeWindow) => { 9729 const iframeDocument = iframeWindow.document; 9730 const iframeBody = iframeDocument.body; 9731 const bodyStyle = iframeBody.style; 9732 bodyStyle.width = `${containerWidthPx}px`; 9733 bodyStyle.webkitTextSizeAdjust = bodyStyle.textSizeAdjust = 'none'; 9734 // See the big comment above 9735 if (isChromium()) { 9736 iframeBody.style.zoom = `${1 / iframeWindow.devicePixelRatio}`; 9737 } 9738 else if (isWebKit()) { 9739 iframeBody.style.zoom = 'reset'; 9740 } 9741 // See the big comment above 9742 const linesOfText = iframeDocument.createElement('div'); 9743 linesOfText.textContent = [...Array((containerWidthPx / 20) << 0)].map(() => 'word').join(' '); 9744 iframeBody.appendChild(linesOfText); 9745 return action(iframeDocument, iframeBody); 9746 }, '<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">'); 9747 } 9748 9749 function isPdfViewerEnabled() { 9750 return navigator.pdfViewerEnabled; 9751 } 9752 9753 /** 9754 * Unlike most other architectures, on x86/x86-64 when floating-point instructions 9755 * have no NaN arguments, but produce NaN output, the output NaN has sign bit set. 9756 * We use it to distinguish x86/x86-64 from other architectures, by doing subtraction 9757 * of two infinities (must produce NaN per IEEE 754 standard). 9758 * 9759 * See https://codebrowser.bddppq.com/pytorch/pytorch/third_party/XNNPACK/src/init.c.html#79 9760 */ 9761 function getArchitecture() { 9762 const f = new Float32Array(1); 9763 const u8 = new Uint8Array(f.buffer); 9764 f[0] = Infinity; 9765 f[0] = f[0] - f[0]; 9766 return u8[3]; 9767 } 9768 9769 /** 9770 * The return type is a union instead of the enum, because it's too challenging to embed the const enum into another 9771 * project. Turning it into a union is a simple and an elegant solution. 9772 */ 9773 function getApplePayState() { 9774 const { ApplePaySession } = window; 9775 if (typeof (ApplePaySession === null || ApplePaySession === void 0 ? void 0 : ApplePaySession.canMakePayments) !== 'function') { 9776 return -1 /* ApplePayState.NoAPI */; 9777 } 9778 if (willPrintConsoleError()) { 9779 return -3 /* ApplePayState.NotAvailableInFrame */; 9780 }
vendor: 24,098 bytes, lines 9781-10311
9781 try { 9782 return ApplePaySession.canMakePayments() ? 1 /* ApplePayState.Enabled */ : 0 /* ApplePayState.Disabled */; 9783 } 9784 catch (error) { 9785 return getStateFromError(error); 9786 } 9787 } 9788 /** 9789 * Starting from Safari 15 calling `ApplePaySession.canMakePayments()` produces this error message when FingerprintJS 9790 * runs in an iframe with a cross-origin parent page, and the iframe on that page has no allow="payment *" attribute: 9791 * Feature policy 'Payment' check failed for element with origin 'https://example.com' and allow attribute ''. 9792 * This function checks whether the error message is expected. 9793 * 9794 * We check for cross-origin parents, which is prone to false-positive results. Instead, we should check for allowed 9795 * feature/permission, but we can't because none of these API works in Safari yet: 9796 * navigator.permissions.query({ name: âpayment' }) 9797 * navigator.permissions.query({ name: âpayment-handler' }) 9798 * document.featurePolicy 9799 */ 9800 const willPrintConsoleError = isAnyParentCrossOrigin; 9801 function getStateFromError(error) { 9802 // See full expected error messages in the test 9803 if (error instanceof Error && error.name === 'InvalidAccessError' && /\bfrom\b.*\binsecure\b/i.test(error.message)) { 9804 return -2 /* ApplePayState.NotAvailableInInsecureContext */; 9805 } 9806 throw error; 9807 } 9808 9809 /** 9810 * Checks whether the Safari's Privacy Preserving Ad Measurement setting is on. 9811 * The setting is on when the value is not undefined. 9812 * A.k.a. private click measurement, privacy-preserving ad attribution. 9813 * 9814 * Unfortunately, it doesn't work in mobile Safari. 9815 * Probably, it will start working in mobile Safari or stop working in desktop Safari later. 9816 * We've found no way to detect the setting state in mobile Safari. Help wanted. 9817 * 9818 * @see https://webkit.org/blog/11529/introducing-private-click-measurement-pcm/ 9819 * @see https://developer.apple.com/videos/play/wwdc2021/10033 9820 */ 9821 function getPrivateClickMeasurement() { 9822 var _a; 9823 const link = document.createElement('a'); 9824 const sourceId = (_a = link.attributionSourceId) !== null && _a !== void 0 ? _a : link.attributionsourceid; 9825 return sourceId === undefined ? undefined : String(sourceId); 9826 } 9827 9828 /** WebGl context is not available */ 9829 const STATUS_NO_GL_CONTEXT = -1; 9830 /** WebGL context `getParameter` method is not a function */ 9831 const STATUS_GET_PARAMETER_NOT_A_FUNCTION = -2; 9832 const validContextParameters = new Set([ 9833 10752, 2849, 2884, 2885, 2886, 2928, 2929, 2930, 2931, 2932, 2960, 2961, 2962, 2963, 2964, 2965, 2966, 2967, 2968, 9834 2978, 3024, 3042, 3088, 3089, 3106, 3107, 32773, 32777, 32777, 32823, 32824, 32936, 32937, 32938, 32939, 32968, 32969, 9835 32970, 32971, 3317, 33170, 3333, 3379, 3386, 33901, 33902, 34016, 34024, 34076, 3408, 3410, 3411, 3412, 3413, 3414, 9836 3415, 34467, 34816, 34817, 34818, 34819, 34877, 34921, 34930, 35660, 35661, 35724, 35738, 35739, 36003, 36004, 36005, 9837 36347, 36348, 36349, 37440, 37441, 37443, 7936, 7937, 7938, 9838 // SAMPLE_ALPHA_TO_COVERAGE (32926) and SAMPLE_COVERAGE (32928) are excluded because they trigger a console warning 9839 // in IE, Chrome ⤠59 and Safari ⤠13 and give no entropy. 9840 ]); 9841 const validExtensionParams = new Set([ 9842 34047, 9843 35723, 9844 36063, 9845 34852, 9846 34853, 9847 34854, 9848 34229, 9849 36392, 9850 36795, 9851 38449, // MAX_VIEWS_OVR 9852 ]); 9853 const shaderTypes = ['FRAGMENT_SHADER', 'VERTEX_SHADER']; 9854 const precisionTypes = ['LOW_FLOAT', 'MEDIUM_FLOAT', 'HIGH_FLOAT', 'LOW_INT', 'MEDIUM_INT', 'HIGH_INT']; 9855 const rendererInfoExtensionName = 'WEBGL_debug_renderer_info'; 9856 const polygonModeExtensionName = 'WEBGL_polygon_mode'; 9857 /** 9858 * Gets the basic and simple WebGL parameters 9859 */ 9860 function getWebGlBasics({ cache }) { 9861 var _a, _b, _c, _d, _e, _f; 9862 const gl = getWebGLContext(cache); 9863 if (!gl) { 9864 return STATUS_NO_GL_CONTEXT; 9865 } 9866 if (!isValidParameterGetter(gl)) { 9867 return STATUS_GET_PARAMETER_NOT_A_FUNCTION; 9868 } 9869 const debugExtension = shouldAvoidDebugRendererInfo() ? null : gl.getExtension(rendererInfoExtensionName); 9870 return { 9871 version: ((_a = gl.getParameter(gl.VERSION)) === null || _a === void 0 ? void 0 : _a.toString()) || '', 9872 vendor: ((_b = gl.getParameter(gl.VENDOR)) === null || _b === void 0 ? void 0 : _b.toString()) || '', 9873 vendorUnmasked: debugExtension ? (_c = gl.getParameter(debugExtension.UNMASKED_VENDOR_WEBGL)) === null || _c === void 0 ? void 0 : _c.toString() : '', 9874 renderer: ((_d = gl.getParameter(gl.RENDERER)) === null || _d === void 0 ? void 0 : _d.toString()) || '', 9875 rendererUnmasked: debugExtension ? (_e = gl.getParameter(debugExtension.UNMASKED_RENDERER_WEBGL)) === null || _e === void 0 ? void 0 : _e.toString() : '', 9876 shadingLanguageVersion: ((_f = gl.getParameter(gl.SHADING_LANGUAGE_VERSION)) === null || _f === void 0 ? void 0 : _f.toString()) || '', 9877 }; 9878 } 9879 /** 9880 * Gets the advanced and massive WebGL parameters and extensions 9881 */ 9882 function getWebGlExtensions({ cache }) { 9883 const gl = getWebGLContext(cache); 9884 if (!gl) { 9885 return STATUS_NO_GL_CONTEXT; 9886 } 9887 if (!isValidParameterGetter(gl)) { 9888 return STATUS_GET_PARAMETER_NOT_A_FUNCTION; 9889 } 9890 const extensions = gl.getSupportedExtensions(); 9891 const contextAttributes = gl.getContextAttributes(); 9892 const unsupportedExtensions = []; 9893 // Features 9894 const attributes = []; 9895 const parameters = []; 9896 const extensionParameters = []; 9897 const shaderPrecisions = []; 9898 // Context attributes 9899 if (contextAttributes) { 9900 for (const attributeName of Object.keys(contextAttributes)) { 9901 attributes.push(`${attributeName}=${contextAttributes[attributeName]}`); 9902 } 9903 } 9904 // Context parameters 9905 const constants = getConstantsFromPrototype(gl); 9906 for (const constant of constants) { 9907 const code = gl[constant]; 9908 parameters.push(`${constant}=${code}${validContextParameters.has(code) ? `=${gl.getParameter(code)}` : ''}`); 9909 } 9910 // Extension parameters 9911 if (extensions) { 9912 for (const name of extensions) { 9913 if ((name === rendererInfoExtensionName && shouldAvoidDebugRendererInfo()) || 9914 (name === polygonModeExtensionName && shouldAvoidPolygonModeExtensions())) { 9915 continue; 9916 } 9917 const extension = gl.getExtension(name); 9918 if (!extension) { 9919 unsupportedExtensions.push(name); 9920 continue; 9921 } 9922 for (const constant of getConstantsFromPrototype(extension)) { 9923 const code = extension[constant]; 9924 extensionParameters.push(`${constant}=${code}${validExtensionParams.has(code) ? `=${gl.getParameter(code)}` : ''}`); 9925 } 9926 } 9927 } 9928 // Shader precision 9929 for (const shaderType of shaderTypes) { 9930 for (const precisionType of precisionTypes) { 9931 const shaderPrecision = getShaderPrecision(gl, shaderType, precisionType); 9932 shaderPrecisions.push(`${shaderType}.${precisionType}=${shaderPrecision.join(',')}`); 9933 } 9934 } 9935 // Postprocess 9936 extensionParameters.sort(); 9937 parameters.sort(); 9938 return { 9939 contextAttributes: attributes, 9940 parameters: parameters, 9941 shaderPrecisions: shaderPrecisions, 9942 extensions: extensions, 9943 extensionParameters: extensionParameters, 9944 unsupportedExtensions, 9945 }; 9946 } 9947 /** 9948 * This function usually takes the most time to execute in all the sources, therefore we cache its result. 9949 * 9950 * Warning for package users: 9951 * This function is out of Semantic Versioning, i.e. can change unexpectedly. Usage is at your own risk. 9952 */ 9953 function getWebGLContext(cache) { 9954 if (cache.webgl) { 9955 return cache.webgl.context; 9956 } 9957 const canvas = document.createElement('canvas'); 9958 let context; 9959 canvas.addEventListener('webglCreateContextError', () => (context = undefined)); 9960 for (const type of ['webgl', 'experimental-webgl']) { 9961 try { 9962 context = canvas.getContext(type); 9963 } 9964 catch (_a) { 9965 // Ok, continue 9966 } 9967 if (context) { 9968 break; 9969 } 9970 } 9971 cache.webgl = { context }; 9972 return context; 9973 } 9974 /** 9975 * https://developer.mozilla.org/en-US/docs/Web/API/WebGLShaderPrecisionFormat 9976 * https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderPrecisionFormat 9977 * https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.12 9978 */ 9979 function getShaderPrecision(gl, shaderType, precisionType) { 9980 const shaderPrecision = gl.getShaderPrecisionFormat(gl[shaderType], gl[precisionType]); 9981 return shaderPrecision ? [shaderPrecision.rangeMin, shaderPrecision.rangeMax, shaderPrecision.precision] : []; 9982 } 9983 function getConstantsFromPrototype(obj) { 9984 // eslint-disable-next-line @typescript-eslint/no-explicit-any 9985 const keys = Object.keys(obj.__proto__); 9986 return keys.filter(isConstantLike); 9987 } 9988 function isConstantLike(key) { 9989 return typeof key === 'string' && !key.match(/[^A-Z0-9_x]/); 9990 } 9991 /** 9992 * Some browsers print a console warning when the WEBGL_debug_renderer_info extension is requested. 9993 * JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers. 9994 */ 9995 function shouldAvoidDebugRendererInfo() { 9996 return isGecko(); 9997 } 9998 /** 9999 * Some browsers print a console warning when the WEBGL_polygon_mode extension is requested. 10000 * JS Agent aims to avoid printing messages to console, so we avoid this extension in that browsers. 10001 */ 10002 function shouldAvoidPolygonModeExtensions() { 10003 return isChromium() || isWebKit(); 10004 } 10005 /** 10006 * Some unknown browsers have no `getParameter` method 10007 */ 10008 function isValidParameterGetter(gl) { 10009 return typeof gl.getParameter === 'function'; 10010 } 10011 10012 function getAudioContextBaseLatency() { 10013 // The signal emits warning in Chrome and Firefox, therefore it is enabled on Safari where it doesn't produce warning 10014 // and on Android where it's less visible 10015 const isAllowedPlatform = isAndroid() || isWebKit(); 10016 if (!isAllowedPlatform) { 10017 return -2 /* SpecialFingerprint.Disabled */; 10018 } 10019 if (!window.AudioContext) { 10020 return -1 /* SpecialFingerprint.NotSupported */; 10021 } 10022 const latency = new AudioContext().baseLatency; 10023 if (latency === null || latency === undefined) { 10024 return -1 /* SpecialFingerprint.NotSupported */; 10025 } 10026 if (!isFinite(latency)) { 10027 return -3 /* SpecialFingerprint.NotFinite */; 10028 } 10029 return latency; 10030 } 10031 10032 /** 10033 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/resolvedOptions 10034 * 10035 * The return type is a union instead of a const enum due to the difficulty of embedding const enums in other projects. 10036 * This makes integration simpler and more elegant. 10037 */ 10038 function getDateTimeLocale() { 10039 if (!window.Intl) { 10040 return -1 /* Status.IntlAPINotSupported */; 10041 } 10042 const DateTimeFormat = window.Intl.DateTimeFormat; 10043 if (!DateTimeFormat) { 10044 return -2 /* Status.DateTimeFormatNotSupported */; 10045 } 10046 const locale = DateTimeFormat().resolvedOptions().locale; 10047 if (!locale && locale !== '') { 10048 return -3 /* Status.LocaleNotAvailable */; 10049 } 10050 return locale; 10051 } 10052 10053 /** 10054 * The list of entropy sources used to make visitor identifiers. 10055 * 10056 * This value isn't restricted by Semantic Versioning, i.e. it may be changed without bumping minor or major version of 10057 * this package. 10058 * 10059 * Note: Rollup and Webpack are smart enough to remove unused properties of this object during tree-shaking, so there is 10060 * no need to export the sources individually. 10061 */ 10062 const sources = { 10063 // READ FIRST: 10064 // See https://github.com/fingerprintjs/fingerprintjs/blob/master/contributing.md#how-to-add-an-entropy-source 10065 // to learn how entropy source works and how to make your own. 10066 // The sources run in this exact order. 10067 // The asynchronous sources are at the start to run in parallel with other sources. 10068 fonts: getFonts, 10069 domBlockers: getDomBlockers, 10070 fontPreferences: getFontPreferences, 10071 audio: getAudioFingerprint, 10072 screenFrame: getScreenFrame, 10073 canvas: getCanvasFingerprint, 10074 osCpu: getOsCpu, 10075 languages: getLanguages, 10076 colorDepth: getColorDepth, 10077 deviceMemory: getDeviceMemory, 10078 screenResolution: getScreenResolution, 10079 hardwareConcurrency: getHardwareConcurrency, 10080 timezone: getTimezone, 10081 sessionStorage: getSessionStorage, 10082 localStorage: getLocalStorage, 10083 indexedDB: getIndexedDB, 10084 openDatabase: getOpenDatabase, 10085 cpuClass: getCpuClass, 10086 platform: getPlatform, 10087 plugins: getPlugins, 10088 touchSupport: getTouchSupport, 10089 vendor: getVendor, 10090 vendorFlavors: getVendorFlavors, 10091 cookiesEnabled: areCookiesEnabled, 10092 colorGamut: getColorGamut, 10093 invertedColors: areColorsInverted, 10094 forcedColors: areColorsForced, 10095 monochrome: getMonochromeDepth, 10096 contrast: getContrastPreference, 10097 reducedMotion: isMotionReduced, 10098 reducedTransparency: isTransparencyReduced, 10099 hdr: isHDR, 10100 math: getMathFingerprint, 10101 pdfViewerEnabled: isPdfViewerEnabled, 10102 architecture: getArchitecture, 10103 applePay: getApplePayState, 10104 privateClickMeasurement: getPrivateClickMeasurement, 10105 audioBaseLatency: getAudioContextBaseLatency, 10106 dateTimeLocale: getDateTimeLocale, 10107 // Some sources can affect other sources (e.g. WebGL can affect canvas), so it's important to run these sources 10108 // after other sources. 10109 webGlBasics: getWebGlBasics, 10110 webGlExtensions: getWebGlExtensions, 10111 }; 10112 /** 10113 * Loads the built-in entropy sources. 10114 * Returns a function that collects the entropy components to make the visitor identifier. 10115 */ 10116 function loadBuiltinSources(options) { 10117 return loadSources(sources, options, []); 10118 } 10119 10120 const commentTemplate = '$ if upgrade to Pro: https://fingerprint.com/github/?utm_source=oss&utm_medium=referral&utm_campaign=confidence_score'; 10121 function getConfidence(components) { 10122 const openConfidenceScore = getOpenConfidenceScore(components); 10123 const proConfidenceScore = deriveProConfidenceScore(openConfidenceScore); 10124 return { score: openConfidenceScore, comment: commentTemplate.replace(/\$/g, `${proConfidenceScore}`) }; 10125 } 10126 function getOpenConfidenceScore(components) { 10127 // In order to calculate the true probability of the visitor identifier being correct, we need to know the number of 10128 // website visitors (the higher the number, the less the probability because the fingerprint entropy is limited). 10129 // JS agent doesn't know the number of visitors, so we can only do an approximate assessment. 10130 if (isAndroid()) { 10131 return 0.4; 10132 } 10133 // Safari (mobile and desktop) 10134 if (isWebKit()) { 10135 return isDesktopWebKit() && !(isWebKit616OrNewer() && isSafariWebKit()) ? 0.5 : 0.3; 10136 } 10137 const platform = 'value' in components.platform ? components.platform.value : ''; 10138 // Windows 10139 if (/^Win/.test(platform)) { 10140 // The score is greater than on macOS because of the higher variety of devices running Windows. 10141 // Chrome provides more entropy than Firefox according too 10142 // https://netmarketshare.com/browser-market-share.aspx?options=%7B%22filter%22%3A%7B%22%24and%22%3A%5B%7B%22platform%22%3A%7B%22%24in%22%3A%5B%22Windows%22%5D%7D%7D%5D%7D%2C%22dateLabel%22%3A%22Trend%22%2C%22attributes%22%3A%22share%22%2C%22group%22%3A%22browser%22%2C%22sort%22%3A%7B%22share%22%3A-1%7D%2C%22id%22%3A%22browsersDesktop%22%2C%22dateInterval%22%3A%22Monthly%22%2C%22dateStart%22%3A%222019-11%22%2C%22dateEnd%22%3A%222020-10%22%2C%22segments%22%3A%22-1000%22%7D 10143 // So we assign the same score to them. 10144 return 0.6; 10145 } 10146 // macOS 10147 if (/^Mac/.test(platform)) { 10148 // Chrome provides more entropy than Safari and Safari provides more entropy than Firefox. 10149 // Chrome is more popular than Safari and Safari is more popular than Firefox according to 10150 // https://netmarketshare.com/browser-market-share.aspx?options=%7B%22filter%22%3A%7B%22%24and%22%3A%5B%7B%22platform%22%3A%7B%22%24in%22%3A%5B%22Mac%20OS%22%5D%7D%7D%5D%7D%2C%22dateLabel%22%3A%22Trend%22%2C%22attributes%22%3A%22share%22%2C%22group%22%3A%22browser%22%2C%22sort%22%3A%7B%22share%22%3A-1%7D%2C%22id%22%3A%22browsersDesktop%22%2C%22dateInterval%22%3A%22Monthly%22%2C%22dateStart%22%3A%222019-11%22%2C%22dateEnd%22%3A%222020-10%22%2C%22segments%22%3A%22-1000%22%7D 10151 // So we assign the same score to them. 10152 return 0.5; 10153 } 10154 // Another platform, e.g. a desktop Linux. It's rare, so it should be pretty unique. 10155 return 0.7; 10156 } 10157 function deriveProConfidenceScore(openConfidenceScore) { 10158 return round(0.99 + 0.01 * openConfidenceScore, 0.0001); 10159 } 10160 10161 function componentsToCanonicalString(components) { 10162 let result = ''; 10163 for (const componentKey of Object.keys(components).sort()) { 10164 const component = components[componentKey]; 10165 const value = 'error' in component ? 'error' : JSON.stringify(component.value); 10166 result += `${result ? '|' : ''}${componentKey.replace(/([:|\\])/g, '\\$1')}:${value}`; 10167 } 10168 return result; 10169 } 10170 function componentsToDebugString(components) { 10171 return JSON.stringify(components, (_key, value) => { 10172 if (value instanceof Error) { 10173 return errorToObject(value); 10174 } 10175 return value; 10176 }, 2); 10177 } 10178 function hashComponents(components) { 10179 return x64hash128(componentsToCanonicalString(components)); 10180 } 10181 /** 10182 * Makes a GetResult implementation that calculates the visitor id hash on demand. 10183 * Designed for optimisation. 10184 */ 10185 function makeLazyGetResult(components) { 10186 let visitorIdCache; 10187 // This function runs very fast, so there is no need to make it lazy 10188 const confidence = getConfidence(components); 10189 // A plain class isn't used because its getters and setters aren't enumerable. 10190 return { 10191 get visitorId() { 10192 if (visitorIdCache === undefined) { 10193 visitorIdCache = hashComponents(this.components); 10194 } 10195 return visitorIdCache; 10196 }, 10197 set visitorId(visitorId) { 10198 visitorIdCache = visitorId; 10199 }, 10200 confidence, 10201 components, 10202 version, 10203 }; 10204 } 10205 /** 10206 * A delay is required to ensure consistent entropy components. 10207 * See https://github.com/fingerprintjs/fingerprintjs/issues/254 10208 * and https://github.com/fingerprintjs/fingerprintjs/issues/307 10209 * and https://github.com/fingerprintjs/fingerprintjs/commit/945633e7c5f67ae38eb0fea37349712f0e669b18 10210 */ 10211 function prepareForSources(delayFallback = 50) { 10212 // A proper deadline is unknown. Let it be twice the fallback timeout so that both cases have the same average time. 10213 return requestIdleCallbackIfAvailable(delayFallback, delayFallback * 2); 10214 } 10215 /** 10216 * The function isn't exported from the index file to not allow to call it without `load()`. 10217 * The hiding gives more freedom for future non-breaking updates. 10218 * 10219 * A factory function is used instead of a class to shorten the attribute names in the minified code. 10220 * Native private class fields could've been used, but TypeScript doesn't allow them with `"target": "es5"`. 10221 */ 10222 function makeAgent(getComponents, debug) { 10223 const creationTime = Date.now(); 10224 return { 10225 async get(options) { 10226 const startTime = Date.now(); 10227 const components = await getComponents(); 10228 const result = makeLazyGetResult(components); 10229 if (debug || (options === null || options === void 0 ? void 0 : options.debug)) { 10230 // console.log is ok here because it's under a debug clause 10231 // eslint-disable-next-line no-console 10232 console.log(`Copy the text below to get the debug data: 10233 10234\`\`\` 10235version: ${result.version} 10236userAgent: ${navigator.userAgent} 10237timeBetweenLoadAndGet: ${startTime - creationTime} 10238visitorId: ${result.visitorId} 10239components: ${componentsToDebugString(components)} 10240\`\`\``); 10241 } 10242 return result; 10243 }, 10244 }; 10245 } 10246 /** 10247 * Sends an unpersonalized AJAX request to collect installation statistics 10248 */ 10249 function monitor() { 10250 // The FingerprintJS CDN (https://github.com/fingerprintjs/cdn) replaces `window.__fpjs_d_m` with `true` 10251 if (window.__fpjs_d_m || Math.random() >= 0.001) { 10252 return; 10253 } 10254 try { 10255 const request = new XMLHttpRequest(); 10256 request.open('get', `https://m1.openfpcdn.io/fingerprintjs/v${version}/npm-monitoring`, true); 10257 request.send(); 10258 } 10259 catch (error) { 10260 // console.error is ok here because it's an unexpected error handler 10261 // eslint-disable-next-line no-console 10262 console.error(error); 10263 } 10264 } 10265 /** 10266 * Builds an instance of Agent and waits a delay required for a proper operation. 10267 */ 10268 async function load(options = {}) { 10269 const { delayFallback, debug, monitoring = true } = options; 10270 if (monitoring) { 10271 monitor(); 10272 } 10273 await prepareForSources(delayFallback); 10274 const getComponents = loadBuiltinSources({ cache: {}, debug }); 10275 return makeAgent(getComponents, debug); 10276 } 10277 10278 // The default export is a syntax sugar (`import * as FP from '...' â import FP from '...'`). 10279 // It should contain all the public exported values. 10280 var index = { load, hashComponents, componentsToDebugString }; 10281 // The exports below are for private usage. They may change unexpectedly. Use them at your own risk. 10282 /** Not documented, out of Semantic Versioning, usage is at your own risk */ 10283 const murmurX64Hash128 = x64hash128; 10284 10285 exports.componentsToDebugString = componentsToDebugString; 10286 exports.default = index; 10287 exports.getFullscreenElement = getFullscreenElement; 10288 exports.getUnstableAudioFingerprint = getUnstableAudioFingerprint; 10289 exports.getUnstableCanvasFingerprint = getUnstableCanvasFingerprint; 10290 exports.getUnstableHardwareConcurrency = getUnstableHardwareConcurrency; 10291 exports.getUnstableScreenFrame = getUnstableScreenFrame; 10292 exports.getUnstableScreenResolution = getUnstableScreenResolution; 10293 exports.getWebGLContext = getWebGLContext; 10294 exports.hashComponents = hashComponents; 10295 exports.isAndroid = isAndroid; 10296 exports.isChromium = isChromium; 10297 exports.isDesktopWebKit = isDesktopWebKit; 10298 exports.isEdgeHTML = isEdgeHTML; 10299 exports.isGecko = isGecko; 10300 exports.isSamsungInternet = isSamsungInternet; 10301 exports.isTrident = isTrident; 10302 exports.isWebKit = isWebKit; 10303 exports.load = load; 10304 exports.loadSources = loadSources; 10305 exports.murmurX64Hash128 = murmurX64Hash128; 10306 exports.prepareForSources = prepareForSources; 10307 exports.sources = sources; 10308 exports.transformSource = transformSource; 10309 exports.withIframe = withIframe; 10310 10311 Object.defineProperty(exports, '__esModule', { value: true });
10312 10313 return exports; 10314 10315})({}); 10316 10317"use strict" 10318var VetrinaMotoriUtils = { 10319 UniqueList: function (originalList) { 10320 var result = new Array(); 10321 10322 $(originalList).each(function (index, item) { 10323 var exist = $.grep(result, function (it) { 10324 return it.MacroModelID == item.MacroModelID; 10325 }); 10326 if ($(exist).length === 0) { 10327 result.push(item); 10328 } 10329 }); 10330 10331 return result; 10332 }, 10333 constants: { 10334 MyMotoLoginUrl: "/Views/IFrame/Access/NetworkLogin.aspx?fs=v", 10335 MyMotoMenuAutoUrl: "/Views/IFrame/IUserMenuAuto.aspx?fs=v", 10336 MyMotoSubscriptionPrivateUrl: "/MyAuto/Views/IFrame/Subscription/Privato.aspx?fs=v", 10337 MyMotoSubscriptionUrl: "/Views/IFrame/Subscription/NetworkSubscription.aspx?fs=v", 10338 MyMotoSubscriptionDealerUrl: "/Views/IFrame/Subscription/NetworkSubscriptionDealer.aspx?fs=v", 10339 MyMotoMenuUrl: "/Views/IFrame/INetworkUserMenuVM.aspx?fs=v", 10340 MyMotoMobileMenuUrl: "/Views/IFrame/Mobile/INetworkUserMenuVM.aspx?fs=v", 10341 MyMotoCheckAuthentication: "/Views/Handlers/Authentication/CheckAuthentication.ashx", 10342 LoginMode: 'Network' 10343 }, 10344 TabNavigation: function () { 10345 //navigazione tab in tutto il sito 10346 var e = function (e) { 10347 $(".js-tab-content").each(function (i, t) { 10348 t.getAttribute("data-group") !== e && e || t.classList.remove("js-tab-initial", "js-tab-selected") 10349 }) 10350 }; 10351 10352 $(".js-tab-header").each(function (i, t) { 10353 t.addEventListener("click", function (event) { 10354 if ($(t).parents("#search-box-market").length == 0) { 10355 event.preventDefault(); 10356 } 10357 var n = t.getAttribute("data-target"), 10358 r = document.getElementById(n), 10359 o = t.getAttribute("data-group"); 10360 $.cookie("sitenav-data-target", n); 10361 $.cookie("sitenav-data-group", o); 10362 e(o), r && (r.className += " js-tab-selected"), $(".js-tab-header").each(function (index, e) { 10363 e.getAttribute("data-group") === o && e.classList.remove("js-tab-selected") 10364 }), t.classList.add("js-tab-selected") 10365 }) 10366 }); 10367 10368 // var navTarget = $.cookie("sitenav-data-target"); 10369 // var navGroup = $.cookie("sitenav-data-group"); 10370 // if (navTarget && navGroup) { 10371 // //$('*[data-customerID="22"]'); 10372 // var clickedElements = $(".js-tab-header[data-target='"+ navTarget+"'][data-group='"+navGroup+"'] "); 10373 // if (clickedElements.length > 0) { 10374 // clickedElements[0].click(); 10375 // } 10376 // } 10377 }, 10378 ShowNumber: function () { 10379 $(".phone-button").on("click", function (e) { 10380 var btn = this; 10381 $(btn).parent().addClass("show-number"); 10382 var span = $(btn).parent().children(".phone-number"); 10383 var uID = $(span).data('userinfo'); 10384 10385 $.ajax({
10386 url: '/ajax/putdata', 10387 type: 'POST', 10388 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10389 data: {methodName: 'account/getauthcodebuyserid', jsonParameters: JSON.stringify({UserID: uID})} 10390 }) 10391 .done(function (data) { 10392 if (data.Success) { 10393 var jsonData = JSON.parse(data["Result"]["ResultSet"]); 10394 var phone = jsonData[0]["Phone"]; 10395 $(span).text(phone); 10396 } 10397 }); 10398 }); 10399 }, 10400 ShowAllButton: function () { 10401 $(".btn-show-all").each(function (i, e) { 10402 $(e).on("click", function (e) { 10403 $("#sidebarcarousel-otherbrands").toggle(); 10404 $(this).toggleClass("opened"); 10405 }); 10406 }); 10407 }, 10408 GeoSearch: function () { 10409 var $body = $("body"), 10410 input = $("#geocomplete"), 10411 input_lat = $("#latitude"), 10412 input_lng = $("#longitude"), 10413 service = "/geo/place-suggest", 10414 event_name = "GetSearchChanged"; 10415 10416 console.log("GeoSearch init"); 10417 10418 input.autocomplete({ 10419 source: service, 10420 minLength: 1, 10421 select: function (event, ui) { 10422 10423 input_lng.val(ui.item.Longitude); 10424 10425 input_lat.val(ui.item.Latitude); 10426 10427 $body.trigger(event_name); 10428 } 10429 }); 10430 10431 10432 // clear underlaying values when needed 10433 input.keyup(function () { 10434 10435 if (input.val() === "") { 10436 input_lng.val(""); 10437 input_lat.val(""); 10438 10439 $body.trigger(event_name); 10440 } 10441 10442 }); 10443 }, 10444 IsLoggedIn: function () { 10445 return userAuthorized; 10446 }, 10447 SetupLogin: function () { 10448 $("#btn-auth-text").click(function (e) { 10449 //console.log("click"); 10450 e.preventDefault(); 10451 e.stopPropagation(); 10452 $(".login-panel").empty(); 10453 $(".login-panel").append($('<iframe/>', { 10454 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoLoginUrl + '&ru=' + envCompounded.LoginRedirectUrl, 10455 seamless: '', 10456 width: '100%', 10457 height: '100%' 10458 })); 10459 $(".login-panel").css("display", "block"); 10460 }); 10461 $("#btn-auth-text-mobile").click(function (e) { 10462 e.preventDefault(); 10463 e.stopPropagation(); 10464 $(".login-panel").empty(); 10465 $(".login-panel").append($('<iframe/>', { 10466 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoLoginUrl + '&ru=' + envCompounded.LoginRedirectUrl, 10467 seamless: '', 10468 width: '100%', 10469 border: '0', 10470 height: '100%' 10471 })); 10472 $(".login-panel").css("display", "block"); 10473 }); 10474 }, 10475 SetupSubscription: function () { 10476 $("#btn-register-text-mobile").click(function (e) { 10477 e.preventDefault(); 10478 e.stopPropagation(); 10479 $(".login-panel").empty(); 10480 $(".login-panel").append($('<iframe/>', { 10481 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoSubscriptionUrl + '&ru=' + envCompounded.LoginRedirectUrl, 10482 seamless: '', 10483 width: '100%', 10484 height: '100%' 10485 })); 10486 $(".login-panel").css("display", "block"); 10487 }); 10488 10489 $("#btn-dealer").click(function (e) { 10490 e.preventDefault(); 10491 e.stopPropagation(); 10492 $(".login-panel").empty(); 10493 $(".login-panel").append($('<iframe/>', { 10494 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoSubscriptionDealerUrl + '&ru=' + envCompounded.LoginRedirectUrl, 10495 seamless: '', 10496 width: '100%', 10497 height: '100%' 10498 })); 10499 $(".login-panel").css("display", "block"); 10500 }); 10501 10502 $("#btn-dealer-mobile").click(function (e) { 10503 e.preventDefault(); 10504 e.stopPropagation(); 10505 $(".login-panel").empty(); 10506 $(".login-panel").append($('<iframe/>', { 10507 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoSubscriptionDealerUrl + '&ru=' + envCompounded.LoginRedirectUrl, 10508 seamless: '', 10509 width: '100%', 10510 border: '0', 10511 height: '100%' 10512 })); 10513 $(".login-panel").css("display", "block"); 10514 }); 10515 }, 10516 SetupDashboard: function () { 10517 $("#btn-auth-logged").click(function (e) { 10518 e.preventDefault(); 10519 e.stopPropagation(); 10520 10521 if (window.matchMedia("(min-width: 1024px)").matches) { 10522 if ($(".logged-panel-desktop").hasClass('opened')) { 10523 $(".logged-panel-desktop").css("display", "none"); 10524 $(".logged-panel-desktop").removeClass("opened"); 10525 } else { 10526 $(".logged-panel-desktop").empty(); 10527 $(".logged-panel-desktop").append($('<iframe/>', { 10528 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoMenuUrl + '&ru=' + envCompounded.LogoutRedirectUrl, 10529 seamless: '', 10530 width: '100%', 10531 scrolling: 'no', 10532 height: '340' 10533 })); 10534 $(".logged-panel-desktop").css("display", "block"); 10535 $(".logged-panel-desktop").addClass("opened"); 10536 } 10537 10538 } else { 10539 $(".sidemenudx-logged-mobile-wrapper").empty(); 10540 $(".sidemenudx-logged-mobile-wrapper").append($('<iframe/>', { 10541 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoMobileMenuUrl + '&ru=' + envCompounded.LogoutRedirectUrl, 10542 seamless: '' 10543 })); 10544 $(".sidemenudx-login-wrapper").hide(); 10545 $('#btn-auth-logged').toggleClass('clicked'); 10546 $('.sidemenudx-overlay').toggleClass('show'); 10547 $('div.sidemenudx-mobileacc').toggleClass('show'); 10548 $('body').toggleClass('overflow'); 10549 $('html').toggleClass('freeze-body'); 10550 $(".sidemenudx-logged-mobile-wrapper").show(); 10551 } 10552 }); 10553 }, 10554 CheckNetworkLogin: function () { 10555 $.ajax({
10556 async: false, 10557 type: 'GET', 10558 crossDomain: true, 10559 dataType: 'jsonp', 10560 url: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoCheckAuthentication, 10561 done: function (result) { 10562 }, 10563 success: function (response) { 10564 if (response["Result"] == "0") { 10565 $.ajax({ 10566 type: 'POST', 10567 url: env.PortalUrl + '/Account/VMLogout', 10568 success: function (data) { 10569 document.location.reload(); 10570 } 10571 }); 10572 } 10573 }, 10574 fail: function (response) { 10575 alert('Si è verificato un errore: ' + response); 10576 } 10577 }); 10578 }, 10579 WarmUp: function () { 10580 }, 10581 ScrollIntoView: function () { 10582 var btnScrollTo = $("[data-scroll-to-view]"); 10583 $(btnScrollTo).each(function (i, item) { 10584 $(item).on("click", function (e) { 10585 var theView = $(item).attr("data-scroll-to-view"); 10586 var offset = $(theView).offset(); 10587 10588 $("html, body").animate({ 10589 scrollTop: offset.top - $('#content').offset().top + $('#content').scrollTop() - $("header").height() - 10, 10590 scrolLeft: offset.left 10591 }); 10592 }); 10593 }); 10594 }, 10595 FormSearchBy: function (vehictype) { 10596 $("a[data-group='sidebarsearch']").on('click', function (evt, ui) { 10597 10598 var vm = ko.dataFor(document.getElementById("form-search")); 10599 if (vm) { 10600 ko.cleanNode(document.getElementById("form-search")); 10601 $("#form-search").remove(); 10602 } 10603 10604 var divToFill = "#" + $(this).data("target"), 10605 url = $(this).data("url"), 10606 vehicle = $(this).data("vehicle"); 10607 10608 $.get(url, function (data) { 10609 $(divToFill).empty(); 10610 $(divToFill).html(data); 10611 $(divToFill).addClass("js-tab-selected"); 10612 10613 var path = $("#search-bind").attr('src'); 10614 if (vehicle === 'car') { 10615 path = path.replace('UsedNewCommercials', 'UsedNewCars'); 10616 path = path.replace('UsedNewMotorbikes', 'UsedNewCars'); 10617 } else if (vehicle === 'motorbike') { 10618 path = path.replace('UsedNewCommercials', 'UsedNewMotorbikes'); 10619 path = path.replace('UsedNewCars', 'UsedNewMotorbikes'); 10620 } else if (vehicle === 'commercial') { 10621 path = path.replace('UsedNewCars', 'UsedNewCommercials'); 10622 path = path.replace('UsedNewMotorbikes', 'UsedNewCommercials'); 10623 } 10624 10625 $("#search-bind").remove(); 10626 10627 $('<script>').attr({ 10628 src: path, 10629 id: 'search-bind' 10630 }).appendTo('body'); 10631 }); 10632 $("a[data-group='sidebarsearch']").unbind('click'); 10633 }); 10634 }, 10635 ImageGallery: function () { 10636 //image gllery nel dettaglio modalità originale 10637 var slideCount = $(".gallery-images-list li").length; 10638 10639 var myObject = { 10640 title: $(".detail-info > h1").html(), 10641 images: [] 10642 }; 10643 10644 var list = $(".gallery-images-list li"); 10645 $(list).each(function (index, item) { 10646 var fullimg = $(item).attr("data-fullimg"); 10647 var bckimge = $(item).first('span').css("background-image").slice(4, -1).replace(/"/g, ""); 10648 10649 myObject.images.push({ 10650 id: index, 10651 full: fullimg, 10652 bck: bckimge, 10653 elem: item 10654 }); 10655 }); 10656 10657 function movePrevious() { 10658 var currentimg = $(".gallery-current-image").css("background-image").slice(4, -1).replace(/"/g, ""); 10659 var current = $.grep(myObject.images, function (e) { 10660 return e.full == currentimg 10661 })[0]; 10662 10663 var previd = current.id - 1; 10664 if (previd < 0) { 10665 previd = slideCount - 1; 10666 } 10667 10668 $(current.elem).removeClass("active"); 10669 var find = $.grep(myObject.images, function (e) { 10670 return e.id == previd 10671 })[0]; 10672 $(".gallery-current-image").css("background-image", "url(" + find.full + ")"); 10673 $(find.elem).addClass("active"); 10674 }; 10675 10676 function moveNext() { 10677 var currentimg = $(".gallery-current-image").css("background-image").slice(4, -1).replace(/"/g, ""); 10678 var current = $.grep(myObject.images, function (e) { 10679 return e.full == currentimg 10680 })[0]; 10681 10682 var nextid = current.id + 1; 10683 if (nextid === slideCount) { 10684 nextid = 0; 10685 } 10686 10687 $(current.elem).removeClass("active"); 10688 var find = $.grep(myObject.images, function (e) { 10689 return e.id == nextid 10690 })[0]; 10691 $(".gallery-current-image").css("background-image", "url(" + find.full + ")"); 10692 $(find.elem).addClass("active"); 10693 }; 10694 10695 $(".gallery-previous").on('click', function () { 10696 movePrevious(); 10697 }); 10698 $(".gallery-next").on('click', function () { 10699 moveNext(); 10700 }); 10701 10702 $(".gallery-images-list li").each(function (index, item) { 10703 $(item).on("click", function (e) { 10704 var url = $(item).attr("data-fullimg"); 10705 $(".gallery-current-image").css("background-image", "url(" + url + ")"); 10706 10707 $(".gallery-images-list li.active").removeClass('active'); 10708 $(item).addClass("active"); 10709 }); 10710 }) 10711 }, 10712 SetBackPage: function () { 10713 $(window).on("beforeunload", function () { 10714 localStorage.setItem("currentsearch", document.location); 10715 localStorage.setItem("currentpage", document.location.hash); 10716 }); 10717 }, 10718 ManageFavorites: function (action, current, type) { 10719 var pm = new Object(); 10720 pm.AdID = current; 10721 var params = {methodName: 'account/' + type + '/favorite/' + action, jsonParameters: JSON.stringify(pm)}; 10722 10723 $.ajax({
10724 url: '/ajax/putdata', 10725 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10726 type: 'POST', 10727 data: params 10728 }) 10729 .done(function (data) { 10730 console.log(data); 10731 }); 10732 }, 10733 ManageFavoritsOnCookie: function (action, current, type) { 10734 var arCookies; 10735 var valcookies = $.cookie("favorites"); 10736 if (typeof valcookies === 'undefined' || !valcookies) { 10737 arCookies = [{'action': action, 'ID': current, 'type': type}]; 10738 } else { 10739 arCookies = $.grep($.parseJSON(valcookies), function (o) { 10740 return (o !== undefined && o.ID !== current) 10741 }); 10742 $.cookie("favorites", null); 10743 arCookies.push({'action': action, 'ID': current, 'type': type}); 10744 } 10745 $.cookie("favorites", JSON.stringify(arCookies)); 10746 }, 10747 DrawCaptcha: function () { 10748 grecaptcha.render('captchaContainer', { 10749 'sitekey': '6Lf7DD0UAAAAALDgnPdeUtLWQFTmrs8w4v1SpD--' 10750 }); 10751 }, 10752 FormSendToFriend: function () { 10753 var self = this; 10754 self.SenderName = ko.observable().extend({required: {message: "Inserisci il tupo nome"}}); 10755 self.SenderEmail = ko.observable().extend({required: {message: "inserisci la tua mail"}}); 10756 self.ContactEmail = ko.observable().extend({required: true});//.extend({ required: {message:"Inserisci il nome del tuo amico"} }); 10757 self.Message = ko.observable('Ciao,\r\n\tsu Vetrina Motori ho trovato questo annuncio che potrebbe interessarti.').extend({required: true}); //.extend({ required: true }); 10758 self.Url = ko.computed(function () { 10759 return document.location.href; 10760 }); 10761 self.Load = function () { 10762 $.ajax({
10763 url: '/ajax/putdata', 10764 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10765 type: 'POST', 10766 data: {methodName: 'account/getuserinfo', jsonParameters: JSON.stringify({Page: 1})}, 10767 dataType: 'JSON' 10768 }) 10769 .done(function (data) { 10770 if (data.Success) { 10771 var userInfo = $.parseJSON(data.Result.ResultSet)[0]; 10772 self.SenderName(userInfo.Name); 10773 self.SenderEmail(userInfo.Email); 10774 } 10775 }); 10776 }; 10777 self.Submit = function () { 10778 var apiBrandParam = new Object(); 10779 apiBrandParam.ContactEmail = self.ContactEmail(); 10780 apiBrandParam.SenderName = self.SenderName(); 10781 apiBrandParam.SenderEmail = self.SenderEmail(); 10782 apiBrandParam.Message = self.Message(); 10783 apiBrandParam.UrlToShare = self.Url(); 10784 $.ajax({
10785 url: '/ajax/putdata', 10786 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10787 type: 'POST', 10788 data: {methodName: 'common/sendtofriend', jsonParameters: JSON.stringify(apiBrandParam)}, 10789 dataType: 'JSON' 10790 }) 10791 .done(function (data) { 10792 10793 $('#myModalSendToFriend').modal({ 10794 backdrop: true 10795 }); 10796 10797 //alert("Annuncio inviato correttamente"); 10798 10799 // $("#send-to-friend").hide(); 10800 // $("#send-to-friend-success").show(); 10801 //window.location.reload(); 10802 }); 10803 }; 10804 self.PerformValdation = function () { 10805 ko.validation.group(self).showAllMessages(); 10806 10807 if (self.errors().length > 0) { 10808 console.log("Prevent something") 10809 } else { 10810 console.log("Good to go!") 10811 } 10812 }; 10813 }, 10814 ReportFraud: function () { 10815 var self = this; 10816 self.SenderName = ko.observable().extend({required: true}); 10817 self.SenderEmail = ko.observable().extend({required: true}); 10818 self.Message = ko.observable('').extend({required: true}); 10819 self.Subject = ko.computed(function () { 10820 return document.getElementsByTagName("title")[0].innerHTML; 10821 }); 10822 self.AcceptCondition = ko.observable(false).extend({required: true}), 10823 self.ChangeCondition = function () { 10824 self.AcceptCondition(!self.AcceptCondition()); 10825 if (self.AcceptCondition()) { 10826 $("#reportfraud-privacy-confirmation").hide(); 10827 } 10828 //console.log(self.AcceptCondition()); 10829 }, 10830 self.VehicType = ko.computed(function () { 10831 var currentUl = document.location.href; 10832 var segmntsUrl = currentUl.split('/'); 10833 10834 segmntsUrl = segmntsUrl.filter(function (n) { 10835 return n !== ""; 10836 }); 10837 var lastSegment = segmntsUrl[segmntsUrl.length - 1]; 10838 var segmentsLstSegment = lastSegment.split('-'); 10839 segmentsLstSegment = segmentsLstSegment.filter(function (n) { 10840 return n !== ""; 10841 }); 10842 var lasSegOfLast = segmentsLstSegment[segmentsLstSegment.length - 1]; 10843 10844 if (lasSegOfLast.match("^au")) { 10845 return "car"; 10846 } else if (lasSegOfLast.match("^an")) { 10847 return "car"; 10848 } else if (lasSegOfLast.match("^ae")) { 10849 return "car"; 10850 } else if (lasSegOfLast.match("^cn")) { 10851 return "car"; 10852 } else if (lasSegOfLast.match("^cu")) { 10853 return "car"; 10854 } else if (lasSegOfLast.match("^mn")) { 10855 return "motorbike"; 10856 } else if (lasSegOfLast.match("^mu")) { 10857 return "motorbike"; 10858 } else if (lasSegOfLast.match("^me")) { 10859 return "motorbike"; 10860 } 10861 return "unknown"; 10862 }); 10863 self.AdID = ko.pureComputed(function () { 10864 //return $($("span.value")[0]).text(); 10865 10866 var segmntsUrl = document.location.href.split('/').filter(function (n) { 10867 return n !== ""; 10868 }); 10869 var lastSegment = segmntsUrl[segmntsUrl.length - 1]; 10870 var segmentsLstSegment = lastSegment.split('-').filter(function (n) { 10871 return n !== ""; 10872 }); 10873 var lasSegOfLast = segmentsLstSegment[segmentsLstSegment.length - 1]; 10874 10875 var adId = lasSegOfLast 10876 .replace(/au/g, '') 10877 .replace(/an/g, '') 10878 .replace(/ae/g, '') 10879 .replace(/mu/g, '') 10880 .replace(/mn/g, '') 10881 .replace(/me/g, '') 10882 .replace(/cn/g, '') 10883 .replace(/cu/g, ''); 10884 10885 return adId; 10886 }); 10887 self.Load = function () { 10888 $.ajax({
10889 url: '/ajax/putdata', 10890 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10891 type: 'POST', 10892 data: {methodName: 'account/getuserinfo', jsonParameters: JSON.stringify({Page: 1})}, 10893 dataType: 'JSON' 10894 }) 10895 .done(function (data) { 10896 if (data.Success) { 10897 var userInfo = $.parseJSON(data.Result.ResultSet)[0]; 10898 self.SenderName(userInfo.Name); 10899 self.SenderEmail(userInfo.Email); 10900 } 10901 }); 10902 }; 10903 self.Submit = function () { 10904 if (!self.AcceptCondition()) { 10905 $("#reportfraud-privacy-confirmation").show(); 10906 return false; 10907 } else { 10908 var apiParam = new Object(); 10909 apiParam.AdID = self.AdID(); 10910 apiParam.SenderPropsParameters = new Object(); 10911 apiParam.SenderPropsParameters.CoreConsens = self.AcceptCondition(); 10912 apiParam.SenderPropsParameters.ContactName = self.SenderName(); 10913 apiParam.SenderPropsParameters.ContactEmail = self.SenderEmail(); 10914 apiParam.SenderPropsParameters.ContactMessage = self.Message(); 10915 10916 $.ajax({
10917 url: '/ajax/putdata', 10918 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 10919 type: 'POST', 10920 data: { 10921 methodName: 'market/' + self.VehicType() + '/reportfraud', 10922 jsonParameters: JSON.stringify(apiParam) 10923 }, 10924 dataType: 'JSON' 10925 }) 10926 .done(function (data) { 10927 $('#myModal').modal({ 10928 backdrop: true 10929 }); 10930 10931 //alert("Message sent successfully"); 10932 // $("#send-to-friend").hide(); 10933 // $("#send-to-friend-success").show(); 10934 //window.location.reload(); 10935 }); 10936 } 10937 }; 10938 }, 10939 ContactDealer: function () { 10940 var self = this; 10941 10942 self.SenderName = ko.observable().extend({required: true}); 10943 self.SenderEmail = ko.observable().extend({required: true}); 10944 self.SenderPhone = ko.observable().extend({required: true}); 10945 self.Message = ko.observable('').extend({required: true}); 10946 self.DealerName = ko.computed(function () { 10947 var dealername = $(".seller-info-top > h2").text(); 10948 return dealername; 10949 }); 10950 self.AdID = ko.pureComputed(function () { 10951 // return $($("span.value")[0]).text(); 10952 var segmntsUrl = document.location.href.split('/').filter(function (n) { 10953 return n !== ""; 10954 }); 10955 var lastSegment = segmntsUrl[segmntsUrl.length - 1]; 10956 var segmentsLstSegment = lastSegment.split('-').filter(function (n) { 10957 return n !== ""; 10958 }); 10959 var lasSegOfLast = segmentsLstSegment[segmentsLstSegment.length - 1]; 10960 10961 var adId = lasSegOfLast 10962 .replace(/au/g, '') 10963 .replace(/an/g, '') 10964 .replace(/ae/g, '') 10965 .replace(/mu/g, '') 10966 .replace(/mn/g, '') 10967 .replace(/me/g, '') 10968 .replace(/cn/g, '') 10969 .replace(/cu/g, ''); 10970 10971 return adId; 10972 }); 10973 self.Privacy1 = ko.observable(); 10974 self.Privacy2 = ko.observable(); 10975 self.CheckPrivacy1 = ko.computed({ 10976 read: function () { 10977 return self.Privacy1; 10978 }, 10979 write: function (value) { 10980 //console.log(value); 10981 $("#id_privacy1").prop("checked", !$("#id_privacy1").prop("checked")); 10982 10983 self.Privacy1($("#id_privacy1").prop("checked")); 10984 } 10985 }); 10986 self.CheckPrivacy2 = ko.computed({ 10987 read: function () { 10988 return self.Privacy2; 10989 }, 10990 write: function (value) { 10991 //console.log(value); 10992 $("#id_privacy2").prop("checked", !$("#id_privacy2").prop("checked")); 10993 10994 self.Privacy2($("#id_privacy2").prop("checked")); 10995 } 10996 }); 10997 self.VehicType = ko.computed(function () { 10998 var currentUl = document.location.href; 10999 var segmntsUrl = currentUl.split('/'); 11000 11001 segmntsUrl = segmntsUrl.filter(function (n) { 11002 return n !== ""; 11003 }); 11004 var lastSegment = segmntsUrl[segmntsUrl.length - 1]; 11005 var segmentsLstSegment = lastSegment.split('-'); 11006 segmentsLstSegment = segmentsLstSegment.filter(function (n) { 11007 return n !== ""; 11008 }); 11009 var lasSegOfLast = segmentsLstSegment[segmentsLstSegment.length - 1]; 11010 11011 if (lasSegOfLast.match("^da")) { 11012 return "car"; 11013 } else if (lasSegOfLast.match("^dm")) { 11014 return "motorbike"; 11015 } 11016 return "unknown"; 11017 }); 11018 self.Load = function () { 11019 $.ajax({
11020 url: '/ajax/putdata', 11021 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 11022 type: 'POST', 11023 data: {methodName: 'account/getuserinfo', jsonParameters: JSON.stringify({Page: 1})}, 11024 dataType: 'JSON' 11025 }) 11026 .done(function (data) { 11027 if (data.Success) { 11028 var userInfo = $.parseJSON(data.Result.ResultSet)[0]; 11029 self.SenderName(userInfo.Name); 11030 self.SenderEmail(userInfo.Email); 11031 self.SenderPhone(userInfo.Phone); 11032 } 11033 }); 11034 }; 11035 self.Submit = function () { 11036 if (!self.Privacy1() || !self.Privacy2()) { 11037 alert("E' necessario accettare le condizioni della privacy"); 11038 return; 11039 } 11040 var apiMessage = new Object(); 11041 apiMessage.AdID = self.AdID(); 11042 apiMessage.SenderPropsParameters = new Object(); 11043 apiMessage.SenderPropsParameters.SenderFullName = self.SenderName(); 11044 apiMessage.SenderPropsParameters.SenderEmail = self.SenderEmail(); 11045 apiMessage.SenderPropsParameters.SenderPhoneNumber = self.SenderPhone(); 11046 apiMessage.SenderPropsParameters.SenderMessage = self.Message(); 11047 11048 $.ajax({
11049 url: '/ajax/putdata', 11050 headers: {'__RequestVerificationToken': $('[name=__RequestVerificationToken]').val()}, 11051 type: 'POST', 11052 data: { 11053 methodName: 'market/' + self.VehicType() + '/contactseller', 11054 jsonParameters: JSON.stringify(apiMessage) 11055 }, 11056 dataType: 'JSON' 11057 }) 11058 .done(function (data) { 11059 $('html,body').animate({ 11060 scrollTop: $("#vehicle-contact-seller").offset().top 11061 }, 500); 11062 //alert("Messaggio inviato correttamente"); 11063 // window.location.reload(); 11064 }); 11065 } 11066 }, 11067 //Modal Form - BEGIN 11068 OpenModalWindow: function (url) { 11069 $.ajax({ 11070 url: url, 11071 type: 'GET', 11072 }).done(function (response) { 11073 $("div#update-form").empty(); 11074 $("div#update-form").append(response); 11075 $("#modalShared").removeClass("reportfraud"); 11076 11077 $(".modal-container").attr("id", "modalShared"); 11078 if (url.toString().indexOf("sendtofriendform") >= 0) { 11079 var sendTofriendForm = ko.validatedObservable(new VetrinaMotoriUtils.FormSendToFriend()); 11080 ko.applyBindings(sendTofriendForm, document.getElementById("send-to-friend")); 11081 sendTofriendForm().Load(); 11082 $("#myModalLabel").empty().text("Invia ad un amico"); 11083 } 11084 if (url.toString().indexOf("reportfraudform") >= 0) { 11085 var reportFraudFrm = new VetrinaMotoriUtils.ReportFraud(); 11086 ko.applyBindings(reportFraudFrm, document.getElementById("report-fraud")); 11087 reportFraudFrm.Load(); 11088 $("#myModalLabel").empty().text("Segnala una truffa"); 11089 $("#modalShared").addClass("reportfraud"); 11090 } 11091 if (url.toString().indexOf("SendeMessageFormDealer") >= 0) { 11092 var contactDealer = new VetrinaMotoriUtils.ContactDealer(); 11093 ko.applyBindings(contactDealer, document.getElementById("contact-dealer")); 11094 contactDealer.Load(); 11095 $("#myModalLabel").empty().text("Contatta il Concessionario"); 11096 } 11097 $("#modalShared").modal({show: true}); 11098 }); 11099 }, 11100 MenuNavigation: function (itemsel) { 11101 $('.vehicle-categories .vehicle-categories-bottom ul > li.selected').removeClass("selected"); 11102 $(itemsel).addClass("selected"); 11103 }, 11104 StickyButton: function () { 11105 //STICKY FILTER - BEGIN 11106 var win = $(window), 11107 canvas = $(".sidebar-search-form .js-tab-content.js-tab-selected").first(), //contenitore sticky bar 11108 css = { 11109 fixedbottom: "col-fixedbottom" 11110 }; 11111 11112 //container-fluid vehicle-filters vehicle-filters-category 11113 11114 //attivo check sticky solo se ho il canvas contenitore 11115 if ($(canvas).length > 0) { 11116 setupBottomOptions(); 11117 11118 function setupBottomOptions() { 11119 var checker = canvas.find(".bottom-checker")[0], 11120 opts = canvas.find(".btn-container"), 11121 min_h = 100; //minimum content visible area for becoming sticky 11122 11123 // search button always visibile 11124 win.on("scroll", function () { 11125 defineFixedStatus(); 11126 }); 11127 win.resize(function () { 11128 defineFixedStatus(); 11129 }); 11130 11131 11132 // check iniziale 11133 defineFixedStatus(); 11134 11135 function defineFixedStatus() { 11136 11137 //se ho elemento e container 11138 if ($(checker).length > 0 && $(opts).length) { 11139 11140 //rimuovo sticky se: 11141 //- sono su mobile 11142 //- la fine sotto del contenitore è nel viewport 11143 //- la fine sotto è più in alto 11144 //- l'altezza della porzione visibile del canvas è < dell'altezza minima richiesta 11145 if (isMobileView() || isElementInViewport(checker) 11146 || isElementUpperThanCurrentView(checker) 11147 || isVisibleContainerSmallerThanMinimumHeight(opts, canvas, min_h)) { 11148 canvas.removeClass(css.fixedbottom); 11149 opts.css("left", "0px"); 11150 } else { 11151 var left = canvas.offset().left; 11152 opts.css("left", left + "px"); 11153 canvas.addClass(css.fixedbottom); 11154 } 11155 } 11156 } 11157 11158 } 11159 11160 //se l'elemento cercato è nel viewport 11161 function isElementInViewport(el) { 11162 11163 var rect = el.getBoundingClientRect(); 11164 return ( 11165 rect.top >= 0 && 11166 rect.left >= 0 && 11167 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ 11168 rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ 11169 ); 11170 } 11171 11172 //se sono in dimensioni tablet o mobile 11173 function isMobileView() { 11174 return ($('.vmq-md').css('display') != 'block') && ($('.vmq-lg').css('display') != 'block') && ($('.vmq-xl').css('display') != 'block') && ($('.vmq-xxl').css('display') != 'block') 11175 } 11176 11177 //se l'elemento non è ancora entrato nel viewport 11178 function isElementUpperThanCurrentView(el) { 11179 11180 var scroll = win.scrollTop(); 11181 var el_top = $(el).offset().top; 11182 return el_top < scroll; 11183 } 11184 11185 //Se l'altezza della porzione visibile del canvas è < dell'altezza del contenitore 11186 function isVisibleContainerSmallerThanMinimumHeight(el, container, h) { 11187 11188 var cont_top = $(container).offset().top - win.scrollTop(); 11189 var cont_visiblepart = (window.innerHeight || document.documentElement.clientHeight) - cont_top; 11190 cont_visiblepart = (cont_visiblepart < 0) ? 0 : cont_visiblepart; 11191 var el_h = $(el).height() + h; 11192 11193 //console.log ("el_h: " + el_h + " - cont_visiblepart: " + cont_visiblepart + (el_h>cont_visiblepart)); 11194 11195 return (el_h > cont_visiblepart); 11196 } 11197 11198 //STICKY FILTER - END 11199 } 11200 }, 11201 OrderDataEvt: function () { 11202 var url = window.location.pathname; 11203 url = url.replace(/pag-(\d+).*$/g, ""); 11204 11205 var re = new RegExp("([?&])orderby=.*?(&|$)", "i"); 11206 var src = window.location.search; 11207 var separator = src.indexOf('?') !== -1 ? "&" : "?"; 11208 var selected = $("#orderby option:selected").val(); 11209 11210 if (src.match(re)) { 11211 src = src.replace(re, '$1' + "orderby=" + selected + '$2'); 11212 } else { 11213 src = src + separator + "orderby=" + selected; 11214 src = src.replace("&&", "&"); 11215 //url = url.slice(0, -1) + separator + "orderby=" + self.OrderData.SelectedOrder(); 11216 } 11217 window.location.href = url + src; 11218 }, 11219 SelectedOrderDefault: function () { 11220 var url = window.location.pathname; 11221 url = url.replace(/pag-(\d+).*$/g, ""); 11222 11223 var re = new RegExp("([?&])orderby=.*?(&|$)", "i"); 11224 var src = window.location.search; 11225 var separator = src.indexOf('?') !== -1 ? "&" : "?"; 11226 11227 if (src.match(re)) { 11228 //src = src.replace(re, '$1' + "orderby=" + self.OrderData.SelectedOrder() + '$2'); 11229 var orderval = src.match(re)[0].replace("orderby=", ""); 11230 orderval = orderval.replace("?", ""); 11231 orderval = orderval.replace("&", ""); 11232 $("#orderby").val(orderval); 11233 } else { 11234 $("#orderby").val("relevance-asc"); 11235 } 11236 }, 11237 BgText: function () { 11238 if ($(".filters-bg > img").length) { 11239 var el = $(".filters-bg > img"); 11240 var txel = $(".bg-text"); 11241 var io = el.offset(); 11242 if ($(window).width() > 1024) { 11243 io.left += el.width() * 0.41 + el.width() * 0.58 / 2 - txel.width() / 2; 11244 } else { 11245 io.left += el.width() / 2 - txel.width() / 2; 11246 } 11247 io.top += el.height() / 2 - txel.height() / 2; 11248 11249 $(".bg-text").offset(io); 11250 } 11251 }, 11252 IsCar: function () { 11253 var sUrl = window.location.href; 11254 return (sUrl.indexOf('ricerca-auto') >= 0 || sUrl.indexOf('auto-nuove') >= 0 || sUrl.indexOf('auto-usate') >= 0 || sUrl.indexOf('auto-epoca') >= 0); 11255 }, 11256 IsMotorbike: function () { 11257 var sUrl = window.location.href; 11258 return (sUrl.indexOf('ricerca-moto') >= 0 || sUrl.indexOf('moto-nuove') >= 0 || sUrl.indexOf('moto-usate') >= 0 || sUrl.indexOf('moto-epoca') >= 0); 11259 }, 11260 SetFavorite: function (current) { 11261 /*when is not logged in*/ 11262 if (!userAuthorized) { 11263 $(window).on("message", function (evt) { 11264 if (evt.originalEvent.data === "postLoginActionReload") { 11265 VetrinaMotoriUtils.Favorite(current); 11266 } 11267 }); 11268 $(".login-panel").empty(); 11269 $(".login-panel").append($('<iframe/>', { 11270 src: env.MyMotoUrl + VetrinaMotoriUtils.constants.MyMotoLoginUrl + '&ru=' + env.PortalUrl, 11271 seamless: '', 11272 width: '100%', 11273 height: '100%' 11274 })); 11275 $(".login-panel").css("display", "block"); 11276 } else { 11277 /*when is logged in*/ 11278 VetrinaMotoriUtils.Favorite(current); 11279 } 11280 }, 11281 Favorite: function (current) { 11282 /*set element added*/ 11283 $("#" + current + "-favorite").toggleClass("added"); 11284 var action = ''; 11285 var e = $("#" + current + "-favorite").attr("class").indexOf("added") > -1; 11286 11287 if (e) { 11288 action = 'create'; 11289 } else { 11290 action = 'delete'; 11291 } 11292 11293 var vehicType = 'car';
11294 if (VetrinaMotoriUtils.IsMotorbike()) { 11295 vehicType = 'motorbike' 11296 } 11297 var currentAd = $("#" + current + "-favorite"); 11298 VetrinaMotoriUtils.ManageFavorites(action, current, vehicType); 11299 }, 11300 Distance: function (lon1, lat1, lon2, lat2) { 11301 var R = 6371; // Radius of the earth in km 11302 var dLat = VetrinaMotoriUtils.toRad(lat2 - lat1); // Javascript functions in radians 11303 var dLon = VetrinaMotoriUtils.toRad(lon2 - lon1); 11304 var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 11305 Math.cos(VetrinaMotoriUtils.toRad(lat1)) * Math.cos(VetrinaMotoriUtils.toRad(lat2)) * 11306 Math.sin(dLon / 2) * Math.sin(dLon / 2); 11307 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 11308 var d = R * c; // Distance in km 11309 return d; 11310 }, 11311 toRad: function (value) { 11312 return value * Math.PI / 180; 11313 } 11314}; 11315 11316function CategoryClassByUrl(url) { 11317 if (url) { 11318 if (url.indexOf("segmento-") < 0) { 11319 url = '/segmento-' + url.substring(1); 11320 } 11321 return 'vehicle-type vehicle-type-' + url.substring(1); 11322 } 11323 return 'vehicle-type vehicle-type-segmento-berlina'; 11324} 11325 11326 11327(function ($, sr) { 11328 11329 // debouncing function from John Hann 11330 // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ 11331 var debounce = function (func, threshold, execAsap) { 11332 var timeout; 11333 11334 return function debounced() { 11335 var obj = this, args = arguments; 11336 11337 function delayed() { 11338 if (!execAsap) 11339 func.apply(obj, args); 11340 timeout = null; 11341 }; 11342 11343 if (timeout) 11344 clearTimeout(timeout); 11345 else if (execAsap) 11346 func.apply(obj, args); 11347 11348 timeout = setTimeout(delayed, threshold || 100); 11349 }; 11350 } 11351 // smartresize 11352 jQuery.fn[sr] = function (fn) { 11353 return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); 11354 }; 11355 11356})(jQuery, 'smartresize'); 11357 11358 11359//Logged Menu - BEGIN 11360AdjustLoggedMenuSize = function () { 11361 11362 //se il logged menu è aperto 11363 if ($("#account-dropdown").hasClass("opened")) { 11364 11365 //setto l'altezza di #account-dropdown uguale al contenuto 11366 $("#account-dropdown").css("height", $("#account-dropdown .auth-content").css("height")); 11367 11368 //se l'altezza del menu più la distanza dal top è SUPERIORE all'altezza dello schermo, quindi il menu esce 11369 // setto l'altezza del menu = altezza schermo 11370 if ($("#account-dropdown").height() + parseInt($("#account-dropdown").css("top"), 10) > $(window).height()) { 11371 $("#account-dropdown").height($(window).height() - parseInt($("#account-dropdown").css("top"), 10)); 11372 $("#account-dropdown .auth-content").height($("#account-dropdown").height() - parseInt($("#account-dropdown .auth-header").css("height")) - 2 * parseInt($("#account-dropdown .auth-content").css("padding-top"), 10)); 11373 } 11374 11375 //se screenwidth è xxs o xxxs && l'altezza del menu è inferiore all'altezza dello schermo, 11376 //setto l'altezza del menu = altezza schermo 11377 if ((($('.vmq-xxs').css('display') === 'block') || ($('.vmq-xxxs').css('display') === 'block')) 11378 && ($("#account-dropdown").height() + parseInt($("#account-dropdown").css("top"), 10) < $(window).height())) { 11379 $("#account-dropdown").height($(window).height()); 11380 } 11381 } 11382} 11383 11384CheckLoggedMenuResize = function () { 11385 11386 var changedheight = (vh != $(window).height()); 11387 var changedwidth = (vw != $(window).width()); 11388 11389 //se faccio resize: 11390 //solo se cambia la larghezza nascondo logged menu e risetto l'altezza iniziale 11391 if (changedwidth) { 11392 $("#auth-backdrop, #account-dropdown").removeClass("opened"); 11393 $("#account-dropdown").height("initial"); 11394 } 11395 11396 if (changedheight) { 11397 AdjustLoggedMenuSize(); 11398 } 11399 11400} 11401 11402ToggleLoggedMenu = function (e) { 11403 console.log("toggle"); 11404 11405 e.preventDefault(); 11406 e.stopPropagation(); 11407 11408 //accendo/spengo #auth-backdrop e #account-dropdown 11409 $("#auth-backdrop, #account-dropdown").toggleClass("opened"); 11410 AdjustLoggedMenuSize(); 11411} 11412//Logged Menu - END 11413 11414 11415//Toggle Search Form on Mobile - BEGIN 11416ToggleMobileSearch = function (e) { 11417 $('.sidebar-search-form').addClass("displayform"); 11418 $('.sidebar-search-form').length > 0 && $("html, body").animate({ 11419 scrollTop: (+$('.sidebar-search-form').offset().top - $('#content').offset().top + $('#content').scrollTop() - $("header").height() - 10), 11420 }, 600); 11421} 11422//Toggle Search Form on Mobile - END 11423 11424CheckMobileSearch = function () { 11425 $(window).smartresize(function () { 11426 if ( 11427 ($('.vmq-md').css('display') === 'block') || ($('.vmq-lg').css('display') === 'block') || ($('.vmq-xl').css('display') === 'block') || ($('.vmq-xxl').css('display') === 'block') 11428 ) { 11429 $('.sidebar-search-form').removeClass("displayform"); 11430 } 11431 });
11432} 11433 11434 11435//ScrollTop - BEGIN 11436InitScrollTop = function () { 11437 $(window).scroll(function () { 11438 if ($(this).scrollTop() > 100) { 11439 $('.scroll-top').fadeIn(); 11440 } else { 11441 $('.scroll-top').fadeOut(); 11442 } 11443 }); 11444 11445 $('.scroll-top').click(function () { 11446 $("html, body").animate({ 11447 scrollTop: 0 11448 }, 600); 11449 return false; 11450 }); 11451} 11452//Scrolltop - END 11453 11454 11455// Check list layout - List vs Grid - BEGIN 11456CheckListingGrid = function () { 11457 $(window).smartresize(function () { 11458 11459 /* $(window).on('resize', function () { versione originale */ 11460 //se i selettori lista/griglia sono nascosti e c'è un elemento 11461 //con classe "ul.grid-view", lo cambio in "ul.list-view" 11462 if (($(".list-header-selectors .listgrid-selector").css("display") == 'none') && ($("div[class$='-listing'] > ul").hasClass("grid-view"))) { 11463 11464 //setto il listing in formato list-view 11465 $("div[class$='-listing'] > ul").addClass('list-view').removeClass('grid-view'); 11466 11467 //spengo il selettore grid-view e riaccendo il selettore list-view 11468 $(".list-header-selectors div[class$='-listing-selectors'] a").toggleClass("selected"); 11469 } 11470 }); 11471} 11472// Check list layout - List vs Grid - END 11473 11474SetExpandableText = function () { 11475 // expandable_mh - expandable text max-height 11476 // use multiple of '#expandable-text p' line-height that is 25px 11477 var expandable_mh = 250; 11478 $('#expandable-text p:first').data("orig-height", $('#expandable-text p').height()); 11479 11480 if (($('.vmq-xxxs').css('display') === 'block') || ($('.vmq-xxs').css('display') === 'block')) { 11481 expandable_mh = 250 11482 } else if (($('.vmq-xs').css('display') === 'block') || ($('.vmq-sm').css('display') === 'block')) { 11483 expandable_mh = 375 11484 } else { 11485 expandable_mh = 375 11486 } 11487 ; 11488 11489 if ($('#expandable-text p').height() > expandable_mh) { 11490 $('#expandable-text p').css("max-height", expandable_mh + "px") 11491 $('.btn-show-all').css("display", "block") 11492 } 11493 // Expandable text - END 11494} 11495 11496/* CLOSE ALL OPENED SIDEBAR CAROUSEL TABS - BEGIN */ 11497 11498function collapseOpenCarousels() { 11499 $(".sidebar-carousel .tab-dropdown.opened .tab-content.collapse").each(function (j) { 11500 $(this).collapse("hide"); 11501 $(this).parent().removeClass("opened"); 11502 }); 11503} 11504 11505/* CLOSE ALL OPENED SIDEBAR CAROUSEL TABS - END */ 11506 11507 11508function collapseCarouselsOnMobile() { 11509 //se sono su mobile 11510 if ( 11511 ($('.vmq-md').css('display') != 'block') && ($('.vmq-lg').css('display') != 'block') && ($('.vmq-xl').css('display') != 'block') && ($('.vmq-xxl').css('display') != 'block') 11512 ) { 11513 collapseOpenCarousels(); 11514 } 11515} 11516 11517 11518/* OPEN ALL SIDEBAR CAROUSEL TABS WITH DATa-STATUS-START=OPENED - BEGIN */ 11519function showCarousels() { 11520 $(".sidebar-carousel .tab-dropdown[data-status-start='opened'] .tab-content.collapse").each(function (j) { 11521 $(this).collapse("show"); 11522 $(this).parent().addClass("opened"); 11523 }) 11524} 11525 11526/* OPEN ALL SIDEBAR CAROUSEL TABS WITH DATa-STATUS-START=OPENED - END */ 11527 11528 11529/* ONLY ON DESKTOP, OPEN ALL SIDEBAR CAROUSEL TABS WITH DATa-STATUS-START=OPENED - BEGIN */ 11530function showCarouselsOnDesktop() { 11531 //se sono su desktop 11532 if ( 11533 ($('.vmq-md').css('display') === 'block') || ($('.vmq-lg').css('display') === 'block') || ($('.vmq-xl').css('display') === 'block') || ($('.vmq-xxl').css('display') === 'block') 11534 ) { 11535 showCarousels(); 11536 } 11537} 11538 11539/* ONLY ON DESKTOP, OPEN ALL SIDEBAR CAROUSEL TABS WITH DATa-STATUS-START=OPENED - END */ 11540 11541 11542function CheckSidebarCarousel() { 11543 //al load aggiungo classe collapse a tutti i tab della sidebar - così senza js sono tutti aperti 11544 $(".sidebar-carousel .tab-dropdown .tab-content").addClass("collapse"); 11545 11546 //se sono su desktop apro i carousel che hanno data-status-start='opened' 11547 $(function () { 11548 collapseCarouselsOnMobile(); 11549 showCarouselsOnDesktop() 11550 }); 11551 //al termine del resize chiudo i carousel se sono su mobile e li apro se sono su desktop 11552 $(window).smartresize(function () { 11553 collapseCarouselsOnMobile(); 11554 showCarouselsOnDesktop(); 11555 });
11556} 11557 11558 11559if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 11560 var msViewportStyle = document.createElement("style"); 11561 msViewportStyle.appendChild( 11562 document.createTextNode( 11563 "@-ms-viewport{width:auto!important}" 11564 ) 11565 ); 11566 document.getElementsByTagName("head")[0].appendChild(msViewportStyle); 11567} 11568 11569//Check versione desktop vs mobile 11570var desktopVersion = (!(/Android|iPhone|iPad|iPod|BlackBerry|Windows Phone/i).test(navigator.userAgent || navigator.vendor || window.opera)) ? true : false; 11571 11572//check dimensione larghezza e altezza dello schermo del device (NB non dell viewport) 11573//NB: racchiuso in IIFE per non esporre w/d/e/g/x/y nello scope globale: un 'd' globale 11574//collideva con un 'let/const/class d' di script terzi/consent -> SyntaxError che uccideva app.js. 11575var img_size = 'm'; //default (resta globale, usato dalla gallery) 11576(function () { 11577 var w = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], 11578 x = w.innerWidth || e.clientWidth || g.clientWidth, 11579 y = w.innerHeight || e.clientHeight || g.clientHeight, 11580 max_W_H = Math.max(x, y, 320); //MAX larghezza/altezza schermo. Minimo: 320px 11581 if (max_W_H > 600) { 11582 img_size = 'l'; 11583 } 11584})(); 11585 11586//TURN ON MAIN MENU TOP HEADER - BEGIN 11587SetTopNav = function () { 11588 //console.log( $("body").attr("class")) 11589 var body = $("body"); 11590 if ($(body).attr("class")) { 11591 var sct = { 11592 main: $(body).attr("class").split("-")[0], //home-car-motorbike ecc 11593 sub: $(body).attr("class").split("-")[1], //new-used-vintage ecc 11594 subsub: $(body).attr("class").split("-")[2], //list-detail 11595 target: "" 11596 } 11597 //console.log(sct.main +" -- "+ sct.sub +" -- "+ sct.subsub) 11598 if (sct.main === 'home') { 11599 //burger 11600 $('.sidemenu-nav ul.main li a[data-section="' + sct.main + '"]').addClass("selected"); 11601 } 11602 if (sct.main === 'car' || sct.main === 'motorbike' || sct.main === 'commercial' || sct.main === 'dealership') { 11603 //top menu 11604 $('.top-menu-vm ul li a[data-section="' + sct.main + "-" + sct.sub + '"]').parent().addClass("active"); 11605 //burger 11606 $('.sidemenu-nav ul.main li a[data-section="' + sct.main + "-" + sct.sub + '"]').addClass("selected"); 11607 } 11608 } 11609} 11610//TURN ON MAIN MENU TOP HEADER - END 11611 11612 11613//Check Mouse Presence - BEGIN 11614detectHover = function () { 11615 document.removeEventListener('mousemove', detectHover, false); 11616 currentMousePos = event.clientX; 11617 if (currentMousePos) { 11618 $(body).addClass("hasMouse"); 11619 } 11620}; 11621 11622//Check mouse presence - END 11623 11624$(function () { 11625 11626 SetTopNav(); 11627 11628 //Sidebar - setto l'altezza di tutti gli ul in tab che hanno l'attributo "data-show" valorizzato 11629 $('.sidebar-carousel').find("[data-show]").each(function (i) { 11630 var itemsToShow = $(this).data("show"); 11631 var t = 0; 11632 $(this).find(".tab-content ul li").slice(0, itemsToShow).each(function (index) { 11633 t = t + $(this).outerHeight(true); 11634 if (index == itemsToShow - 1) { 11635 t = t - parseInt($(this).css("margin-bottom")); 11636 } 11637 11638 }); 11639 $(this).find(".tab-content ul").height(t).css('overflow', 'hidden'); 11640 }); 11641 11642 $("#src-car, #src-motorbike, #src-commercials").click(function (e) { 11643 e.preventDefault(); 11644 e.stopPropagation(); 11645 }); 11646 11647 // BURGER MENU - BEGIN 11648 $('.sidemenu-burger, .sidemenu-overlay').click(function () { 11649 $('.sidemenu-burger').toggleClass('clicked'); 11650 $('.sidemenu-overlay').toggleClass('show'); 11651 $('nav').toggleClass('show'); 11652 $('body').toggleClass('sidemenu-overflow'); 11653 }); 11654 // BURGER MENU - END 11655 11656 11657 //Sidebar - tasto Show all - click su "Mostra tutto" 11658 $('.sidebar-carousel .btn-show-all').on('click', function (e) { 11659 $(e.target).closest("[data-show]").find("ul").removeAttr("style"); 11660 $(e.target).css('display', 'none'); 11661 }) 11662 11663 // Sidebar - tab dropdown - gestione apertura e icona 11664 //apre il tab 11665 $(".tab-dropdown.opened .tab-content.collapse").addClass("in"); 11666 //gestisce l'icona del tab header 11667 $('.tab-dropdown .tab-header .btn').on('click', function (e) { 11668 $(e.target).closest('.tab-dropdown').toggleClass("opened"); 11669 });
11670 11671 11672 //listview, GalleryView 11673 $('.vehicle-listing-selectors a').on('click', function (e) { 11674 var t = $(e.target).closest("[data-type]").attr("data-type"); 11675 $(e.target).toggleClass("selected").siblings().toggleClass("selected"); 11676 $(".items-listing > ul").removeClass().addClass(t); 11677 }); 11678 11679 $(".share-mail").click(function (e) { 11680 VetrinaMotoriUtils.OpenModalWindow('/market/sendtofriendform'); 11681 }); 11682 $("#btn-report-fraud").click(function (e) { 11683 VetrinaMotoriUtils.OpenModalWindow('/market/reportfraudform'); 11684 }); 11685 11686 // $("#btn-contact-seller").click(function (e) { 11687 // VetrinaMotoriUtils.OpenModalWindow('/market/contactsellerform?adID=4CE3A1'); 11688 // }); 11689 11690 $("#auth-backdrop, #account-dropdown .auth-header button.close").click(function (e) { 11691 ToggleLoggedMenu(e); 11692 }); 11693 //scrollTop 11694 InitScrollTop(); 11695 11696 11697 //Check della Grid view - la disabilita sulle small viewports 11698 CheckListingGrid(); 11699 11700 vh = $(window).height(); 11701 vw = $(window).width(); 11702 11703 //Check dimensione logged menu sul resize 11704 $(window).resize(function () { 11705 CheckLoggedMenuResize(); 11706 }); 11707 11708 11709 //GALLERY CAROUSEL - BEGIN 11710 var $glr = $("#gallery"); 11711 11712 //Carieamento immagini 11713 //Se la gallery deve ricaricare alcune immagini ad alta risoluzione 11714 if (img_size == 'l') { 11715 11716 $glr.find(".item img").each(function () { 11717 //Se l'immagine è da sostituire con l'immagine ad alta risoluzione 11718 if ($(this).attr('src') != $(this).data('src-fullsize')) { 11719 $(this).attr('src', $(this).data('src-fullsize')); 11720 } 11721 }); 11722 } 11723 11724 //Inizialmente creo e stoppo il carousel 11725 $glr.carousel("pause"); 11726 var blockclick = false; //lo scroll è fermo, consento il click 11727 11728 //precedente 11729 $glr.find(".left").click(function (e) { 11730 e.preventDefault(); 11731 e.stopPropagation(); 11732 $("#gallery").carousel("prev"); 11733 }); 11734 11735 //successivo 11736 $glr.find(".right").click(function (e) { 11737 e.preventDefault(); 11738 e.stopPropagation(); 11739 $("#gallery").carousel("next"); 11740 }); 11741 11742 //click su thumbnail 11743 $(".carousel-thumbs li").click(function (e) { 11744 e.preventDefault(); 11745 e.stopPropagation(); 11746 //Se ho cliccato a scroll fermo 11747 if (blockclick == false) { 11748 var t = $(this).parent().data("carousel-target"); // li â ul 11749 var i = $(this).data("carousel-slideto"); // li 11750 var j = $(t + " .item.active").index(); //recupero item attivo 11751 if (i != j) { 11752 $(t).carousel(i); 11753 } 11754 } 11755 }); 11756 11757 $glr.on('slide.bs.carousel', function (e) { 11758 blockclick = true; //blocco il click 11759 }); 11760 11761 //finito lo slide, riallineo il thumb attivo e consento il click 11762 $glr.on('slid.bs.carousel', function (e) { 11763 blockclick = false; 11764 var i = $glr.find(".item.active").index(); 11765 $(".carousel-thumbs li.active").removeClass("active"); 11766 $('.carousel-thumbs li[data-carousel-slideto="' + i + '"]').addClass("active"); 11767 $glr.carousel("pause"); 11768 }); 11769 11770 //Swipe support 11771 $glr.swipe({ 11772 swipe: function (event, direction, distance, duration, fingerCount, fingerData) { 11773 if (direction == 'left') $(this).carousel('next'); 11774 if (direction == 'right') $(this).carousel('prev'); 11775 }, 11776 allowPageScroll: "vertical" 11777 }); 11778 //GALLERY CAROUSEL - END 11779 11780 11781 //SEARCH LINK - TOGGLE SEARCH ON mobile 11782 $("header .search-link").click(function (e) { 11783 ToggleMobileSearch(e); 11784 }); 11785 CheckMobileSearch(); 11786 11787 // Expandable text - BEGIN 11788 SetExpandableText(); 11789 $('#show-text').click(function (e) { 11790 e.preventDefault(); 11791 e.stopPropagation(); 11792 $('#expandable-text p').css("max-height", "none"); 11793 $('.expandable-text .btn-show-all').css("display", "none"); 11794 }); 11795 11796 CheckSidebarCarousel(); 11797 11798 //menage menu tabs everywhere 11799 VetrinaMotoriUtils 11800 .TabNavigation(); 11801 11802 //menage login box 11803 VetrinaMotoriUtils 11804 .WarmUp();
11805 11806 $("#src-car, #src-motorbike, #src-commercials").click(function (e) { 11807 e.preventDefault(); 11808 e.stopPropagation(); 11809 }); 11810 11811 //LOGIN MENU - SOLO DA MOBILE 11812 $('#btn-auth-dropdown-mobile, .sidemenudx-overlay, .close-overlay').click(function (e) { 11813 e.stopPropagation(); 11814 e.preventDefault(); 11815 $('#btn-auth-dropdown-mobile').toggleClass('clicked'); 11816 $('.sidemenudx-overlay').toggleClass('show'); 11817 $('div.sidemenudx-mobileacc').toggleClass('show'); 11818 $('body').toggleClass('overflow'); 11819 $('html').toggleClass('freeze-body'); 11820 }); 11821 11822 window.addEventListener("message", function (event) { 11823 switch (event.data) { 11824 case "closeMyMotoOverlay": 11825 $(".login-panel").html("").css("display", "none"); 11826 break; 11827 case "postLoginActionReload": 11828 document.location.reload(); 11829 break; 11830 } 11831 }); 11832 11833 //network login from mymoto 11834 VetrinaMotoriUtils.SetupLogin(); 11835 //subscription from mymoto 11836 VetrinaMotoriUtils.SetupSubscription(); 11837 //mymoto dashboard 11838 VetrinaMotoriUtils.SetupDashboard(); 11839 11840 VetrinaMotoriUtils.SelectedOrderDefault(); 11841 11842 $("#orderby").on("change", function (e) { 11843 VetrinaMotoriUtils.OrderDataEvt(); 11844 }); 11845 11846 VetrinaMotoriUtils.BgText(); 11847 $(window).resize(VetrinaMotoriUtils.BgText); 11848}); 11849(function ($) { 11850 "use strict" 11851 11852 const COOKIE_NAME = 'CrmFp'; 11853 11854 $(function () { 11855 saveFingerprint(); 11856 }) 11857 11858 11859 async function saveFingerprint() { 11860 11861 if (Cookies.get(COOKIE_NAME)) return; 11862 11863 const fp = await FingerprintJS.load(); 11864 const result = await fp.get(); 11865 11866 Cookies.set(COOKIE_NAME, result.visitorId, {expires: 365}); 11867 } 11868 11869})(jQuery);
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.