Advertisement
mere1y

Untitled

Feb 1st, 2023
945
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Create the XenForo namespace
  3.  * @package XenForo
  4.  */
  5. var XenForo = {};
  6.  
  7. // _XF_JS_UNCOMPRESSED_TEST_ - do not edit/remove
  8.  
  9. if (jQuery === undefined) jQuery = $ = {};
  10. if ($.tools === undefined) console.error('jQuery Tools is not loaded.');
  11.  
  12.  
  13.  
  14. function animateCSS(node, animationName, callback) {
  15.  
  16.     var isString = animationName instanceof String;
  17.     if (isString) {
  18.         animationName = ['animated', animationName];
  19.     } else {
  20.         animationName = ['animated'].concat(animationName);
  21.     }
  22.  
  23.     for (var i = 0; i < animationName.length; i++) {
  24.         node.classList.add(animationName[i]);
  25.     }
  26.  
  27.  
  28.     function handleAnimationEnd() {
  29.         for (var i = 0; i < animationName.length; i++) {
  30.             node.classList.remove(animationName[i]);
  31.         }
  32.  
  33.         node.removeEventListener('animationend', handleAnimationEnd);
  34.  
  35.         if (typeof callback === 'function') callback()
  36.     }
  37.  
  38.  
  39.     node.addEventListener('animationend', handleAnimationEnd);
  40. }
  41.  
  42. var isScrolledIntoView = function (elem) {
  43.     var docViewTop = $(window).scrollTop() + $('#header').height();
  44.     var docViewBottom = docViewTop + $(window).height();
  45.  
  46.     var elemTop = $(elem).offset().top;
  47.     var elemBottom = elemTop + $(elem).height();
  48.  
  49.     return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
  50. };
  51.  
  52. var supports_html5_storage = function () {
  53.     try {
  54.         return 'localStorage' in window && window['localStorage'] !== null;
  55.     } catch (e) {
  56.         return false;
  57.     }
  58. };
  59.  
  60. function isElementInViewport(el) {
  61.     var rect = el[0].getBoundingClientRect();
  62.     return (
  63.         rect.top >= 0 &&
  64.         rect.left >= 0 &&
  65.         rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
  66.         rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  67.     );
  68. }
  69.  
  70. function scrollParentToChild(parent, child) {
  71.     var parentRect = parent.getBoundingClientRect();
  72.     var parentViewableArea = { height: parent.clientHeight, width: parent.clientWidth };
  73.     var childRect = child.getBoundingClientRect();
  74.     var isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height);
  75.     if (!isViewable) {
  76.         const scrollTop = childRect.top - parentRect.top;
  77.         parent.scrollTop += scrollTop;
  78.     }
  79. }
  80.  
  81. /**
  82.  * Deal with Firebug not being present
  83.  */
  84. !function (w) {
  85.     var fn, i = 0;
  86.     if (!w.console) w.console = {};
  87.     if (w.console.log && !w.console.debug) w.console.debug = w.console.log;
  88.     fn = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'getFirebugElement', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'notifyFirebug', 'profile', 'profileEnd', 'time', 'timeEnd', 'trace', 'warn'];
  89.     for (i = 0; i < fn.length; ++i) if (!w.console[fn[i]]) w.console[fn[i]] = function () {
  90.     };
  91. }(window);
  92.  
  93.  
  94. /** @param {jQuery} $ jQuery Object */
  95. !function ($, window, document, _undefined) {
  96.     var isTouchBrowser = (function () {
  97.         var _isTouchBrowserVal;
  98.  
  99.         try {
  100.             _isTouchBrowserVal = 'ontouchstart' in document.documentElement;
  101.             //_isTouchBrowserVal = !!('ontouchstart' in window || navigator.maxTouchPoints || navigator.msMaxTouchPoints);
  102.         } catch (e) {
  103.             _isTouchBrowserVal = !!(navigator.userAgent.indexOf('webOS') != -1);
  104.         }
  105.  
  106.         return function () {
  107.             return _isTouchBrowserVal;
  108.         };
  109.     })();
  110.  
  111.     var classes = ['hasJs'];
  112.     classes.push(isTouchBrowser() ? 'Touch' : 'NoTouch');
  113.  
  114.     var div = document.createElement('div');
  115.     classes.push(('draggable' in div) || ('ondragstart' in div && 'ondrop' in div) ? 'HasDragDrop' : 'NoDragDrop');
  116.  
  117.     // not especially nice but...
  118.     if (navigator.userAgent.search(/\((iPhone|iPad|iPod);/) != -1) {
  119.         classes.push('iOS');
  120.     }
  121.  
  122.     var $html = $('html');
  123.     $html.addClass(classes.join(' ')).removeClass('NoJs');
  124.  
  125.     /**
  126.      * Fix IE abbr handling
  127.      */
  128.     document.createElement('abbr');
  129.  
  130.     /**
  131.      * Detect mobile webkit
  132.      */
  133.     if (/webkit.*mobile/i.test(navigator.userAgent)) {
  134.         XenForo._isWebkitMobile = true;
  135.     }
  136.  
  137.     // preserve original jQuery Tools .overlay()
  138.     jQuery.fn._jQueryToolsOverlay = jQuery.fn.overlay;
  139.  
  140.     /**
  141.      * Extends jQuery core
  142.      */
  143.     jQuery.extend(true,
  144.         {
  145.             /**
  146.              * Sets the context of 'this' within a called function.
  147.              * Takes identical parameters to $.proxy, but does not
  148.              * enforce the one-elment-one-method merging that $.proxy
  149.              * does, allowing multiple objects of the same type to
  150.              * bind to a single element's events (for example).
  151.              *
  152.              * @param function|object Function to be called | Context for 'this', method is a property of fn
  153.              * @param function|string Context for 'this' | Name of method within fn to be called
  154.              *
  155.              * @return function
  156.              */
  157.             context: function (fn, context) {
  158.                 if (typeof context == 'string') {
  159.                     var _context = fn;
  160.                     fn = fn[context];
  161.                     context = _context;
  162.                 }
  163.  
  164.                 return fn.bind(context)
  165.             },
  166.  
  167.             /**
  168.              * Sets a cookie.
  169.              *
  170.              * @param string cookie name (escaped)
  171.              * @param mixed cookie value
  172.              * @param string cookie expiry date
  173.              *
  174.              * @return mixed cookie value
  175.              */
  176.             setCookie: function (name, value, expires) {
  177.                 console.log('Set cookie %s = %s', name, value);
  178.  
  179.                 document.cookie = XenForo._cookieConfig.prefix + name + '=' + encodeURIComponent(value)
  180.                     + (expires === undefined ? '' : ';expires=' + expires.toUTCString())
  181.                     + (XenForo._cookieConfig.path ? ';path=' + XenForo._cookieConfig.path : '')
  182.                     + (XenForo._cookieConfig.domain ? ';domain=' + XenForo._cookieConfig.domain : '');
  183.  
  184.                 return value;
  185.             },
  186.  
  187.             /**
  188.              * Fetches the value of a named cookie.
  189.              *
  190.              * @param string Cookie name (escaped)
  191.              *
  192.              * @return string Cookie value
  193.              */
  194.             getCookie: function (name) {
  195.                 var expr, cookie;
  196.  
  197.                 expr = new RegExp('(^| )' + XenForo._cookieConfig.prefix + name + '=([^;]+)(;|$)');
  198.                 cookie = expr.exec(document.cookie);
  199.  
  200.                 if (cookie) {
  201.                     return decodeURIComponent(cookie[2]);
  202.                 } else {
  203.                     return null;
  204.                 }
  205.             },
  206.  
  207.             /**
  208.              * Deletes a cookie.
  209.              *
  210.              * @param string Cookie name (escaped)
  211.              *
  212.              * @return null
  213.              */
  214.             deleteCookie: function (name) {
  215.                 console.info('Delete cookie %s', name);
  216.  
  217.                 document.cookie = XenForo._cookieConfig.prefix + name + '='
  218.                     + (XenForo._cookieConfig.path ? '; path=' + XenForo._cookieConfig.path : '')
  219.                     + (XenForo._cookieConfig.domain ? '; domain=' + XenForo._cookieConfig.domain : '')
  220.                     + '; expires=Thu, 01-Jan-70 00:00:01 GMT';
  221.  
  222.                 return null;
  223.             }
  224.         });
  225.  
  226.     /**
  227.      * Extends jQuery functions
  228.      */
  229.     jQuery.fn.extend(
  230.         {
  231.             /**
  232.              * Wrapper for XenForo.activate, for 'this' element
  233.              *
  234.              * @return jQuery
  235.              */
  236.             xfActivate: function () {
  237.                 return XenForo.activate(this);
  238.             },
  239.  
  240.             /**
  241.              * Retuns .data(key) for this element, or the default value if there is no data
  242.              *
  243.              * @param string key
  244.              * @param mixed defaultValue
  245.              *
  246.              * @return mixed
  247.              */
  248.             dataOrDefault: function (key, defaultValue) {
  249.                 var value = this.data(key);
  250.  
  251.                 if (value === undefined) {
  252.                     return defaultValue;
  253.                 }
  254.  
  255.                 return value;
  256.             },
  257.  
  258.             /**
  259.              * Like .val() but also trims trailing whitespace
  260.              */
  261.             strval: function () {
  262.                 return String(this.val()).replace(/\s+$/g, '');
  263.             },
  264.  
  265.             /**
  266.              * Get the 'name' attribute of an element, or if it exists, the value of 'data-fieldName'
  267.              *
  268.              * @return string
  269.              */
  270.             fieldName: function () {
  271.                 return this.data('fieldname') || this.attr('name');
  272.             },
  273.  
  274.             /**
  275.              * Get the value that would be submitted with 'this' element's name on form submit
  276.              *
  277.              * @return string
  278.              */
  279.             fieldValue: function () {
  280.                 switch (this.attr('type')) {
  281.                     case 'checkbox': {
  282.                         return $('input:checkbox[name="' + this.fieldName() + '"]:checked', this.context.form).val();
  283.                     }
  284.  
  285.                     case 'radio': {
  286.                         return $('input:radio[name="' + this.fieldName() + '"]:checked', this.context.form).val();
  287.                     }
  288.  
  289.                     default: {
  290.                         return this.val();
  291.                     }
  292.                 }
  293.             },
  294.  
  295.             _jqSerialize: $.fn.serialize,
  296.  
  297.             /**
  298.              * Overridden jQuery serialize method to ensure that RTE areas are serialized properly.
  299.              */
  300.             serialize: function () {
  301.                 $('textarea.BbCodeWysiwygEditor').each(function () {
  302.                     var data = $(this).data('XenForo.BbCodeWysiwygEditor');
  303.                     if (data) {
  304.                         data.syncEditor();
  305.                     }
  306.                 });
  307.  
  308.                 return this._jqSerialize();
  309.             },
  310.  
  311.             _jqSerializeArray: $.fn.serializeArray,
  312.  
  313.             /**
  314.              * Overridden jQuery serializeArray method to ensure that RTE areas are serialized properly.
  315.              */
  316.             serializeArray: function () {
  317.                 $('textarea.BbCodeWysiwygEditor').each(function () {
  318.                     var data = $(this).data('XenForo.BbCodeWysiwygEditor');
  319.                     if (data) {
  320.                         data.syncEditor();
  321.                     }
  322.                 });
  323.  
  324.                 return this._jqSerializeArray();
  325.             },
  326.  
  327.             /**
  328.              * Returns the position and size of an element, including hidden elements.
  329.              *
  330.              * If the element is hidden, it will very quickly un-hides a display:none item,
  331.              * gets its offset and size, restore the element to its hidden state and returns values.
  332.              *
  333.              * @param string inner/outer/{none} Defines the jQuery size function to use
  334.              * @param string offset/position/{none} Defines the jQuery position function to use (default: offset)
  335.              *
  336.              * @return object Offset { left: float, top: float }
  337.              */
  338.             coords: function (sizeFn, offsetFn) {
  339.                 var coords,
  340.                     visibility,
  341.                     display,
  342.                     widthFn,
  343.                     heightFn,
  344.                     hidden = this.is(':hidden');
  345.  
  346.                 if (hidden) {
  347.                     visibility = this.css('visibility'),
  348.                         display = this.css('display');
  349.  
  350.                     this.css(
  351.                         {
  352.                             visibility: 'hidden',
  353.                             display: 'block'
  354.                         });
  355.                 }
  356.  
  357.                 switch (sizeFn) {
  358.                     case 'inner': {
  359.                         widthFn = 'innerWidth';
  360.                         heightFn = 'innerHeight';
  361.                         break;
  362.                     }
  363.                     case 'outer': {
  364.                         widthFn = 'outerWidth';
  365.                         heightFn = 'outerHeight';
  366.                         break;
  367.                     }
  368.                     default: {
  369.                         widthFn = 'width';
  370.                         heightFn = 'height';
  371.                     }
  372.                 }
  373.  
  374.                 switch (offsetFn) {
  375.                     case 'position': {
  376.                         offsetFn = 'position';
  377.                         break;
  378.                     }
  379.  
  380.                     default: {
  381.                         offsetFn = 'offset';
  382.                         break;
  383.                     }
  384.                 }
  385.  
  386.                 coords = this[offsetFn]();
  387.                 coords.width = this[widthFn]();
  388.                 coords.height = this[heightFn]();
  389.  
  390.                 if (hidden) {
  391.                     this.css(
  392.                         {
  393.                             display: display,
  394.                             visibility: visibility
  395.                         });
  396.                 }
  397.  
  398.                 return coords;
  399.             },
  400.  
  401.             /**
  402.              * Sets a unique id for an element, if one is not already present
  403.              */
  404.             uniqueId: function () {
  405.                 if (!this.attr('id')) {
  406.                     this.attr('id', 'XenForoUniq' + XenForo._uniqueIdCounter++);
  407.                 }
  408.  
  409.                 return this;
  410.             },
  411.  
  412.             /**
  413.              * Wrapper functions for commonly-used animation effects, so we can customize their behaviour as required
  414.              */
  415.             xfFadeIn: function (speed, callback) {
  416.                 return this.fadeIn(speed, function () {
  417.                     $(this).ieOpacityFix(callback);
  418.                 });
  419.             },
  420.             xfFadeOut: function (speed, callback) {
  421.                 return this.fadeOut(speed, callback);
  422.             },
  423.             xfShow: function (speed, callback) {
  424.                 return this.show(speed, function () {
  425.                     $(this).ieOpacityFix(callback);
  426.                 });
  427.             },
  428.             xfHide: function (speed, callback) {
  429.                 return this.hide(speed, callback);
  430.             },
  431.             xfSlideDown: function (speed, callback) {
  432.                 return this.slideDown(speed, function () {
  433.                     $(this).ieOpacityFix(callback);
  434.                 });
  435.             },
  436.             xfSlideUp: function (speed, callback) {
  437.                 return this.slideUp(speed, callback);
  438.             },
  439.  
  440.             /**
  441.              * Animates an element opening a space for itself, then fading into that space
  442.              *
  443.              * @param integer|string Speed of fade-in
  444.              * @param function Callback function on completion
  445.              *
  446.              * @return jQuery
  447.              */
  448.             xfFadeDown: function (fadeSpeed, callback, finish) {
  449.                 this.filter(':hidden').xfHide().css('opacity', 0);
  450.  
  451.                 fadeSpeed = fadeSpeed || XenForo.speed.normal;
  452.                 if (finish) {
  453.                     $(this).clearQueue().finish();
  454.                 }
  455.  
  456.                 return this
  457.                     .xfSlideDown(XenForo.speed.fast)
  458.                     .animate({ opacity: 1 }, fadeSpeed, function () {
  459.                         $(this).ieOpacityFix(callback);
  460.                     });
  461.             },
  462.  
  463.             /**
  464.              * Animates an element fading out then closing the gap left behind
  465.              *
  466.              * @param integer|string Speed of fade-out - if this is zero, there will be no animation at all
  467.              * @param function Callback function on completion
  468.              * @param integer|string Slide speed - ignored if fadeSpeed is zero
  469.              * @param string Easing method
  470.              *
  471.              * @return jQuery
  472.              */
  473.             xfFadeUp: function (fadeSpeed, callback, slideSpeed, easingMethod, finish) {
  474.                 fadeSpeed = ((typeof fadeSpeed == 'undefined' || fadeSpeed === null) ? XenForo.speed.normal : fadeSpeed);
  475.                 slideSpeed = ((typeof slideSpeed == 'undefined' || slideSpeed === null) ? fadeSpeed : slideSpeed);
  476.  
  477.                 if (finish) {
  478.                     $(this).clearQueue().finish();
  479.                 }
  480.  
  481.                 return this
  482.                     .slideUp({
  483.                         duration: Math.max(fadeSpeed, slideSpeed),
  484.                         easing: easingMethod || 'swing',
  485.                         complete: callback,
  486.                         queue: false
  487.                     })
  488.                     .animate({ opacity: 0, queue: false }, fadeSpeed);
  489.             },
  490.  
  491.             /**
  492.              * Inserts and activates content into the DOM, using xfFadeDown to animate the insertion
  493.              *
  494.              * @param string jQuery method with which to insert the content
  495.              * @param string Selector for the previous parameter
  496.              * @param string jQuery method with which to animate the showing of the content
  497.              * @param string|integer Speed at which to run the animation
  498.              * @param function Callback for when the animation is complete
  499.              *
  500.              * @return jQuery
  501.              */
  502.             xfInsert: function (insertMethod, insertReference, animateMethod, animateSpeed, callback) {
  503.                 if (insertMethod == 'replaceAll') {
  504.                     $(insertReference).xfFadeUp(animateSpeed);
  505.                 }
  506.  
  507.                 this
  508.                     .addClass('__XenForoActivator')
  509.                     .css('display', 'none')
  510.                 [insertMethod || 'appendTo'](insertReference)
  511.                     .xfActivate()
  512.                 [animateMethod || 'xfFadeDown'](animateSpeed, callback);
  513.  
  514.                 return this;
  515.             },
  516.  
  517.             /**
  518.              * Removes an element from the DOM, animating its removal with xfFadeUp
  519.              * All parameters are optional.
  520.              *
  521.              *  @param string animation method
  522.              *  @param function callback function
  523.              *  @param integer Sliding speed
  524.              *  @param string Easing method
  525.              *
  526.              * @return jQuery
  527.              */
  528.             xfRemove: function (animateMethod, callback, slideSpeed, easingMethod) {
  529.                 return this[animateMethod || 'xfFadeUp'](XenForo.speed.normal, function () {
  530.                     $(this).empty().remove();
  531.  
  532.                     if ($.isFunction(callback)) {
  533.                         callback();
  534.                     }
  535.                 }, slideSpeed, easingMethod);
  536.             },
  537.  
  538.             /**
  539.              * Prepares an element for xfSlideIn() / xfSlideOut()
  540.              *
  541.              * @param boolean If true, return the height of the wrapper
  542.              *
  543.              * @return jQuery|integer
  544.              */
  545.             _xfSlideWrapper: function (getHeight) {
  546.                 if (!this.data('slidewrapper')) {
  547.                     this.data('slidewrapper', this.wrap('<div class="_swOuter"><div class="_swInner" /></div>')
  548.                         .closest('div._swOuter').css('overflow', 'hidden'));
  549.                 }
  550.  
  551.                 if (getHeight) {
  552.                     try {
  553.                         return this.data('slidewrapper').height();
  554.                     } catch (e) {
  555.                         // so IE11 seems to be randomly throwing an exception in jQuery here, so catch it
  556.                         return 0;
  557.                     }
  558.                 }
  559.  
  560.                 return this.data('slidewrapper');
  561.             },
  562.  
  563.             /**
  564.              * Slides content in (down), with content glued to lower edge, drawer-like
  565.              *
  566.              * @param duration
  567.              * @param easing
  568.              * @param callback
  569.              *
  570.              * @return jQuery
  571.              */
  572.             xfSlideIn: function (duration, easing, callback) {
  573.                 var $wrap = this._xfSlideWrapper().css('height', 'auto'),
  574.                     height = 0;
  575.  
  576.                 $wrap.find('div._swInner').css('margin', 'auto');
  577.                 height = this.show(0).outerHeight();
  578.  
  579.                 $wrap
  580.                     .css('height', 0)
  581.                     .animate({ height: height }, duration, easing, function () {
  582.                         $wrap.css('height', '');
  583.                     })
  584.                     .find('div._swInner')
  585.                     .css('marginTop', height * -1)
  586.                     .animate({ marginTop: 0 }, duration, easing, callback);
  587.  
  588.                 return this;
  589.             },
  590.  
  591.             /**
  592.              * Slides content out (up), reversing xfSlideIn()
  593.              *
  594.              * @param duration
  595.              * @param easing
  596.              * @param callback
  597.              *
  598.              * @return jQuery
  599.              */
  600.             xfSlideOut: function (duration, easing, callback) {
  601.                 var height = this.outerHeight();
  602.  
  603.                 this._xfSlideWrapper()
  604.                     .animate({ height: 0 }, duration, easing)
  605.                     .find('div._swInner')
  606.                     .animate({ marginTop: height * -1 }, duration, easing, callback);
  607.  
  608.                 return this;
  609.             },
  610.  
  611.             /**
  612.              * Workaround for IE's font-antialiasing bug when dealing with opacity
  613.              *
  614.              * @param function Callback
  615.              */
  616.             ieOpacityFix: function (callback) {
  617.                 //ClearType Fix
  618.                 if (!$.support.opacity) {
  619.                     this.css('filter', '');
  620.                     this.attr('style', this.attr('style').replace(/filter:\s*;/i, ''));
  621.                 }
  622.  
  623.                 if ($.isFunction(callback)) {
  624.                     callback.apply(this);
  625.                 }
  626.  
  627.                 return this;
  628.             },
  629.  
  630.             /**
  631.              * Wraps around jQuery Tools .overlay().
  632.              *
  633.              * Prepares overlay options before firing overlay() for best possible experience.
  634.              * For example, removes fancy (slow) stuff from options for touch browsers.
  635.              *
  636.              * @param options
  637.              *
  638.              * @returns jQuery
  639.              */
  640.             overlay: function (options) {
  641.                 if (XenForo.isTouchBrowser()) {
  642.                     return this._jQueryToolsOverlay($.extend(true, options,
  643.                         {
  644.                             //mask: false,
  645.                             speed: 0,
  646.                             loadSpeed: 0
  647.                         }));
  648.                 } else {
  649.                     return this._jQueryToolsOverlay(options);
  650.                 }
  651.             }
  652.         });
  653.  
  654.     /* jQuery Tools Extensions */
  655.  
  656.     /**
  657.      * Effect method for jQuery.tools overlay.
  658.      * Slides down a container, then fades up the content.
  659.      * Closes by reversing the animation.
  660.      */
  661.  
  662.     $.tools.overlay.addEffect('zoomIn',
  663.         function (position, callback) {
  664.             var $overlay = this.getOverlay();
  665.  
  666.             $overlay.find('.content').css('opacity', 0);
  667.  
  668.             if (this.getConf().fixed) {
  669.                 position.position = 'fixed';
  670.             } else {
  671.                 position.position = 'absolute';
  672.                 position.top += $(window).scrollTop();
  673.                 position.left += $(window).scrollLeft();
  674.             }
  675.  
  676.             $overlay.removeClass('animated zoomOut faster').css(position);
  677.             $overlay.show(0, callback);
  678.  
  679.             animateCSS($overlay.get(0), ['zoomIn', 'faster']);
  680.         },
  681.         function (callback) {
  682.             var $overlay = this.getOverlay();
  683.             $overlay.removeClass('animated zoomIn faster');
  684.             animateCSS($overlay.get(0), ['zoomOut', 'faster'], function () {
  685.                 callback();
  686.                 $overlay.hide(0, callback);
  687.             });
  688.         },
  689.     );
  690.  
  691.     $.tools.overlay.addEffect('slideDownContentFade',
  692.         function (position, callback) {
  693.             var $overlay = this.getOverlay(),
  694.                 conf = this.getConf();
  695.  
  696.             $overlay.find('.content').css('opacity', 0);
  697.  
  698.             if (this.getConf().fixed) {
  699.                 position.position = 'fixed';
  700.             } else {
  701.                 position.position = 'absolute';
  702.                 position.top += $(window).scrollTop();
  703.                 position.left += $(window).scrollLeft();
  704.             }
  705.  
  706.             $overlay.css(position).xfSlideDown(XenForo.speed.fast, function () {
  707.                 $overlay.find('.content').animate({ opacity: 1 }, conf.speed, function () {
  708.                     $(this).ieOpacityFix(callback);
  709.                 });
  710.             });
  711.         },
  712.         function (callback) {
  713.             var $overlay = this.getOverlay();
  714.  
  715.             $overlay.find('.content').animate({ opacity: 0 }, this.getConf().speed, function () {
  716.                 $overlay.xfSlideUp(XenForo.speed.fast, callback);
  717.             });
  718.         }
  719.     );
  720.  
  721.     $.tools.overlay.addEffect('slideDown',
  722.         function (position, callback) {
  723.             if (this.getConf().fixed) {
  724.                 position.position = 'fixed';
  725.             } else {
  726.                 position.position = 'absolute';
  727.                 position.top += $(window).scrollTop();
  728.                 position.left += $(window).scrollLeft();
  729.             }
  730.  
  731.             this.getOverlay()
  732.                 .css(position)
  733.                 .xfSlideDown(this.getConf().speed, callback);
  734.         },
  735.         function (callback) {
  736.             this.getOverlay().hide(0, callback);
  737.         }
  738.     );
  739.  
  740.     // *********************************************************************
  741.  
  742.     $.extend(XenForo,
  743.         {
  744.             /**
  745.              * Cache for overlays
  746.              *
  747.              * @var object
  748.              */
  749.             _OverlayCache: {},
  750.  
  751.             /**
  752.              * Defines whether or not an AJAX request is known to be in progress
  753.              *
  754.              *  @var boolean
  755.              */
  756.             _AjaxProgress: false,
  757.  
  758.             /**
  759.              * Defines a variable that can be overridden to force/control the base HREF
  760.              * used to canonicalize AJAX requests
  761.              *
  762.              * @var string
  763.              */
  764.             ajaxBaseHref: '',
  765.  
  766.             /**
  767.              * Counter for unique ID generation
  768.              *
  769.              * @var integer
  770.              */
  771.             _uniqueIdCounter: 0,
  772.  
  773.             /**
  774.              * Configuration for overlays, should be redefined in the PAGE_CONTAINER template HTML
  775.              *
  776.              * @var object
  777.              */
  778.             _overlayConfig: {},
  779.  
  780.             /**
  781.              * Contains the URLs of all externally loaded resources from scriptLoader
  782.              *
  783.              * @var object
  784.              */
  785.             _loadedScripts: {},
  786.  
  787.             /**
  788.              * Configuration for cookies
  789.              *
  790.              * @var object
  791.              */
  792.             _cookieConfig: { path: '/', domain: '', 'prefix': 'xf_' },
  793.  
  794.             /**
  795.              * Flag showing whether or not the browser window has focus. On load, assume true.
  796.              *
  797.              * @var boolean
  798.              */
  799.             _hasFocus: true,
  800.  
  801.             /**
  802.              * @var object List of server-related time info (now, today, todayDow)
  803.              */
  804.             serverTimeInfo: {},
  805.  
  806.             /**
  807.              * @var object Information about the XenForo visitor. Usually contains user_id.
  808.              */
  809.             visitor: {},
  810.  
  811.             /**
  812.              * @var integer Time the page was loaded.
  813.              */
  814.             _pageLoadTime: (new Date()).getTime() / 1000,
  815.  
  816.             /**
  817.              * JS version key, to force refreshes when needed
  818.              *
  819.              * @var string
  820.              */
  821.             _jsVersion: '',
  822.  
  823.             /**
  824.              * If true, disables reverse tabnabbing protection
  825.              *
  826.              * @var bool
  827.              */
  828.             _noRtnProtect: false,
  829.  
  830.             /**
  831.              * CSRF Token
  832.              *
  833.              * @var string
  834.              */
  835.             _csrfToken: '',
  836.  
  837.             /**
  838.              * URL to CSRF token refresh.
  839.              *
  840.              * @var string
  841.              */
  842.             _csrfRefreshUrl: '',
  843.  
  844.             _noSocialLogin: false,
  845.  
  846.             /**
  847.              * Speeds for animation
  848.              *
  849.              * @var object
  850.              */
  851.             speed:
  852.             {
  853.                 xxfast: 50,
  854.                 xfast: 100,
  855.                 fast: 200,
  856.                 normal: 400,
  857.                 slow: 600
  858.             },
  859.  
  860.             /**
  861.              * Multiplier for animation speeds
  862.              *
  863.              * @var float
  864.              */
  865.             _animationSpeedMultiplier: 1,
  866.  
  867.             /**
  868.              * Enable overlays or use regular pages
  869.              *
  870.              * @var boolean
  871.              */
  872.             _enableOverlays: true,
  873.  
  874.             /**
  875.              * Enables AJAX submission via AutoValidator. Doesn't change things other than
  876.              * that. Useful to disable for debugging.
  877.              *
  878.              * @var boolean
  879.              */
  880.             _enableAjaxSubmit: true,
  881.  
  882.             /**
  883.              * Determines whether the lightbox shows all images from the current page,
  884.              * or just from an individual message
  885.              *
  886.              * @var boolean
  887.              */
  888.             _lightBoxUniversal: false,
  889.  
  890.             /**
  891.              * @var object Phrases
  892.              */
  893.             phrases: {},
  894.  
  895.             /**
  896.              * Binds all registered functions to elements within the DOM
  897.              */
  898.             init: function () {
  899.                 var dStart = new Date(),
  900.                     xfFocus = function () {
  901.                         XenForo._hasFocus = true;
  902.                         $(document).triggerHandler('XenForoWindowFocus');
  903.                     },
  904.                     xfBlur = function () {
  905.                         XenForo._hasFocus = false;
  906.                         $(document).triggerHandler('XenForoWindowBlur');
  907.                     },
  908.                     $html = $('html');
  909.  
  910.                 if ($.browser.msie) {
  911.                     $(document).bind(
  912.                         {
  913.                             focusin: xfFocus,
  914.                             focusout: xfBlur
  915.                         });
  916.                 } else {
  917.                     $(window).bind(
  918.                         {
  919.                             focus: xfFocus,
  920.                             blur: xfBlur
  921.                         });
  922.                 }
  923.  
  924.                 $(window).on('resize', function () {
  925.                     XenForo.checkQuoteSizing($(document));
  926.                 });
  927.  
  928.                 // Set the animation speed based around the style property speed multiplier
  929.                 XenForo.setAnimationSpeed(XenForo._animationSpeedMultiplier);
  930.  
  931.                 // Periodical timestamp refresh
  932.                 XenForo._TimestampRefresh = new XenForo.TimestampRefresh();
  933.  
  934.                 // Find any ignored content that has not been picked up by PHP
  935.                 XenForo.prepareIgnoredContent();
  936.  
  937.                 // init ajax progress indicators
  938.                 XenForo.AjaxProgress();
  939.  
  940.                 // Activate all registered controls
  941.                 XenForo.activate(document);
  942.  
  943.                 $(document).on('click', '.bbCodeQuote .quoteContainer .quoteExpand', function (e) {
  944.                     $(this).closest('blockquote.quote').css('max-height', 'unset')
  945.                     $(this).closest('.quoteContainer').toggleClass('expanded');
  946.                 });
  947.  
  948.                 XenForo.watchProxyLinks();
  949.                 if (!XenForo._noRtnProtect) {
  950.                     XenForo.watchExternalLinks();
  951.                 }
  952.  
  953.                 // make the breadcrumb and navigation responsive
  954.                 if (!$html.hasClass('NoResponsive')) {
  955.                     XenForo.updateVisibleBreadcrumbs();
  956.                     XenForo.updateVisibleNavigationTabs();
  957.                     XenForo.updateVisibleNavigationLinks();
  958.  
  959.                     var resizeTimer, htmlWidth = $html.width();
  960.                     $(window).on('resize orientationchange load', function (e) {
  961.                         if (resizeTimer) {
  962.                             return;
  963.                         }
  964.                         if (e.type != 'load' && $html.width() == htmlWidth) {
  965.                             return;
  966.                         }
  967.                         htmlWidth = $html.width();
  968.                         resizeTimer = setTimeout(function () {
  969.                             resizeTimer = 0;
  970.                             XenForo.updateVisibleBreadcrumbs();
  971.                             XenForo.updateVisibleNavigationTabs();
  972.                             XenForo.updateVisibleNavigationLinks();
  973.                         }, 20);
  974.                     });
  975.                     $(document).on('click', '.breadcrumb .placeholder', function () {
  976.                         $(this).closest('.breadcrumb').addClass('showAll');
  977.                         XenForo.updateVisibleBreadcrumbs();
  978.                     });
  979.                 }
  980.  
  981.                 // Periodical CSRF token refresh
  982.                 XenForo._CsrfRefresh = new XenForo.CsrfRefresh();
  983.  
  984.                 // Autofocus for non-supporting browsers
  985.                 if (!('autofocus' in document.createElement('input'))) {
  986.                     //TODO: work out a way to prevent focusing if something else already has focus http://www.w3.org/TR/html5/forms.html#attr-fe-autofocus
  987.                     $('input[autofocus], textarea[autofocus], select[autofocus]').first().focus();
  988.                 }
  989.  
  990.  
  991.                 // init Tweet buttons
  992.                 XenForo.tweetButtonInit();
  993.  
  994.                 console.info('XenForo.init() %dms. jQuery %s/%s', new Date() - dStart, $().jquery, $.tools.version);
  995.  
  996.                 if ($('#ManualDeferredTrigger').length) {
  997.                     setTimeout(XenForo.manualDeferredHandler, 100);
  998.                 }
  999.  
  1000.                 if ($('html.RunDeferred').length) {
  1001.                     setTimeout(XenForo.runAutoDeferred, 100);
  1002.                 }
  1003.             },
  1004.  
  1005.             runAutoDeferred: function () {
  1006.                 XenForo.ajax('deferred.php', {}, function (ajaxData) {
  1007.                     if (ajaxData && ajaxData.moreDeferred) {
  1008.                         setTimeout(XenForo.runAutoDeferred, 100);
  1009.                     }
  1010.                 }, { error: false, global: false });
  1011.             },
  1012.  
  1013.             prepareIgnoredContent: function () {
  1014.                 var $displayLink = $('a.DisplayIgnoredContent'),
  1015.                     namesObj = {},
  1016.                     namesArr = [];
  1017.  
  1018.                 if ($displayLink.length) {
  1019.                     $('.ignored').each(function () {
  1020.                         var name = $(this).data('author');
  1021.                         if (name) {
  1022.                             namesObj[name] = true;
  1023.                         }
  1024.                     });
  1025.  
  1026.                     $.each(namesObj, function (name) {
  1027.                         namesArr.push(name);
  1028.                     });
  1029.  
  1030.                     if (namesArr.length) {
  1031.                         $displayLink.attr('title', XenForo.phrases['show_hidden_content_by_x'].replace(/\{names\}/, namesArr.join(', ')));
  1032.                         $displayLink.parent().show();
  1033.                     }
  1034.                 }
  1035.             },
  1036.  
  1037.             watchProxyLinks: function () {
  1038.                 var proxyLinkClick = function (e) {
  1039.                     var $this = $(this),
  1040.                         proxyHref = $this.data('proxy-href'),
  1041.                         lastEvent = $this.data('proxy-handler-last');
  1042.  
  1043.                     if (!proxyHref) {
  1044.                         return;
  1045.                     }
  1046.  
  1047.                     // we may have a direct click event and a bubbled event. Ensure they don't both fire.
  1048.                     if (lastEvent && lastEvent == e.timeStamp) {
  1049.                         return;
  1050.                     }
  1051.                     $this.data('proxy-handler-last', e.timeStamp);
  1052.  
  1053.                     XenForo.ajax(proxyHref, {}, function (ajaxData) {
  1054.                     }, { error: false, global: false });
  1055.                 };
  1056.  
  1057.                 $(document)
  1058.                     .on('click', 'a.ProxyLink', proxyLinkClick)
  1059.                     .on('focusin', 'a.ProxyLink', function (e) {
  1060.                         // This approach is taken because middle click events do not bubble. This is a way of
  1061.                         // getting the equivalent of event bubbling on middle clicks in Chrome.
  1062.                         var $this = $(this);
  1063.                         if ($this.data('proxy-handler')) {
  1064.                             return;
  1065.                         }
  1066.  
  1067.                         $this.data('proxy-handler', true)
  1068.                             .click(proxyLinkClick);
  1069.                     });
  1070.             },
  1071.  
  1072.             watchExternalLinks: function () {
  1073.                 var externalLinkClick = function (e) {
  1074.                     if (e.isDefaultPrevented()) {
  1075.                         return;
  1076.                     }
  1077.  
  1078.                     var $this = $(this),
  1079.                         href = $this.attr('href'),
  1080.                         lastEvent = $this.data('blank-handler-last');
  1081.                     if (!href) {
  1082.                         return;
  1083.                     }
  1084.  
  1085.                     const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
  1086.                     if (!urlRegExp.test(href)) {
  1087.                         return;
  1088.                     }
  1089.  
  1090.                     href = XenForo.canonicalizeUrl(href);
  1091.  
  1092.                     var regex = new RegExp('^[a-z]+://' + location.host + '(/|$|:)', 'i');
  1093.                     if (regex.test(href) && !$this.hasClass('ProxyLink')) {
  1094.                         // if the link is local, then don't do the special processing... unless it's a proxy link
  1095.                         // so it's likely to be external after the redirect
  1096.                         return;
  1097.                     }
  1098.  
  1099.                     // we may have a direct click event and a bubbled event. Ensure they don't both fire.
  1100.                     if (lastEvent && lastEvent == e.timeStamp) {
  1101.                         return;
  1102.                     }
  1103.  
  1104.                     $this.data('blank-handler-last', e.timeStamp);
  1105.  
  1106.                     var ua = navigator.userAgent,
  1107.                         isOldIE = ua.indexOf('MSIE') !== -1,
  1108.                         isSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') == -1,
  1109.                         isGecko = ua.indexOf('Gecko/') !== -1;
  1110.  
  1111.                     if (e.shiftKey && isGecko) {
  1112.                         // Firefox doesn't trigger when holding shift. If the code below runs, it will force
  1113.                         // opening in a new tab instead of a new window, so stop. Note that Chrome still triggers here,
  1114.                         // but it does open in a new window anyway so we run the normal code.
  1115.                         return;
  1116.                     }
  1117.                     if (isSafari && (e.shiftKey || e.altKey)) {
  1118.                         // this adds to reading list or downloads instead of opening a new tab
  1119.                         return;
  1120.                     }
  1121.                     if (isOldIE) {
  1122.                         // IE has mitigations for this and this blocks referrers
  1123.                         return;
  1124.                     }
  1125.  
  1126.                     // now run the opener clearing
  1127.  
  1128.                     if (isSafari) {
  1129.                         // Safari doesn't work with the other approach
  1130.                         // Concept from: https://github.com/danielstjules/blankshield
  1131.                         var $iframe, iframeDoc, $script;
  1132.  
  1133.                         $iframe = $('<iframe style="display: none" />').appendTo(document.body);
  1134.                         iframeDoc = $iframe[0].contentDocument || $iframe[0].contentWindow.document;
  1135.  
  1136.                         iframeDoc.__href = href; // set this so we don't need to do an eval-type thing
  1137.  
  1138.                         $script = $('<script />', iframeDoc);
  1139.                         $script[0].text = 'window.opener=null;' +
  1140.                             'window.parent=null;window.top=null;window.frameElement=null;' +
  1141.                             'window.open(document.__href).opener = null;';
  1142.  
  1143.                         iframeDoc.body.appendChild($script[0]);
  1144.                         $iframe.remove();
  1145.                     } else {
  1146.                         // use this approach for the rest to maintain referrers when possible
  1147.                         var w = window.open(href);
  1148.  
  1149.                         try {
  1150.                             // this can potentially fail, don't want to break
  1151.                             w.opener = null;
  1152.                         } catch (e) {
  1153.                         }
  1154.                     }
  1155.  
  1156.                     e.preventDefault();
  1157.                 };
  1158.  
  1159.                 $(document)
  1160.                     .on('click', 'a[target=_blank]:not(.fr-element a)', externalLinkClick)
  1161.                     .on('focusin', 'a[target=_blank]:not(.fr-element a)', function (e) {
  1162.                         // This approach is taken because middle click events do not bubble. This is a way of
  1163.                         // getting the equivalent of event bubbling on middle clicks in Chrome.
  1164.                         var $this = $(this);
  1165.                         if ($this.data('blank-handler')) {
  1166.                             return;
  1167.                         }
  1168.  
  1169.                         $this.data('blank-handler', true)
  1170.                             .click(externalLinkClick);
  1171.                     });
  1172.             },
  1173.  
  1174.             /**
  1175.              * Asynchronously load the specified JavaScript, with an optional callback on completion.
  1176.              *
  1177.              * @param string Script source
  1178.              * @param object Callback function
  1179.              * @param string innerHtml for the script tags
  1180.              */
  1181.             loadJs: function (src, callback, innerHTML) {
  1182.                 try {
  1183.                     var script = document.createElement('script');
  1184.                     script.async = true;
  1185.                     if (innerHTML) {
  1186.                         try {
  1187.                             script.innerHTML = innerHTML;
  1188.                         } catch (e2) {
  1189.                         }
  1190.                     }
  1191.                     var f = function () {
  1192.                         if (callback) {
  1193.                             callback();
  1194.                             callback = null;
  1195.                         }
  1196.                     };
  1197.                     script.onload = f;
  1198.                     script.onreadystatechange = function () {
  1199.                         if (script.readyState === 'loaded') {
  1200.                             f();
  1201.                         }
  1202.                     };
  1203.                     script.src = src;
  1204.                     document.getElementsByTagName('head')[0].appendChild(script);
  1205.                 } catch (e) {
  1206.                 }
  1207.             },
  1208.  
  1209.             /**
  1210.              * Asynchronously load the Twitter button JavaScript.
  1211.              */
  1212.             tweetButtonInit: function () {
  1213.                 if ($('a.twitter-share-button').length) {
  1214.                     XenForo.loadJs('https://platform.twitter.com/widgets.js');
  1215.                 }
  1216.             },
  1217.  
  1218.             /**
  1219.              * Asynchronously load the +1 button JavaScript.
  1220.              */
  1221.             plusoneButtonInit: function (el) {
  1222.                 if ($(el).find('div.g-plusone, .GoogleLogin').length) {
  1223.                     var locale = $('html').attr('lang');
  1224.  
  1225.                     var callback = function () {
  1226.                         if (!window.gapi) {
  1227.                             return;
  1228.                         }
  1229.  
  1230.                         $(el).find('.GoogleLogin').each(function () {
  1231.                             var $button = $(this),
  1232.                                 clientId = $button.data('client-id'),
  1233.                                 auth2;
  1234.  
  1235.                             gapi.load('auth2', function () {
  1236.                                 auth2 = gapi.auth2.init({
  1237.                                     client_id: clientId,
  1238.                                     scope: 'profile email',
  1239.                                     response_type: 'permission',
  1240.                                     cookie_policy: 'single_host_origin'
  1241.                                 });
  1242.                                 $button.on('click', function () {
  1243.                                     auth2.grantOfflineAccess({
  1244.                                         scope: 'profile email'
  1245.                                     })
  1246.                                         .then(function (resp) {
  1247.                                             var code = resp.code;
  1248.  
  1249.                                             if (code) {
  1250.                                                 window.location = XenForo.canonicalizeUrl(
  1251.                                                     $button.data('redirect-url').replace('__CODE__', code)
  1252.                                                 );
  1253.                                             }
  1254.                                         })
  1255.                                         .catch(function () {
  1256.                                         });
  1257.                                 });
  1258.  
  1259.  
  1260.                             });
  1261.                         });
  1262.                     };
  1263.  
  1264.                     if (window.___gcfg && window.gapi) {
  1265.                         callback();
  1266.                     } else {
  1267.                         window.___gcfg = {
  1268.                             lang: locale,
  1269.                             isSignedOut: true // this is to stop the "welcome back" prompt as it doesn't fit with our flow
  1270.                         };
  1271.  
  1272.                         XenForo.loadJs('https://apis.google.com/js/api:client.js', callback);
  1273.                     }
  1274.                 }
  1275.             },
  1276.  
  1277.             /**
  1278.              * Prevents Google Chrome's AutoFill from turning inputs yellow.
  1279.              * Adapted from http://www.benjaminmiles.com/2010/11/22/fixing-google-chromes-yellow-autocomplete-styles-with-jquery/
  1280.              */
  1281.             chromeAutoFillFix: function ($root) {
  1282.                 if ($.browser.webkit && navigator.userAgent.toLowerCase().indexOf('chrome') >= 0) {
  1283.                     if (!$root) {
  1284.                         $root = $(document);
  1285.                     }
  1286.  
  1287.                     // trap an error here - CloudFlare RocketLoader causes an error with this.
  1288.                     var $inputs;
  1289.                     try {
  1290.                         $inputs = $root.find('input:-webkit-autofill');
  1291.                     } catch (e) {
  1292.                         $inputs = $([]);
  1293.                     }
  1294.  
  1295.                     if ($inputs.length) {
  1296.                         $inputs.each(function (i) {
  1297.                             var $this = $(this),
  1298.                                 val = $this.val();
  1299.  
  1300.                             if (!val || !val.length) {
  1301.                                 return;
  1302.                             }
  1303.  
  1304.                             $this.after($this.clone(true).val(val)).remove();
  1305.                         });
  1306.                     }
  1307.                 }
  1308.             },
  1309.  
  1310.             updateVisibleBreadcrumbs: function () {
  1311.                 $('.breadcrumb').each(function () {
  1312.                     var container = this,
  1313.                         $container = $(container);
  1314.  
  1315.                     $container.find('.placeholder').remove();
  1316.  
  1317.                     var $crusts = $container.find('.crust');
  1318.                     $crusts.removeClass('firstVisibleCrumb').show();
  1319.  
  1320.                     var $homeCrumb = $crusts.filter('.homeCrumb');
  1321.  
  1322.                     $container.css('height', '');
  1323.                     var beforeHeight = container.offsetHeight;
  1324.                     $container.css('height', 'auto');
  1325.  
  1326.                     if (container.offsetHeight <= beforeHeight) {
  1327.                         $container.css('height', '');
  1328.                         return;
  1329.                     }
  1330.  
  1331.                     var $lastHidden = null,
  1332.                         hideSkipSelector = '.selectedTabCrumb, :last-child';
  1333.  
  1334.                     $crusts.each(function () {
  1335.                         var $crust = $(this);
  1336.                         if ($crust.is(hideSkipSelector)) {
  1337.                             return true;
  1338.                         }
  1339.  
  1340.                         $crust.hide();
  1341.                         $lastHidden = $crust;
  1342.                         return (container.offsetHeight > beforeHeight);
  1343.                     });
  1344.  
  1345.                     if (!$lastHidden) {
  1346.                         $container.css('height', '');
  1347.                         return;
  1348.                     }
  1349.  
  1350.                     var $placeholder = $('<span class="crust placeholder"><a class="crumb" href="javascript:"><span>...</span></a><span class="arrow"><span>&gt;</span></span></span>');
  1351.                     $lastHidden.after($placeholder);
  1352.  
  1353.                     if (container.offsetHeight > beforeHeight) {
  1354.                         var $prev = $lastHidden.prevAll('.crust:not(' + hideSkipSelector + ')').last();
  1355.                         if ($prev.length) {
  1356.                             $prev.hide();
  1357.                         }
  1358.                     }
  1359.  
  1360.                     if (container.offsetHeight > beforeHeight) {
  1361.                         var $next = $lastHidden.nextAll('.crust:not(.placeholder, ' + hideSkipSelector + ')').first();
  1362.                         if ($next.length) {
  1363.                             $next.hide();
  1364.                             $next.after($placeholder);
  1365.                         }
  1366.                     }
  1367.  
  1368.                     if ($homeCrumb.length && !$homeCrumb.is(':visible')) {
  1369.                         $container.find('.crust:visible:first').addClass('firstVisibleCrumb');
  1370.                     }
  1371.  
  1372.                     if (container.offsetHeight <= beforeHeight) {
  1373.                         // firefox doesn't seem to contain the breadcrumbs despite the overflow hidden
  1374.                         $container.css('height', '');
  1375.                     }
  1376.                 });
  1377.             },
  1378.  
  1379.             updateVisibleNavigationTabs: function () {
  1380.                 var $tabs = $('#navigation').find('.navTabs');
  1381.                 if (!$tabs.length) {
  1382.                     return;
  1383.                 }
  1384.  
  1385.                 var tabsCoords = $tabs.coords(),
  1386.                     $publicTabs = $tabs.find('.publicTabs'),
  1387.                     $publicInnerTabs = $publicTabs.find('> .navTab'),
  1388.                     $visitorTabs = $tabs.find('.visitorTabs'),
  1389.                     $visitorInnerTabs = $visitorTabs.find('> .navTab'),
  1390.                     $visitorCounter = $('#VisitorExtraMenu_Counter'),
  1391.                     maxPublicWidth,
  1392.                     $hiddenTab = $publicInnerTabs.filter('.navigationHiddenTabs');
  1393.  
  1394.                 $publicInnerTabs.show();
  1395.                 $hiddenTab.hide();
  1396.  
  1397.                 $visitorInnerTabs.show();
  1398.                 $visitorCounter.addClass('ResponsiveOnly');
  1399.  
  1400.                 if ($tabs.is('.showAll')) {
  1401.                     return;
  1402.                 }
  1403.  
  1404.                 maxPublicWidth = $tabs.width() - $visitorTabs.width() - 1;
  1405.  
  1406.                 var hidePublicTabs = function () {
  1407.                     var shownSel = '.selected, .navigationHiddenTabs';
  1408.  
  1409.                     var $hideable = $publicInnerTabs.filter(':not(' + shownSel + ')'),
  1410.                         $hiddenList = $('<ul />'),
  1411.                         hiddenCount = 0,
  1412.                         overflowMenuShown = false;
  1413.  
  1414.                     $.each($hideable.get().reverse(), function () {
  1415.                         var $this = $(this);
  1416.                         if (isOverflowing($publicTabs.coords(), true)) {
  1417.                             $hiddenList.prepend(
  1418.                                 $('<li />').html($this.find('.navLink').clone())
  1419.                             );
  1420.                             $this.hide();
  1421.                             hiddenCount++;
  1422.                         } else {
  1423.                             if (hiddenCount) {
  1424.                                 $hiddenTab.show();
  1425.  
  1426.                                 if (isOverflowing($publicTabs.coords(), true)) {
  1427.                                     $hiddenList.prepend(
  1428.                                         $('<li />').html($this.find('.navLink').clone())
  1429.                                     );
  1430.                                     $this.hide();
  1431.                                     hiddenCount++;
  1432.                                 }
  1433.                                 $('#NavigationHiddenMenu').html($hiddenList).xfActivate();
  1434.                                 overflowMenuShown = true;
  1435.                             } else {
  1436.                                 $hiddenTab.hide();
  1437.                             }
  1438.  
  1439.                             return false;
  1440.                         }
  1441.                     });
  1442.  
  1443.                     if (hiddenCount && !overflowMenuShown) {
  1444.                         $hiddenTab.show();
  1445.                         $('#NavigationHiddenMenu').html($hiddenList).xfActivate();
  1446.                     }
  1447.                 },
  1448.                     hideVisitorTabs = function () {
  1449.                         $visitorInnerTabs.hide();
  1450.                         $visitorInnerTabs.filter('.account, .selected').show();
  1451.                         $visitorCounter.removeClass('ResponsiveOnly');
  1452.                     },
  1453.                     isOverflowing = function (coords, checkMax) {
  1454.                         if (
  1455.                             coords.top >= tabsCoords.top + tabsCoords.height
  1456.                             || coords.height >= tabsCoords.height * 2
  1457.                         ) {
  1458.                             return true;
  1459.                         }
  1460.  
  1461.                         if (checkMax && coords.width > maxPublicWidth) {
  1462.                             return true;
  1463.                         }
  1464.  
  1465.                         return false;
  1466.                     };
  1467.  
  1468.                 if ($visitorTabs.length) {
  1469.                     if (isOverflowing($visitorTabs.coords())) {
  1470.                         hidePublicTabs();
  1471.  
  1472.                         if (isOverflowing($visitorTabs.coords())) {
  1473.                             hideVisitorTabs();
  1474.                         }
  1475.                     }
  1476.                 } else if (isOverflowing($publicTabs.coords())) {
  1477.                     hidePublicTabs();
  1478.                 }
  1479.             },
  1480.  
  1481.             updateVisibleNavigationLinks: function () {
  1482.                 var $linksList = $('#navigation').find('.navTab.selected .blockLinksList');
  1483.                 if (!$linksList.length) {
  1484.                     return;
  1485.                 }
  1486.  
  1487.                 var $links = $linksList.find('> li'),
  1488.                     listOffset = $linksList.offset(),
  1489.                     $hidden = $links.filter('.navigationHidden'),
  1490.                     $firstHidden = false;
  1491.  
  1492.                 $links.show();
  1493.                 $hidden.hide();
  1494.  
  1495.                 if ($linksList.is('.showAll')) {
  1496.                     return;
  1497.                 }
  1498.  
  1499.                 var hiddenForMenu = [],
  1500.                     $lastLink = $links.filter(':not(.navigationHidden)').last(),
  1501.                     hideOffset = 0,
  1502.                     hasHidden = false,
  1503.                     lastCoords,
  1504.                     $link;
  1505.  
  1506.                 if (!$lastLink.length) {
  1507.                     return;
  1508.                 }
  1509.  
  1510.                 do {
  1511.                     lastCoords = $lastLink.coords();
  1512.                     if (lastCoords.top > listOffset.top + lastCoords.height) {
  1513.                         $link = $links.eq(hideOffset);
  1514.                         $link.hide();
  1515.                         hiddenForMenu.push($link);
  1516.                         hideOffset++;
  1517.  
  1518.                         if (!hasHidden) {
  1519.                             hasHidden = true;
  1520.  
  1521.                             if (!$hidden.length) {
  1522.                                 $hidden = $('<li class="navigationHidden Popup PopupControl PopupClosed"><a rel="Menu" class="NoPopupGadget">...</a><div class="Menu blockLinksList primaryContent" id="NavigationLinksHiddenMenu"></div></li>');
  1523.                                 $linksList.append($hidden);
  1524.                                 new XenForo.PopupMenu($hidden);
  1525.                             } else {
  1526.                                 $hidden.show();
  1527.                             }
  1528.                         }
  1529.                     } else {
  1530.                         break;
  1531.                     }
  1532.                 }
  1533.                 while (hideOffset < $links.length);
  1534.  
  1535.                 if (hasHidden) {
  1536.                     if (hideOffset < $links.length) {
  1537.                         var coords = $hidden.coords();
  1538.                         if (coords.top > listOffset.top + coords.height) {
  1539.                             $link = $links.eq(hideOffset);
  1540.                             $link.hide();
  1541.                             hiddenForMenu.push($link);
  1542.                         }
  1543.                     }
  1544.  
  1545.                     var $hiddenList = $('<ul />');
  1546.                     $(hiddenForMenu).each(function () {
  1547.                         $hiddenList.append(
  1548.                             $('<li />').html($(this).find('a').clone())
  1549.                         );
  1550.                     });
  1551.                     $('#NavigationLinksHiddenMenu').html($hiddenList).xfActivate();
  1552.                 }
  1553.             },
  1554.  
  1555.             /**
  1556.              * Binds a function to elements to fire on a custom event
  1557.              *
  1558.              * @param string jQuery selector - to get the elements to be bound
  1559.              * @param function Function to fire
  1560.              * @param string Custom event name (if empty, assume 'XenForoActivateHtml')
  1561.              */
  1562.             register: function (selector, fn, event) {
  1563.                 if (typeof fn == 'string') {
  1564.                     var className = fn;
  1565.                     fn = function (i) {
  1566.                         XenForo.create(className, this);
  1567.                     };
  1568.                 }
  1569.  
  1570.                 $(document).bind(event || 'XenForoActivateHtml', function (e) {
  1571.                     $(e.element).find(selector).each(fn);
  1572.                 });
  1573.             },
  1574.  
  1575.             /**
  1576.              * Creates a new object of class XenForo.{functionName} using
  1577.              * the specified element, unless one has already been created.
  1578.              *
  1579.              * @param string Function name (property of XenForo)
  1580.              * @param object HTML element
  1581.              *
  1582.              * @return object XenForo[functionName]($(element))
  1583.              */
  1584.             create: function (className, element) {
  1585.                 var $element = $(element),
  1586.                     xfObj = window,
  1587.                     parts = className.split('.'), i;
  1588.  
  1589.                 for (i = 0; i < parts.length; i++) {
  1590.                     xfObj = xfObj[parts[i]];
  1591.                 }
  1592.  
  1593.                 if (typeof xfObj != 'function') {
  1594.                     return console.error('%s is not a function.', className);
  1595.                 }
  1596.  
  1597.                 if (!$element.data(className)) {
  1598.                     $element.data(className, new xfObj($element));
  1599.                 }
  1600.  
  1601.                 return $element.data(className);
  1602.             },
  1603.  
  1604.             /**
  1605.              * Fire the initialization events and activate functions for the specified element
  1606.              *
  1607.              * @param object Usually jQuery
  1608.              *
  1609.              * @return object
  1610.              */
  1611.             activate: function (element) {
  1612.                 var $element = $(element);
  1613.  
  1614.                 console.group('XenForo.activate(%o)', element);
  1615.  
  1616.                 $element.trigger('XenForoActivate').removeClass('__XenForoActivator');
  1617.                 $element.find('noscript').empty().remove();
  1618.  
  1619.                 XenForo._TimestampRefresh.refresh(element, true);
  1620.  
  1621.                 $(document)
  1622.                     .trigger({ element: element, type: 'XenForoActivateHtml' })
  1623.                     .trigger({ element: element, type: 'XenForoActivatePopups' })
  1624.                     .trigger({ element: element, type: 'XenForoActivationComplete' });
  1625.  
  1626.                 var $form = $element.find('form.AutoSubmit:first');
  1627.                 if ($form.length) {
  1628.                     $(document).trigger('PseudoAjaxStart');
  1629.                     $form.submit();
  1630.                     $form.find('input[type="submit"], input[type="reset"]').hide();
  1631.                 }
  1632.  
  1633.                 XenForo.checkQuoteSizing($element);
  1634.                 XenForo.plusoneButtonInit(element);
  1635.                 //XenForo.Facebook.start();
  1636.  
  1637.                 console.groupEnd();
  1638.  
  1639.                 return element;
  1640.             },
  1641.  
  1642.             checkQuoteSizing: function ($element) {
  1643.                 $element.find('.bbCodeQuote .quoteContainer').each(function () {
  1644.                     var self = this,
  1645.                         delay = 0,
  1646.                         checkHeight = function () {
  1647.                             var $self = $(self),
  1648.                                 quote = $self.find('.quote')[0];
  1649.  
  1650.                             if (!quote) {
  1651.                                 return;
  1652.                             }
  1653.  
  1654.                             if (quote.scrollHeight == 0 || quote.offsetHeight == 0) {
  1655.                                 if (delay < 2000) {
  1656.                                     setTimeout(checkHeight, delay);
  1657.                                     delay += 100;
  1658.                                 }
  1659.                                 return;
  1660.                             }
  1661.  
  1662.                             // +1 resolves a chrome rounding issue
  1663.                             if (quote.scrollHeight > parseInt($(quote).css('max-height')) && quote.scrollHeight > quote.offsetHeight + 1) {
  1664.                                 $self.find('.quoteExpand').addClass('quoteCut');
  1665.                             } else {
  1666.                                 $self.find('.quoteExpand').removeClass('quoteCut');
  1667.                             }
  1668.                         };
  1669.  
  1670.                     checkHeight();
  1671.                     $(this).find('img, iframe').one('load', checkHeight);
  1672.                     $(this).on('elementResized', checkHeight);
  1673.                 });
  1674.             },
  1675.  
  1676.             /**
  1677.              * Pushes an additional parameter onto the data to be submitted via AJAX
  1678.              *
  1679.              * @param array|string Data parameters - either from .serializeArray() or .serialize()
  1680.              * @param string Name of parameter
  1681.              * @param mixed Value of parameter
  1682.              *
  1683.              * @return array|string Data including new parameter
  1684.              */
  1685.             ajaxDataPush: function (data, name, value) {
  1686.                 if (!data || typeof data == 'string') {
  1687.                     // data is empty, or a url string - &name=value
  1688.                     data = String(data);
  1689.                     data += '&' + encodeURIComponent(name) + '=' + encodeURIComponent(value);
  1690.                 } else if (data instanceof FormData) {
  1691.                     // data is FormData
  1692.                     data.append(name, value)
  1693.                 } else if (data[0] !== undefined) {
  1694.                     // data is a numerically-keyed array of name/value pairs
  1695.                     data.push({ name: name, value: value });
  1696.                 } else {
  1697.                     // data is an object with a single set of name & value properties
  1698.                     data[name] = value;
  1699.                 }
  1700.  
  1701.                 return data;
  1702.             },
  1703.  
  1704.             /**
  1705.              * Wraps around jQuery's own $.ajax function, with our own defaults provided.
  1706.              * Will submit via POST and expect JSON back by default.
  1707.              * Server errors will be handled using XenForo.handleServerError
  1708.              *
  1709.              * @param string URL to load
  1710.              * @param object Data to pass
  1711.              * @param function Success callback function
  1712.              * @param object Additional options to override or extend defaults
  1713.              *
  1714.              * @return XMLHttpRequest
  1715.              */
  1716.             ajax: function (url, data, success, options) {
  1717.                 if (!url) {
  1718.                     return console.error('No URL specified for XenForo.ajax()');
  1719.                 }
  1720.  
  1721.                 url = XenForo.canonicalizeUrl(url, XenForo.ajaxBaseHref);
  1722.                 try {
  1723.                     let url_element = new URL(url);
  1724.                     if (url_element.pathname === "/index.php") {
  1725.                         url_element.pathname = url_element.search.split("&")[0].slice(1)
  1726.                         url_element.search = "?" + url_element.search.slice(url_element.search.split("&")[0].length + 1)
  1727.                     }
  1728.                     if (url_element.pathname.indexOf('&') !== -1) {
  1729.                         let elements = url_element.pathname.split("&");
  1730.                         url_element.pathname = elements[0]
  1731.                         elements.shift();
  1732.                         url_element.search = "?" + elements.join("&")
  1733.                     }
  1734.                     url = url_element.toString()
  1735.                 } catch (e) {
  1736.  
  1737.                 }
  1738.  
  1739.                 data = XenForo.ajaxDataPush(data, '_xfRequestUri', window.location.pathname + window.location.search);
  1740.                 data = XenForo.ajaxDataPush(data, '_xfNoRedirect', 1);
  1741.                 if (XenForo._csrfToken) {
  1742.                     data = XenForo.ajaxDataPush(data, '_xfToken', XenForo._csrfToken);
  1743.                 }
  1744.  
  1745.                 var successCallback = function (ajaxData, textStatus) {
  1746.                     if (typeof ajaxData == 'object') {
  1747.                         if (typeof ajaxData._visitor_conversationsUnread != 'undefined') {
  1748.                             if ($('#AlertsMenu_Counter').length && ajaxData._visitor_alertsUnread != '0' && $('#AlertsMenu_Counter').text().trim() != ajaxData._visitor_alertsUnread) {
  1749.                                 const svg = $('#AlertsMenu_Counter')
  1750.                                     .closest('.counter-container')
  1751.                                     .find('svg')
  1752.                                     .attr('class', '') // через addClass РЅР° svg РЅРµ хочет
  1753.                                 setTimeout(() => { svg.attr('class', 'animated tada') }, 1)
  1754.                             }
  1755.                             XenForo.balloonCounterUpdate($('#ConversationsMenu_Counter'), ajaxData._visitor_conversationsUnread);
  1756.                             XenForo.balloonCounterUpdate($('#AlertsMenu_Counter'), ajaxData._visitor_alertsUnread);
  1757.                             XenForo.balloonCounterUpdate($('#VisitorExtraMenu_ConversationsCounter'), ajaxData._visitor_conversationsUnread);
  1758.                             XenForo.balloonCounterUpdate($('#VisitorExtraMenu_AlertsCounter'), ajaxData._visitor_alertsUnread);
  1759.                             XenForo.balloonCounterUpdate($('#VisitorExtraMenu_Counter'),
  1760.                                 (
  1761.                                     parseInt(ajaxData._visitor_conversationsUnread, 10) + parseInt(ajaxData._visitor_alertsUnread, 10)
  1762.                                     || 0
  1763.                                 ).toString()
  1764.                             );
  1765.  
  1766.                             var $alertPopup = $('.alerts.Popup')
  1767.                             if (+ajaxData._visitor_alertsUnread) {
  1768.                                 // https://zelenka.guru/threads/4456536/
  1769.                                 if ($alertPopup.data('XenForo.PopupMenu'))
  1770.                                     $alertPopup.data('XenForo.PopupMenu').$menu.find('.tabs li').first().click()
  1771.                             } else if (+ajaxData._visitor_alertsUnread && $alertPopup.is('.PopupOpen') && !ajaxData.error) {
  1772.                                 const popupMenu = $alertPopup.data('XenForo.PopupMenu')
  1773.                                 if (!popupMenu || !popupMenu.loading) return; // if not loaded yet
  1774.                                 XenForo.ajax(popupMenu.contentSrc, {}, ajaxData => {
  1775.                                     if (XenForo.hasResponseError(ajaxData)) return;
  1776.                                     popupMenu.$menu.find('ol').first().prepend($(ajaxData.templateHtml).find('li.Alert').filter(function () {
  1777.                                         const id = $(this).attr('id')
  1778.                                         return !id || !$('#' + id).length
  1779.                                     })).xfActivate()
  1780.                                 })
  1781.                             }
  1782.                         }
  1783.  
  1784.                         if (ajaxData._manualDeferred) {
  1785.                             XenForo.manualDeferredHandler();
  1786.                         } else if (ajaxData._autoDeferred) {
  1787.                             XenForo.runAutoDeferred();
  1788.                         }
  1789.                     }
  1790.  
  1791.                     $(document).trigger(
  1792.                         {
  1793.                             type: 'XFAjaxSuccess',
  1794.                             ajaxData: ajaxData,
  1795.                             textStatus: textStatus
  1796.                         });
  1797.  
  1798.                     if (success) success.call(null, ajaxData, textStatus);
  1799.                 };
  1800.  
  1801.                 var referrer = window.location.href;
  1802.                 if (referrer.match(/[^\x20-\x7f]/)) {
  1803.                     var a = document.createElement('a');
  1804.                     a.href = '';
  1805.                     referrer = referrer.replace(a.href, XenForo.baseUrl());
  1806.                 }
  1807.                 if (data instanceof FormData)
  1808.                     options = $.extend(true,
  1809.                         {
  1810.                             processData: false,
  1811.                             contentType: false
  1812.                         }, options);
  1813.                 options = $.extend(true,
  1814.                     {
  1815.                         data: data,
  1816.                         url: url,
  1817.                         success: successCallback,
  1818.                         type: 'POST',
  1819.                         dataType: 'json',
  1820.                         xhrFields: {
  1821.                             withCredentials: true
  1822.                         },
  1823.                         error: function (xhr, textStatus, errorThrown) {
  1824.                             if (xhr.readyState == 0) {
  1825.                                 return;
  1826.                             }
  1827.  
  1828.                             try {
  1829.                                 // attempt to pass off to success, if we can decode JSON from the response
  1830.                                 successCallback.call(null, $.parseJSON(xhr.responseText), textStatus);
  1831.                             } catch (e) {
  1832.                                 // not valid JSON, trigger server error handler
  1833.                                 XenForo.handleServerError(xhr, textStatus, errorThrown, {
  1834.                                     success: successCallback,
  1835.                                     options: options,
  1836.                                     url: url,
  1837.                                     data: data
  1838.                                 });
  1839.                             }
  1840.                         },
  1841.                         headers: { 'X-Ajax-Referer': referrer },
  1842.                         timeout: 30000 // 30s
  1843.                     }, options);
  1844.  
  1845.                 // override standard extension, depending on dataType
  1846.                 if (!options.data._xfResponseType) {
  1847.                     switch (options.dataType) {
  1848.                         case 'html':
  1849.                         case 'json':
  1850.                         case 'xml': {
  1851.                             // pass _xfResponseType parameter to override default extension
  1852.                             options.data = XenForo.ajaxDataPush(options.data, '_xfResponseType', options.dataType);
  1853.                             break;
  1854.                         }
  1855.                     }
  1856.                 }
  1857.  
  1858.                 return $.ajax(options);
  1859.             },
  1860.  
  1861.             /**
  1862.              * Updates the total in one of the navigation balloons, showing or hiding if necessary
  1863.              *
  1864.              * @param jQuery $balloon
  1865.              * @param string counter
  1866.              */
  1867.             balloonCounterUpdate: function ($balloon, newTotal) {
  1868.                 if ($balloon.length) {
  1869.                     var $counter = $balloon.find('span.Total'),
  1870.                         oldTotal = $counter.text();
  1871.  
  1872.                     $counter.text(newTotal);
  1873.  
  1874.                     if (!newTotal || newTotal == '0') {
  1875.                         $balloon.fadeOut('fast', function () {
  1876.                             $balloon.addClass('Zero').css('display', '');
  1877.                         });
  1878.                     } else {
  1879.                         $balloon.fadeIn('fast', function () {
  1880.                             $balloon.removeClass('Zero').css('display', '');
  1881.  
  1882.                             var oldTotalInt = parseInt(oldTotal.replace(/[^\d]/, ''), 10),
  1883.                                 newTotalInt = parseInt(newTotal.replace(/[^\d]/, ''), 10),
  1884.                                 newDifference = newTotalInt - oldTotalInt;
  1885.  
  1886.                             if (newDifference > 0 && $balloon.data('text')) {
  1887.                                 var $container = $balloon.closest('.Popup'),
  1888.                                     PopupMenu = $container.data('XenForo.PopupMenu'),
  1889.                                     $message;
  1890.  
  1891.                                 $message = $('<a />').css('cursor', 'pointer').html($balloon.data('text').replace(/%d/, newDifference)).click(function (e) {
  1892.                                     if ($container.is(':visible') && PopupMenu) {
  1893.                                         PopupMenu.$clicker.trigger('click');
  1894.                                     } else if ($container.find('a[href]').length) {
  1895.                                         window.location = XenForo.canonicalizeUrl($container.find('a[href]').attr('href'));
  1896.                                     }
  1897.                                     return false;
  1898.                                 });
  1899.  
  1900.                                 if (PopupMenu && !PopupMenu.menuVisible) {
  1901.                                     PopupMenu.resetLoader();
  1902.                                 }
  1903.  
  1904.                                 //XenForo.stackAlert($message, 10000, $balloon);
  1905.                             }
  1906.                         });
  1907.                     }
  1908.                 }
  1909.             },
  1910.  
  1911.             _manualDeferUrl: '',
  1912.             _manualDeferOverlay: false,
  1913.             _manualDeferXhr: false,
  1914.  
  1915.             manualDeferredHandler: function () {
  1916.                 if (!XenForo._manualDeferUrl || XenForo._manualDeferOverlay) {
  1917.                     return;
  1918.                 }
  1919.  
  1920.                 var processing = XenForo.phrases['processing'] || 'Processing',
  1921.                     cancel = XenForo.phrases['cancel'] || 'Cancel',
  1922.                     cancelling = XenForo.phrases['cancelling'] || 'Cancelling';
  1923.  
  1924.                 var $html = $('<div id="ManualDeferOverlay" class="xenOverlay"><h2 class="titleBar">'
  1925.                     + processing + '... '
  1926.                     + '<a class="CancelDeferred button" data-cancelling="' + cancelling + '..." style="display:none">' + cancel + '</a></h2>'
  1927.                     + '<span class="processingText">' + processing + '...</span><span class="close"></span></div>');
  1928.  
  1929.                 $html.find('.CancelDeferred').click(function (e) {
  1930.                     e.preventDefault();
  1931.                     $.setCookie('cancel_defer', '1');
  1932.                     $(this).text($(this).data('cancelling'));
  1933.                 });
  1934.  
  1935.                 $html.appendTo('body').overlay($.extend(true, {
  1936.                     mask: {
  1937.                         color: 'white',
  1938.                         opacity: 0.77,
  1939.                         loadSpeed: XenForo.speed.normal,
  1940.                         closeSpeed: XenForo.speed.fast
  1941.                     },
  1942.                     closeOnClick: false,
  1943.                     closeOnEsc: false,
  1944.                     oneInstance: false
  1945.                 }, XenForo._overlayConfig, { top: '20%' }));
  1946.                 $html.overlay().load();
  1947.  
  1948.                 XenForo._manualDeferOverlay = $html;
  1949.  
  1950.                 $(document).trigger('PseudoAjaxStart');
  1951.  
  1952.                 var closeOverlay = function () {
  1953.                     XenForo._manualDeferOverlay.overlay().close();
  1954.                     $('#ManualDeferOverlay').remove();
  1955.                     XenForo._manualDeferOverlay = false;
  1956.                     XenForo._manualDeferXhr = false;
  1957.  
  1958.                     $(document).trigger('PseudoAjaxStop');
  1959.                     $(document).trigger('ManualDeferComplete');
  1960.                 };
  1961.  
  1962.                 var fn = function () {
  1963.                     XenForo._manualDeferXhr = XenForo.ajax(XenForo._manualDeferUrl, { execute: 1 }, function (ajaxData) {
  1964.                         if (ajaxData && ajaxData.continueProcessing) {
  1965.                             setTimeout(fn, 0);
  1966.                             XenForo._manualDeferOverlay.find('span').text(ajaxData.status);
  1967.  
  1968.                             var $cancel = XenForo._manualDeferOverlay.find('.CancelDeferred');
  1969.                             if (ajaxData.canCancel) {
  1970.                                 $cancel.show();
  1971.                             } else {
  1972.                                 $cancel.hide();
  1973.                             }
  1974.                         } else {
  1975.                             closeOverlay();
  1976.                         }
  1977.                     }).fail(closeOverlay);
  1978.                 };
  1979.                 fn();
  1980.             },
  1981.  
  1982.             /**
  1983.              * Generic handler for server-level errors received from XenForo.ajax
  1984.              * Attempts to provide a useful error message.
  1985.              *
  1986.              * @param object XMLHttpRequest
  1987.              * @param string Response text
  1988.              * @param string Error thrown
  1989.              * @param object XenForo.ajax params
  1990.              *
  1991.              * @return boolean False
  1992.              */
  1993.             handleServerError: function (xhr, responseText, errorThrown, params) {
  1994.                 var invalidDfIdRegexs = [
  1995.                     /^<html>\n<body>\n<script type="text\/javascript" src="\/aes\.js" ><\/script>/,
  1996.                     /^<html>\n<body>\n\x3Cscript type="text\/javascript" src="\/aes.js" >\x3C\/script>\n/,
  1997.                     /^<!doctype html><html><head><script src="\/process-[^"]+.js"><\/script><\/head><body><script>window\.onload=function\(\)\{process\(\);\}<\/script><noscript><p>Please enable JavaScript and Cookies in your browser\.<\/p><\/noscript><\/body><\/html>$/
  1998.                 ]
  1999.                 var errorPageRegexs = [
  2000.                     /^<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset="utf-8" \/>\r\n<title>Error 50[0-9]<\/title>\r\n<style type="text\/css">/,
  2001.                     /^<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" \/>\n<title>Error 50[0-9]<\/title>\n<style type="text\/css">/,
  2002.                     /^<!DOCTYPE html>\r\n<html>\r\n<head>\r\n<meta charset="utf-8" \/>\r\n<title>Site Maintenance<\/title>\r\n<style type="text\/css">/,
  2003.                     /^<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" \/>\n<title>Site Maintenance<\/title>\n<style type="text\/css">/
  2004.                 ]
  2005.  
  2006.                 // handle timeout and parse error before attempting to decode an error
  2007.                 console.log(responseText)
  2008.                 switch (responseText) {
  2009.                     case 'abort': {
  2010.                         return false;
  2011.                     }
  2012.                     case 'timeout': {
  2013.                         XenForo.alert(
  2014.                             XenForo.phrases.server_did_not_respond_in_time_try_again,
  2015.                             XenForo.phrases.following_error_occurred + ':'
  2016.                         );
  2017.                         return false;
  2018.                     }
  2019.                     case 'parsererror': {
  2020.                         var needToUpdateDfId = false
  2021.                         for (let i = 0; i < invalidDfIdRegexs.length; i++)
  2022.                             if (invalidDfIdRegexs[i].test(xhr.responseText)) {
  2023.                                 needToUpdateDfId = true
  2024.                                 break
  2025.                             }
  2026.  
  2027.                         if (params && !params.options.disableDfIdRefresh && needToUpdateDfId) {
  2028.                             console.log('df id refresh...')
  2029.                             $('body').append(
  2030.                                 $('<iframe src="' + xhr.responseText.split('window.location.href="')[1].split('"')[0] + '" style="display: none;"></iframe>')
  2031.                                     .on('load', function () {
  2032.                                         $(this).remove()
  2033.                                         params.options.disableDfIdRefresh = true
  2034.                                         XenForo.ajax(params.url, params.data, params.success, params.options)
  2035.                                     })
  2036.                             )
  2037.                         } else {
  2038.                             console.error('PHP ' + xhr.responseText);
  2039.                             XenForo.alert('The server responded with an error. The error message is in the JavaScript console.');
  2040.                         }
  2041.                         return false;
  2042.                     }
  2043.                     case 'notmodified':
  2044.                     case 'error': {
  2045.                         if (!xhr || !xhr.responseText) {
  2046.                             // this is likely a user cancellation, so just return
  2047.                             return false;
  2048.                         }
  2049.  
  2050.                         var needToParse = false
  2051.                         for (let i = 0; i < errorPageRegexs.length; i++)
  2052.                             if (errorPageRegexs[i].test(xhr.responseText)) {
  2053.                                 needToParse = true
  2054.                                 break
  2055.                             }
  2056.  
  2057.                         if (needToParse) {
  2058.                             XenForo.alert($(xhr.responseText).closest('article').get(0).innerText.trim().replace('\n\t', '<br>'))
  2059.                             return false;
  2060.                         }
  2061.  
  2062.                         break;
  2063.                     }
  2064.                 }
  2065.  
  2066.                 var contentTypeHeader = xhr.getResponseHeader('Content-Type'),
  2067.                     contentType = false,
  2068.                     data;
  2069.  
  2070.                 if (contentTypeHeader) {
  2071.                     switch (contentTypeHeader.split(';')[0]) {
  2072.                         case 'application/json': {
  2073.                             contentType = 'json';
  2074.                             break;
  2075.                         }
  2076.                         case 'text/html': {
  2077.                             contentType = 'html';
  2078.                             break;
  2079.                         }
  2080.                         default: {
  2081.                             if (xhr.responseText.substr(0, 1) == '{') {
  2082.                                 contentType = 'json';
  2083.                             } else if (xhr.responseText.substr(0, 9) == '<!DOCTYPE') {
  2084.                                 contentType = 'html';
  2085.                             }
  2086.                         }
  2087.                     }
  2088.                 }
  2089.  
  2090.                 if (contentType == 'json' && xhr.responseText.substr(0, 1) == '{') {
  2091.                     // XMLHttpRequest response is probably JSON
  2092.                     try {
  2093.                         data = $.parseJSON(xhr.responseText);
  2094.                     } catch (e) {
  2095.                     }
  2096.  
  2097.                     if (data) {
  2098.                         XenForo.hasResponseError(data, xhr.status);
  2099.                     } else {
  2100.                         XenForo.alert(xhr.responseText, XenForo.phrases.following_error_occurred + ':');
  2101.                     }
  2102.                 } else {
  2103.                     // XMLHttpRequest is some other type...
  2104.                     XenForo.alert(xhr.responseText, XenForo.phrases.following_error_occurred + ':');
  2105.                 }
  2106.  
  2107.                 return false;
  2108.             },
  2109.  
  2110.             /**
  2111.              * Checks for the presence of an 'error' key in the provided data
  2112.              * and displays its contents if found, using an alert.
  2113.              *
  2114.              * @param object ajaxData
  2115.              * @param integer HTTP error code (optional)
  2116.              *
  2117.              * @return boolean|string Returns the error string if found, or false if not found.
  2118.              */
  2119.             hasResponseError: function (ajaxData, httpErrorCode) {
  2120.                 if (typeof ajaxData != 'object') {
  2121.                     XenForo.alert('Response not JSON!'); // debug info, no phrasing
  2122.                     return true;
  2123.                 }
  2124.  
  2125.                 if (ajaxData.errorTemplateHtml) {
  2126.                     new XenForo.ExtLoader(ajaxData, function (data) {
  2127.                         var $overlayHtml = XenForo.alert(
  2128.                             ajaxData.errorTemplateHtml,
  2129.                             XenForo.phrases.following_error_occurred + ':'
  2130.                         );
  2131.                         if ($overlayHtml) {
  2132.                             $overlayHtml.find('div.errorDetails').removeClass('baseHtml');
  2133.                             if (ajaxData.errorOverlayType) {
  2134.                                 $overlayHtml.closest('.errorOverlay').removeClass('errorOverlay').addClass(ajaxData.errorOverlayType);
  2135.                             }
  2136.                         }
  2137.                     });
  2138.  
  2139.                     return ajaxData.error || true;
  2140.                 } else if (ajaxData.error !== undefined) {
  2141.                     // TODO: ideally, handle an array of errors
  2142.                     if (typeof ajaxData.error === 'object') {
  2143.                         var key;
  2144.                         for (key in ajaxData.error) {
  2145.                             break;
  2146.                         }
  2147.                         ajaxData.error = ajaxData.error[key];
  2148.                     }
  2149.  
  2150.                     XenForo.alert(
  2151.                         ajaxData.error + '\n'
  2152.                         + (ajaxData.traceHtml !== undefined ? '<ol class="traceHtml">\n' + ajaxData.traceHtml + '</ol>' : ''),
  2153.                         XenForo.phrases.following_error_occurred + ':'
  2154.                     );
  2155.  
  2156.                     return ajaxData.error;
  2157.                 } else if (ajaxData.status == 'ok' && ajaxData.message) {
  2158.                     XenForo.alert(ajaxData.message, '', 4000);
  2159.                     return true;
  2160.                 } else {
  2161.                     return false;
  2162.                 }
  2163.             },
  2164.  
  2165.             /**
  2166.              * Checks that the supplied ajaxData has a key that can be used to create a jQuery object
  2167.              *
  2168.              *  @param object ajaxData
  2169.              *  @param string key to look for (defaults to 'templateHtml')
  2170.              *
  2171.              *  @return boolean
  2172.              */
  2173.             hasTemplateHtml: function (ajaxData, templateKey) {
  2174.                 templateKey = templateKey || 'templateHtml';
  2175.  
  2176.                 if (!ajaxData[templateKey]) {
  2177.                     return false;
  2178.                 }
  2179.                 if (typeof (ajaxData[templateKey].search) == 'function') {
  2180.                     return (ajaxData[templateKey].search(/\S+/) !== -1);
  2181.                 } else {
  2182.                     return true;
  2183.                 }
  2184.             },
  2185.  
  2186.             /**
  2187.              * Creates an overlay using the given HTML
  2188.              *
  2189.              * @param jQuery Trigger element
  2190.              * @param string|jQuery HTML
  2191.              * @param object Extra options for overlay, will override defaults if specified
  2192.              *
  2193.              * @return jQuery Overlay API
  2194.              */
  2195.             createOverlay: function ($trigger, templateHtml, extraOptions) {
  2196.                 var $overlay = null,
  2197.                     $templateHtml = null,
  2198.                     api = null,
  2199.                     scripts = [],
  2200.                     i;
  2201.                 if (templateHtml instanceof jQuery && templateHtml.is('.xenOverlay')) {
  2202.                     // this is an object that has already been initialised
  2203.                     $overlay = templateHtml.appendTo('body');
  2204.                     $templateHtml = templateHtml;
  2205.                 } else {
  2206.                     const parsed = new DOMParser().parseFromString(templateHtml, 'text/html')
  2207.                     const scriptEls = Array.from($(parsed).find('script').filter(function () {
  2208.                         if (!$(this).text().trim().length) return false
  2209.  
  2210.                         const type = $(this).attr('type')
  2211.                         if (type && type !== 'text/javascript') return false
  2212.  
  2213.                         return true
  2214.                     }))
  2215.                     scripts = scriptEls.map(el => el.innerHTML)
  2216.  
  2217.                     for (const el of scriptEls)
  2218.                         templateHtml = templateHtml.replace(el.outerHTML, '')
  2219.  
  2220.                     $templateHtml = $(templateHtml);
  2221.  
  2222.                     // add a header to the overlay, unless instructed otherwise
  2223.                     if (!$templateHtml.is('.NoAutoHeader')) {
  2224.                         if (extraOptions && extraOptions.title) {
  2225.                             $('<h2 class="heading h1" />')
  2226.                                 .html(extraOptions.title)
  2227.                                 .prependTo($templateHtml);
  2228.                         }
  2229.                     }
  2230.  
  2231.                     // add a cancel button to the overlay, if the overlay is a .formOverlay, has a .submitUnit but has no :reset button
  2232.                     if ($templateHtml.is('.formOverlay')) {
  2233.                         if ($templateHtml.find('.submitUnit').length) {
  2234.                             if (!$templateHtml.find('.submitUnit :reset').length) {
  2235.                                 $templateHtml.find('.submitUnit .button:last')
  2236.                                     .after($('<input type="reset" class="button OverlayCloser" />').val(XenForo.phrases.cancel))
  2237.                                     .after(' ');
  2238.                             }
  2239.                         }
  2240.                     }
  2241.  
  2242.                     // create an overlay container, add the activated template to it and append it to the body.
  2243.                     $overlay = $('<div class="xenOverlay __XenForoActivator" />')
  2244.                         .addClass($(templateHtml).data('overlayclass')) // if content defines data-overlayClass, apply the value to the overlay as a class.
  2245.                         .append($templateHtml);
  2246.  
  2247.  
  2248.                     ($trigger || $overlay).one('onBeforeLoad', function () {
  2249.                         for (i = 0; i < scripts.length; i++) {
  2250.                             $.globalEval(scripts[i]);
  2251.                         }
  2252.                         $overlay.xfActivate()
  2253.                     })
  2254.                 }
  2255.  
  2256.                 var linkRegEx = /([^a-z0-9@-]|^)((?:https?:\/\/|www\.)[^\s"<>\{\}\[\]`_,\(\)]+)/ig
  2257.                 $overlay.find(':not(a):not(textarea):not(script)').each(function () {
  2258.                     var $el = $(this)
  2259.                     if ($el.children().length == 0)
  2260.                         $el.html($el.html().replace(linkRegEx, function (matched, c, link) {
  2261.                             return `${c}<a href="${link}">${link}</a>`
  2262.                         }))
  2263.                 })
  2264.  
  2265.                 if (extraOptions) {
  2266.                     // add {effect}Effect class to overlay container if necessary
  2267.                     if (extraOptions.effect) {
  2268.                         $overlay.addClass(extraOptions.effect + 'Effect');
  2269.                     }
  2270.  
  2271.                     // add any extra class name defined in extraOptions
  2272.                     if (extraOptions.className) {
  2273.                         $overlay.addClass(extraOptions.className);
  2274.                         delete (extraOptions.className);
  2275.                     }
  2276.  
  2277.                     if (extraOptions.noCache) {
  2278.                         extraOptions.onClose = function () {
  2279.                             $overlay.empty().remove();
  2280.                         };
  2281.                     }
  2282.                 }
  2283.  
  2284.                 // add an overlay closer if one does not already exist
  2285.                 let exists = !$overlay.find('.OverlayCloser').length;
  2286.  
  2287.                 if (exists) {
  2288.                     $overlay.prepend('<a class="close OverlayCloser"></a>');
  2289.                 }
  2290.                 $overlay.find('.OverlayCloser').toArray().forEach($element => {
  2291.                     if (!$element.href) {
  2292.                         exists = true;
  2293.                     }
  2294.                 })
  2295.  
  2296.                 if (!exists) {
  2297.                     $overlay.prepend('<a class="close OverlayCloser"></a>');
  2298.                 }
  2299.  
  2300.                 $overlay.find('.OverlayCloser').click(function (e) {
  2301.                     e.stopPropagation();
  2302.                 });
  2303.  
  2304.                 // if no trigger was specified (automatic popup), then activate the overlay instead of the trigger
  2305.                 $trigger = $trigger || $overlay;
  2306.  
  2307.                 var windowHeight = $(window).height();
  2308.  
  2309.                 var fixed = !(
  2310.                     ($.browser.msie && $.browser.version <= 6) // IE6 doesn't support position: fixed;
  2311.                     || XenForo.isTouchBrowser()
  2312.                     || $(window).width() <= 600 // overlay might end up especially tall
  2313.                     || windowHeight <= 550
  2314.                     || $overlay.outerHeight() >= .9 * windowHeight
  2315.                 );
  2316.                 if ($templateHtml.is('.NoFixedOverlay')) {
  2317.                     fixed = false;
  2318.                 }
  2319.  
  2320.                 // activate the overlay
  2321.                 var $modal = new XenForo.Modal($overlay, $.extend(true, {
  2322.                     close: '.OverlayCloser',
  2323.                     closeSpeed: XenForo.speed.slow,
  2324.                     fixed: fixed,
  2325.                     trigger: $trigger
  2326.                 }, XenForo._overlayConfig, extraOptions))
  2327.  
  2328.                 $trigger.bind(
  2329.                     {
  2330.                         onBeforeLoad: function (e) {
  2331.                             $(document).triggerHandler('OverlayOpening');
  2332.                             $('.MenuOpened').removeClass('MenuOpened')
  2333.  
  2334.                             $overlay.find('.Popup').each(function ($popup) {
  2335.                                 var $data = $(this).data('XenForo.PopupMenu')
  2336.                                 if ($data) $data.$menu.css('z-index', 11114)
  2337.                             })
  2338.  
  2339.                             if (XenForo.isTouchBrowser()) {
  2340.                                 const height = $overlay.outerHeight();
  2341.                                 if (height < $(window).height() * 0.9) {
  2342.                                     $overlay.addClass('slim');
  2343.                                 }
  2344.                             }
  2345.                         },
  2346.                         onLoad: function (e) {
  2347.                             var api = $(this).data('overlay'),
  2348.                                 $overlay = api.getOverlay(),
  2349.                                 scroller = $overlay.find('.OverlayScroller').get(0),
  2350.                                 resizeClose = null;
  2351.  
  2352.                             if ($overlay.css('position') == 'absolute') {
  2353.                                 $overlay.find('.overlayScroll').removeClass('overlayScroll');
  2354.                             }
  2355.  
  2356.                             // timeout prevents flicker in FF
  2357.                             if (scroller) {
  2358.                                 setTimeout(function () {
  2359.                                     scroller.scrollIntoView(true);
  2360.                                 }, 0);
  2361.                             }
  2362.  
  2363.                             // autofocus the first form element in a .formOverlay
  2364.                             var $focus = $overlay.find('form').find('input[autofocus], textarea[autofocus], select[autofocus], .AutoFocus').first();
  2365.                             if ($focus.length) {
  2366.                                 $focus.focus();
  2367.                             } else {
  2368.                                 $overlay.find('form').find('input:not([type=hidden], [type=file]), textarea, select, button, .submitUnit a.button').first().focus();
  2369.                             }
  2370.  
  2371.                             // hide on window resize
  2372.                             if (api.getConf().closeOnResize) {
  2373.                                 resizeClose = function () {
  2374.                                     console.info('Window resize, close overlay!');
  2375.                                     api.close();
  2376.                                 };
  2377.  
  2378.                                 $(window).one('resize', resizeClose);
  2379.  
  2380.                                 // remove event when closing the overlay
  2381.                                 $trigger.one('onClose', function () {
  2382.                                     $(window).unbind('resize', resizeClose);
  2383.                                 });
  2384.                             }
  2385.  
  2386.                             $(document).triggerHandler('OverlayOpened');
  2387.                         },
  2388.                         onBeforeClose: function (e) {
  2389.                             $overlay.find('.Popup').each(function () {
  2390.                                 var PopupMenu = $(this).data('XenForo.PopupMenu');
  2391.                                 if (PopupMenu.hideMenu) {
  2392.                                     PopupMenu.hideMenu(e, true);
  2393.                                 }
  2394.                             });
  2395.  
  2396.                             $('.autoCompleteList').each(function () {
  2397.                                 let autoCompleteList = $(this).data('XenForo.AutoCompleteResults')
  2398.                                 if (autoCompleteList && autoCompleteList.hideResults) {
  2399.                                     autoCompleteList.hideResults()
  2400.                                 }
  2401.                             })
  2402.  
  2403.                             $('.autoCompleteListSmilies').each(function () {
  2404.                                 let autoCompleteList = $(this).data('XenForo.AutoSmiliesCompleteResults')
  2405.                                 if (autoCompleteList && autoCompleteList.hideResults) {
  2406.                                     autoCompleteList.hideResults()
  2407.                                 }
  2408.                             })
  2409.                         }
  2410.                     });
  2411.  
  2412.                 api = $trigger.data('overlay');
  2413.                 $overlay.data('overlay', api);
  2414.  
  2415.                 return api;
  2416.             },
  2417.  
  2418.             /**
  2419.              * Present the user with a pop-up, modal message that they must confirm
  2420.              *
  2421.              * @param string Message
  2422.              * @param string Message type (error, info, redirect)
  2423.              * @param integer Timeout (auto-close after this period)
  2424.              * @param function Callback onClose
  2425.              */
  2426.             alert: function (message, messageType, timeOut, onClose) {
  2427.                 message = String(message || 'Unspecified error');
  2428.  
  2429.                 var key = message.replace(/[^a-z0-9_]/gi, '_') + parseInt(timeOut),
  2430.                     $overlayHtml;
  2431.  
  2432.                 if (XenForo._OverlayCache[key] === undefined) {
  2433.                     if (timeOut) {
  2434.                         $overlayHtml = ''
  2435.                             + '<div class="xenOverlay animated slideInLeft faster timedMessage">'
  2436.                             + '<div class="content baseHtml">'
  2437.                             + message
  2438.                             + '<span class="close"></span>'
  2439.                             + '</div>'
  2440.                             + '</div>';
  2441.  
  2442.                         new XenForo.TimedMessage($overlayHtml, timeOut, onClose)
  2443.                     } else {
  2444.                         $overlayHtml = $(''
  2445.                             + '<div class="errorOverlay">'
  2446.                             + '<a class="close OverlayCloser"></a>'
  2447.                             + '<h2 class="heading">' + (messageType || XenForo.phrases.following_error_occurred) + '</h2>'
  2448.                             + '<div class="baseHtml errorDetails"></div>'
  2449.                             + '</div>'
  2450.                         );
  2451.                         $overlayHtml.find('div.errorDetails').html(message);
  2452.                         XenForo._OverlayCache[key] = XenForo.createOverlay(null, $overlayHtml, {
  2453.                             onLoad: function () {
  2454.                                 var el = $('input:button.close, button.close', document.getElementById(key)).get(0);
  2455.                                 if (el) {
  2456.                                     el.focus();
  2457.                                 }
  2458.                             },
  2459.                             onClose: function () {
  2460.                                 delete XenForo._OverlayCache[key]
  2461.                                 onClose && onClose()
  2462.                             }
  2463.                         });
  2464.                         XenForo._OverlayCache[key].load();
  2465.  
  2466.                         const $timedMessage = $('body > .timedMessage')
  2467.                         if ($timedMessage.length) {
  2468.                             $timedMessage.fadeOut(250, function () {
  2469.                                 $(this).remove()
  2470.                             })
  2471.                         }
  2472.                     }
  2473.                 }
  2474.  
  2475.                 return $overlayHtml;
  2476.             },
  2477.  
  2478.             /**
  2479.              * Shows a mini timed alert message, much like the OS X notifier 'Growl'
  2480.              *
  2481.              * @param string message
  2482.              * @param integer timeOut Leave empty for a sticky message
  2483.              * @param jQuery Counter balloon
  2484.              */
  2485.             stackAlert: function (message, timeOut, $balloon) {
  2486.                 var $message = $('<li class="stackAlert DismissParent"><div class="stackAlertContent">'
  2487.                     + '<span class="helper"></span>'
  2488.                     + '<a class="DismissCtrl"></a>'
  2489.                     + '</div></li>'),
  2490.  
  2491.                     $container = $('#StackAlerts');
  2492.  
  2493.                 if (!$container.length) {
  2494.                     $container = $('<ul id="StackAlerts"></ul>').appendTo('body');
  2495.                 }
  2496.  
  2497.                 if ((message instanceof jQuery) == false) {
  2498.                     message = $('<span>' + message + '</span>');
  2499.                 }
  2500.  
  2501.                 message.appendTo($message.find('div.stackAlertContent'));
  2502.  
  2503.                 function removeMessage() {
  2504.                     $message.xfFadeUp(XenForo.speed.slow, function () {
  2505.                         $(this).empty().remove();
  2506.  
  2507.                         if (!$container.children().length) {
  2508.                             $container.hide();
  2509.                         }
  2510.                     });
  2511.                 }
  2512.  
  2513.                 function removeMessageAndScroll(e) {
  2514.                     if ($balloon && $balloon.length) {
  2515.                         $balloon.get(0).scrollIntoView(true);
  2516.                     }
  2517.  
  2518.                     removeMessage();
  2519.                 }
  2520.  
  2521.                 $message
  2522.                     .hide()
  2523.                     .prependTo($container.show())
  2524.                     .fadeIn(XenForo.speed.normal, function () {
  2525.                         if (timeOut > 0) {
  2526.                             setTimeout(removeMessage, timeOut);
  2527.                         }
  2528.                     });
  2529.  
  2530.                 $message.find('a').click(removeMessageAndScroll);
  2531.  
  2532.                 return $message;
  2533.             },
  2534.  
  2535.             /**
  2536.              * Adjusts all animation speeds used by XenForo
  2537.              *
  2538.              * @param integer multiplier - set to 0 to disable all animation
  2539.              */
  2540.             setAnimationSpeed: function (multiplier) {
  2541.                 var ieSpeedAdjust, s, index;
  2542.  
  2543.                 for (index in XenForo.speed) {
  2544.                     s = XenForo.speed[index];
  2545.  
  2546.                     if ($.browser.msie) {
  2547.                         // if we are using IE, change the animation lengths for a smoother appearance
  2548.                         if (s <= 100) {
  2549.                             ieSpeedAdjust = 2;
  2550.                         } else if (s > 800) {
  2551.                             ieSpeedAdjust = 1;
  2552.                         } else {
  2553.                             ieSpeedAdjust = 1 + 100 / s;
  2554.                         }
  2555.                         XenForo.speed[index] = s * multiplier * ieSpeedAdjust;
  2556.                     } else {
  2557.                         XenForo.speed[index] = s * multiplier;
  2558.                     }
  2559.                 }
  2560.             },
  2561.  
  2562.             /**
  2563.              * Generates a unique ID for an element, if required
  2564.              *
  2565.              * @param object HTML element (optional)
  2566.              *
  2567.              * @return string Unique ID
  2568.              */
  2569.             uniqueId: function (element) {
  2570.                 if (!element) {
  2571.                     return 'XenForoUniq' + XenForo._uniqueIdCounter++;
  2572.                 } else {
  2573.                     return $(element).uniqueId().attr('id');
  2574.                 }
  2575.             },
  2576.  
  2577.             redirect: function (url) {
  2578.                 url = XenForo.canonicalizeUrl(url);
  2579.  
  2580.                 if (url == window.location.href) {
  2581.                     window.location.reload();
  2582.                 } else {
  2583.                     window.location = url;
  2584.  
  2585.                     var destParts = url.split('#'),
  2586.                         srcParts = window.location.href.split('#');
  2587.  
  2588.                     if (destParts[1]) // has a hash
  2589.                     {
  2590.                         if (destParts[0] == srcParts[0]) {
  2591.                             // destination has a hash, but going to the same page
  2592.                             window.location.reload();
  2593.                         }
  2594.                     }
  2595.                 }
  2596.             },
  2597.  
  2598.             canonicalizeUrl: function (url, baseHref) {
  2599.                 if (url.indexOf('/') == 0) {
  2600.                     return url;
  2601.                 } else if (url.match(/^(https?:|ftp:|mailto:)/i)) {
  2602.                     return url;
  2603.                 } else {
  2604.                     if (!baseHref) {
  2605.                         baseHref = XenForo.baseUrl();
  2606.                     }
  2607.                     if (typeof baseHref != 'string') {
  2608.                         baseHref = '';
  2609.                     }
  2610.                     return baseHref + url;
  2611.                 }
  2612.             },
  2613.  
  2614.             _baseUrl: false,
  2615.  
  2616.             baseUrl: function () {
  2617.                 if (XenForo._baseUrl === false) {
  2618.                     var b = document.createElement('a'), $base = $('base');
  2619.                     b.href = '';
  2620.  
  2621.                     XenForo._baseUrl = (b.href.match(/[^\x20-\x7f]/) && $base.length) ? $base.attr('href') : b.href;
  2622.  
  2623.                     if (!$base.length) {
  2624.                         XenForo._baseUrl = XenForo._baseUrl.replace(/\?.*$/, '').replace(/\/[^\/]*$/, '/');
  2625.                     }
  2626.                 }
  2627.  
  2628.                 return XenForo._baseUrl;
  2629.             },
  2630.  
  2631.             /**
  2632.              * Adds a trailing slash to a string if one is not already present
  2633.              *
  2634.              * @param string
  2635.              */
  2636.             trailingSlash: function (string) {
  2637.                 if (string.substr(-1) != '/') {
  2638.                     string += '/';
  2639.                 }
  2640.  
  2641.                 return string;
  2642.             },
  2643.  
  2644.             /**
  2645.              * Escapes a string so it can be inserted into a RegExp without altering special characters
  2646.              *
  2647.              * @param string
  2648.              *
  2649.              * @return string
  2650.              */
  2651.             regexQuote: function (string) {
  2652.                 return (string + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!<>\|\:])/g, "\\$1");
  2653.             },
  2654.  
  2655.             /**
  2656.              * Escapes HTML into plain text
  2657.              *
  2658.              * @param string
  2659.              *
  2660.              * @return string
  2661.              */
  2662.             htmlspecialchars: function (string) {
  2663.                 return (String(string) || '')
  2664.                     .replace(/&/g, '&amp;')
  2665.                     .replace(/"/g, '&quot;')
  2666.                     .replace(/</g, '&lt;')
  2667.                     .replace(/>/g, '&gt;');
  2668.             },
  2669.  
  2670.             htmlEntityDecode: function (string) {
  2671.                 return (String(string) || '')
  2672.                     .replace(/&quot;/g, '"')
  2673.                     .replace(/&lt;/g, '<')
  2674.                     .replace(/&gt;/g, '>')
  2675.                     .replace(/&amp;/g, '&');
  2676.             },
  2677.  
  2678.             /**
  2679.              * Determines whether the current page is being viewed in RTL mode
  2680.              *
  2681.              * @return boolean
  2682.              */
  2683.             isRTL: function () {
  2684.                 if (XenForo.RTL === undefined) {
  2685.                     var dir = $('html').attr('dir');
  2686.                     XenForo.RTL = (dir && dir.toUpperCase() == 'RTL') ? true : false;
  2687.                 }
  2688.  
  2689.                 return XenForo.RTL;
  2690.             },
  2691.  
  2692.             /**
  2693.              * Switches instances of 'left' with 'right' and vice-versa in the input string.
  2694.              *
  2695.              * @param string directionString
  2696.              *
  2697.              * @return string
  2698.              */
  2699.             switchStringRTL: function (directionString) {
  2700.                 if (XenForo.isRTL()) {
  2701.                     directionString = directionString.replace(/left/i, 'l_e_f_t');
  2702.                     directionString = directionString.replace(/right/i, 'left');
  2703.                     directionString = directionString.replace('l_e_f_t', 'right');
  2704.                 }
  2705.                 return directionString;
  2706.             },
  2707.  
  2708.             /**
  2709.              * Switches the x-coordinate of the input offset array
  2710.              * @param offsetArray
  2711.              * @return string
  2712.              */
  2713.             switchOffsetRTL: function (offsetArray) {
  2714.                 if (XenForo.isRTL() && !isNaN(offsetArray[1])) {
  2715.                     offsetArray[1] = offsetArray[1] * -1;
  2716.                 }
  2717.  
  2718.                 return offsetArray;
  2719.             },
  2720.  
  2721.             /**
  2722.              * Checks whether or not a tag is a list container
  2723.              *
  2724.              * @param jQuery Tag
  2725.              *
  2726.              * @return boolean
  2727.              */
  2728.             isListTag: function ($tag) {
  2729.                 return ($tag.tagName == 'ul' || $tag.tagName == 'ol');
  2730.             },
  2731.  
  2732.             /**
  2733.              * Checks that the value passed is a numeric value, even if its actual type is a string
  2734.              *
  2735.              * @param mixed Value to be checked
  2736.              *
  2737.              * @return boolean
  2738.              */
  2739.             isNumeric: function (value) {
  2740.                 return (!isNaN(value) && (value - 0) == value && value.length > 0);
  2741.             },
  2742.  
  2743.             /**
  2744.              * Helper to check that an attribute value is 'positive'
  2745.              *
  2746.              * @param scalar Value to check
  2747.              *
  2748.              * @return boolean
  2749.              */
  2750.             isPositive: function (value) {
  2751.                 switch (String(value).toLowerCase()) {
  2752.                     case 'on':
  2753.                     case 'yes':
  2754.                     case 'true':
  2755.                     case '1':
  2756.                         return true;
  2757.  
  2758.                     default:
  2759.                         return false;
  2760.                 }
  2761.             },
  2762.  
  2763.             /**
  2764.              * Converts the first character of a string to uppercase.
  2765.              *
  2766.              * @param string
  2767.              *
  2768.              * @return string
  2769.              */
  2770.             ucfirst: function (string) {
  2771.                 return string.charAt(0).toUpperCase() + string.substr(1);
  2772.             },
  2773.  
  2774.             /**
  2775.              * Replaces any existing avatars for the given user on the page
  2776.              *
  2777.              * @param integer user ID
  2778.              * @param array List of avatar urls for the user, keyed with size code
  2779.              * @param boolean Include crop editor image
  2780.              */
  2781.             updateUserAvatars: function (userId, avatarUrls, andEditor) {
  2782.                 console.log('Replacing visitor avatars on page: %o', avatarUrls);
  2783.  
  2784.                 $.each(avatarUrls, function (sizeCode, avatarUrl) {
  2785.                     var sizeClass = '.Av' + userId + sizeCode + (andEditor ? '' : ':not(.AvatarCropControl)');
  2786.  
  2787.                     // .avatar > img
  2788.                     $(sizeClass).find('img').attr('src', avatarUrl);
  2789.  
  2790.                     // .avatar > span.img
  2791.                     $(sizeClass).find('span.img').css('background-image', 'url(' + avatarUrl + ')');
  2792.  
  2793.                     // visitor
  2794.                     $('.navTab--visitorAvatar').attr('src', avatarUrl);
  2795.                 });
  2796.             },
  2797.  
  2798.             getEditorInForm: function (form, extraConstraints) {
  2799.                 var $form = $(form),
  2800.                     $textarea = $form.find('textarea.MessageEditor' + (extraConstraints || '')).first();
  2801.  
  2802.                 if ($textarea.length) {
  2803.                     if ($textarea.prop('disabled')) {
  2804.                         return $form.find('.bbCodeEditorContainer textarea' + (extraConstraints || ''));
  2805.                     } else if ($textarea.data('redactor')) {
  2806.                         return $textarea.data('redactor');
  2807.                     } else {
  2808.                         return $textarea;
  2809.                     }
  2810.                 }
  2811.  
  2812.                 return false;
  2813.             },
  2814.  
  2815.             /**
  2816.              * Returns the name of the tag that should be animated for page scrolling
  2817.              *
  2818.              * @return string
  2819.              */
  2820.             getPageScrollTagName: function () {
  2821.                 // if we're currently scrolled, we should be able to determine where this is from
  2822.                 if ($('html').scrollTop() > 0) {
  2823.                     return 'html';
  2824.                 } else if ($('body').scrollTop() > 0) {
  2825.                     return 'body';
  2826.                 }
  2827.  
  2828.                 // otherwise, need to determine based on browser - Webkit uses body, but Chrome 61 has flipped to HTML
  2829.                 var match = navigator.userAgent.match(/Chrome\/([0-9]+)/i);
  2830.                 if (match && parseInt(match[1], 10) >= 61) {
  2831.                     return 'html';
  2832.                 } else if ($.browser.webkit) {
  2833.                     return 'body';
  2834.                 } else {
  2835.                     return 'html';
  2836.                 }
  2837.             },
  2838.  
  2839.             /**
  2840.              * Determines whether or not we are working with a touch-based browser
  2841.              *
  2842.              * @return boolean
  2843.              */
  2844.             isTouchBrowser: isTouchBrowser,
  2845.  
  2846.             scriptLoader: class {
  2847.                 /**
  2848.                  * Lazy-loads Javascript files
  2849.                  * @param {string} url
  2850.                  * @return {Promise<void>}
  2851.                  */
  2852.                 static async loadScriptAsync(url) {
  2853.                     if (url in XenForo._loadedScripts) return;
  2854.  
  2855.                     const script = await fetch(url, { mode: 'cors' }).then(r => r.text())
  2856.                     $('<script/>').text(script).appendTo('head').remove()
  2857.  
  2858.                     XenForo._loadedScripts[url] = true
  2859.                 }
  2860.  
  2861.                 static async loadScriptsAsync(urls) {
  2862.                     const promises = []
  2863.  
  2864.                     for (const url of urls) {
  2865.                         if (url in XenForo._loadedScripts) continue;
  2866.                         promises.push(fetch(url, { mode: 'cors' }).then(r => r.text()).then(script => ([url, script])))
  2867.                     }
  2868.  
  2869.                     const scripts = await Promise.all(promises)
  2870.  
  2871.                     for (const [url, script] of scripts) {
  2872.                         $('<script/>').text(script).appendTo('head').remove()
  2873.                         XenForo._loadedScripts[url] = true
  2874.                     }
  2875.                 }
  2876.  
  2877.                 /**
  2878.                  * Lazy-loads Javascript files
  2879.                  * @deprecated use loadScriptAsync
  2880.                  * @param {string} url
  2881.                  * @param {() => void} success
  2882.                  * @param {(err: any) => void} failure
  2883.                  * @return {void}
  2884.                  */
  2885.                 static loadScript(url, success, failure) {
  2886.                     this.loadScriptAsync(url).then(success, failure)
  2887.                 }
  2888.  
  2889.                 /**
  2890.                  * Lazy-loads CSS templates
  2891.                  * @param {string[]} css
  2892.                  * @param {string} urlTemplate
  2893.                  * @return {Promise<void>}
  2894.                  */
  2895.                 static async loadCssAsync(css, urlTemplate) {
  2896.                     const requiredCss = css.filter(t => !(t in XenForo._loadedScripts))
  2897.                     if (!requiredCss.length) return;
  2898.  
  2899.                     const url = urlTemplate.replace('__sentinel__', requiredCss.join(','))
  2900.  
  2901.                     const style = await fetch(url, { mode: 'cors' }).then(r => r.text())
  2902.  
  2903.                     for (const template of requiredCss) {
  2904.                         XenForo._loadedScripts[template] = true
  2905.                         console.log('ScriptLoader: Loaded css, %s', template)
  2906.                     }
  2907.  
  2908.                     const baseHref = XenForo.baseUrl()
  2909.                     const patchedStyle = style.replace(/(url\((?:"|')?)([^"')]+)((?:"|')?\))/gi, (all, front, url, back) => {
  2910.                         // if URL already absolute OR looks like a data URI do not prefix with baseHref
  2911.                         if (!url.match(/^https?:|\//i) && !url.match(/^data:/i))
  2912.                             url = baseHref + url
  2913.                         return front + url + back
  2914.                     })
  2915.  
  2916.                     $('<style/>').text(patchedStyle).appendTo('head')
  2917.                 }
  2918.  
  2919.                 /**
  2920.                  * Lazy-loads CSS templates
  2921.                  * @deprecated use loadCss
  2922.                  * @param {string[]} css
  2923.                  * @param {string} urlTemplate
  2924.                  * @param {() => void} success
  2925.                  * @param {(err: any) => void} failure
  2926.                  * @return {void}
  2927.                  */
  2928.                 static loadCss(css, urlTemplate, success, failure) {
  2929.                     this.loadCssAsync(css, urlTemplate).then(success, failure)
  2930.                 }
  2931.  
  2932.                 /**
  2933.                  * Lazy-loads JavaScript and CSS
  2934.                  * @param {({ type: 'css', list: string[]} | { type: 'js', url: string })[]} data
  2935.                  * @param {string} urlTemplate
  2936.                  * @return {Promise<void>}
  2937.                  */
  2938.                 static loadMultipleAsync(data, urlTemplate = `css.php?css=__sentinel__&style=9&_v=${XenForo._jsVersion}`) {
  2939.                     /** @type {Promise<void>[]} */
  2940.                     const promises = []
  2941.  
  2942.                     for (const item of data) {
  2943.                         switch (item.type) {
  2944.                             case 'js': promises.push(this.loadScriptAsync(item.url)); break
  2945.                             case 'css': promises.push(this.loadCssAsync(item.list, urlTemplate))
  2946.                         }
  2947.                     }
  2948.  
  2949.                     return Promise.all(promises)
  2950.                 }
  2951.  
  2952.                 /**
  2953.                  * Lazy-loads JavaScript and CSS
  2954.                  * @deprecated use loadMultipleAsync
  2955.                  * @param {({ type: 'css', list: string[]} | { type: 'js', url: string })[]} data
  2956.                  * @param {() => void} callback
  2957.                  * @param {string | undefined} urlTemplate
  2958.                  * @return {void}
  2959.                  */
  2960.                 static loadMultiple(data, callback, urlTemplate) {
  2961.                     this.loadMultipleAsync(data, urlTemplate).then(callback, err => {
  2962.                         console.warn('ScriptLoader.loadMultiple error:', err)
  2963.                     })
  2964.                 }
  2965.             }
  2966.         });
  2967.  
  2968.     // *********************************************************************
  2969.  
  2970.     /**
  2971.      * Loads the requested list of javascript and css files
  2972.      * Before firing the specified callback.
  2973.      */
  2974.     XenForo.ExtLoader = class {
  2975.         /**
  2976.          * @template T
  2977.          * @param {T} data Ajax data
  2978.          * @param {(T) => void} success Success callback
  2979.          * @param {(T) => void} failure Error callback
  2980.          */
  2981.         constructor(data = {}, success = () => { }, failure = () => { }) {
  2982.             /** @type {Promise<void>[]} */
  2983.             const promises = []
  2984.  
  2985.             if (typeof data?.css?.urlTemplate === 'string' && Array.isArray(data?.css?.stylesheets) && data.css.stylesheets.length) {
  2986.                 promises.push(XenForo.scriptLoader.loadCssAsync(data.css.stylesheets, data.css.urlTemplate))
  2987.             }
  2988.  
  2989.             if (Array.isArray(data?.js)) {
  2990.                 promises.push(XenForo.scriptLoader.loadScriptsAsync(data.js.reverse()))
  2991.             }
  2992.  
  2993.             Promise.all(promises).then(() => { success(data) }, err => {
  2994.                 console.warn('ExtLoader error:', err)
  2995.                 failure(data)
  2996.             })
  2997.         }
  2998.     }
  2999.  
  3000.     // *********************************************************************
  3001.  
  3002.     /**
  3003.      * Instance of XenForo.TimestampRefresh
  3004.      *
  3005.      * @var XenForo.TimestampRefresh
  3006.      */
  3007.     XenForo._TimestampRefresh = null;
  3008.  
  3009.     /**
  3010.      * Allows date/time stamps on the page to be displayed as relative to now, and auto-refreshes periodically
  3011.      */
  3012.     XenForo.TimestampRefresh = function () {
  3013.         this.__construct();
  3014.     };
  3015.     XenForo.TimestampRefresh.prototype =
  3016.     {
  3017.         __construct: function () {
  3018.             this.active = this.activate();
  3019.  
  3020.             $(document).bind('XenForoWindowFocus', $.context(this, 'focus'));
  3021.         },
  3022.  
  3023.         /**
  3024.          * Runs on window.focus, activates the system if deactivated
  3025.          *
  3026.          * @param event e
  3027.          */
  3028.         focus: function (e) {
  3029.             if (!this.active) {
  3030.                 this.activate(true);
  3031.             }
  3032.         },
  3033.  
  3034.         /**
  3035.          * Runs a refresh, then refreshes again every 60 seconds
  3036.          *
  3037.          * @param boolean Refresh instantly
  3038.          *
  3039.          * @return integer Refresh interval or something...
  3040.          */
  3041.         activate: function (instant) {
  3042.             if (instant) {
  3043.                 this.refresh();
  3044.             }
  3045.  
  3046.             return this.active = window.setInterval($.context(this, 'refresh'), 60 * 1000); // one minute
  3047.         },
  3048.  
  3049.         /**
  3050.          * Halts timestamp refreshes
  3051.          *
  3052.          * @return boolean false
  3053.          */
  3054.         deactivate: function () {
  3055.             window.clearInterval(this.active);
  3056.             return this.active = false;
  3057.         },
  3058.  
  3059.         /**
  3060.          * Date/Time output updates
  3061.          */
  3062.         refresh: function (element, force) {
  3063.             if (!XenForo._hasFocus && !force) {
  3064.                 return this.deactivate();
  3065.             }
  3066.  
  3067.             if ($.browser.msie && $.browser.version <= 6) {
  3068.                 return;
  3069.             }
  3070.  
  3071.             var $elements = $('abbr.DateTime[data-time]', element),
  3072.                 pageOpenTime = (new Date().getTime() / 1000),
  3073.                 pageOpenLength = pageOpenTime - XenForo._pageLoadTime,
  3074.                 serverTime = XenForo.serverTimeInfo.now,
  3075.                 today = XenForo.serverTimeInfo.today,
  3076.                 todayDow = XenForo.serverTimeInfo.todayDow,
  3077.                 yesterday, week, dayOffset,
  3078.                 i, $element, thisTime, thisDiff, thisServerTime, interval, calcDow;
  3079.  
  3080.             if (serverTime + pageOpenLength > today + 86400) {
  3081.                 // day has changed, need to adjust
  3082.                 dayOffset = Math.floor((serverTime + pageOpenLength - today) / 86400);
  3083.  
  3084.                 today += dayOffset * 86400;
  3085.                 todayDow = (todayDow + dayOffset) % 7;
  3086.             }
  3087.  
  3088.             yesterday = today - 86400;
  3089.             week = today - 6 * 86400;
  3090.  
  3091.             var rtlMarker = XenForo.isRTL() ? '\u200F' : '';
  3092.  
  3093.             for (i = 0; i < $elements.length; i++) {
  3094.                 $element = $($elements[i]);
  3095.  
  3096.                 // set the original value of the tag as its title
  3097.                 if (!$element.attr('title')) {
  3098.                     $element.attr('title', $element.text());
  3099.                 }
  3100.  
  3101.                 thisDiff = parseInt($element.data('diff'), 10);
  3102.                 thisTime = parseInt($element.data('time'), 10);
  3103.  
  3104.                 thisServerTime = thisTime + thisDiff;
  3105.                 if (thisServerTime > serverTime + pageOpenLength) {
  3106.                     thisServerTime = Math.floor(serverTime + pageOpenLength);
  3107.                 }
  3108.                 interval = serverTime - thisServerTime + thisDiff + pageOpenLength;
  3109.  
  3110.                 if (interval < 0) {
  3111.                     // date in the future
  3112.                 } else if (interval <= 60) {
  3113.                     $element.text(XenForo.phrases.a_moment_ago);
  3114.                 } else if (interval <= 120) {
  3115.                     $element.text(XenForo.phrases.one_minute_ago);
  3116.                 } else if (interval < 3600) {
  3117.                     $element.text(XenForo.phrases.x_minutes_ago
  3118.                         .replace(/%minutes%/, Math.floor(interval / 60)));
  3119.                 } else if (thisTime >= today) {
  3120.                     $element.text(XenForo.phrases.today_at_x
  3121.                         .replace(/%time%/, $element.attr('data-timestring'))); // must use attr for string value
  3122.                 } else if (thisTime >= yesterday) {
  3123.                     $element.text(XenForo.phrases.yesterday_at_x
  3124.                         .replace(/%time%/, $element.attr('data-timestring'))); // must use attr for string value
  3125.                 } else if (thisTime >= week) {
  3126.                     calcDow = todayDow - Math.ceil((today - thisTime) / 86400);
  3127.                     if (calcDow < 0) {
  3128.                         calcDow += 7;
  3129.                     }
  3130.  
  3131.                     $element.text(rtlMarker + XenForo.phrases.day_x_at_time_y
  3132.                         .replace('%day%', XenForo.phrases['day' + calcDow])
  3133.                         .replace(/%time%/, $element.attr('data-timestring')) // must use attr for string value
  3134.                     );
  3135.                 } else {
  3136.                     $element.text(rtlMarker + $element.attr('data-datestring')); // must use attr for string value
  3137.                 }
  3138.             }
  3139.         }
  3140.     };
  3141.  
  3142.     // *********************************************************************
  3143.  
  3144.     /**
  3145.      * Periodically refreshes all CSRF tokens on the page
  3146.      */
  3147.     XenForo.CsrfRefresh = function () {
  3148.         this.__construct();
  3149.     };
  3150.     XenForo.CsrfRefresh.prototype =
  3151.     {
  3152.         __construct: function () {
  3153.             this.activate();
  3154.  
  3155.             $(document).bind('XenForoWindowFocus', $.context(this, 'focus'));
  3156.         },
  3157.  
  3158.         /**
  3159.          * Runs on window focus, activates the system if deactivated
  3160.          *
  3161.          * @param event e
  3162.          */
  3163.         focus: function (e) {
  3164.             if (!this.active) {
  3165.                 this.activate(true);
  3166.             }
  3167.         },
  3168.  
  3169.         /**
  3170.          * Runs a refresh, then refreshes again every hour
  3171.          *
  3172.          * @param boolean Refresh instantly
  3173.          *
  3174.          * @return integer Refresh interval or something...
  3175.          */
  3176.         activate: function (instant) {
  3177.             if (instant) {
  3178.                 this.refresh();
  3179.             }
  3180.  
  3181.             this.active = window.setInterval($.context(this, 'refresh'), 50 * 60 * 1000); // 50 minutes
  3182.             return this.active;
  3183.         },
  3184.  
  3185.         /**
  3186.          * Halts csrf refreshes
  3187.          */
  3188.         deactivate: function () {
  3189.             window.clearInterval(this.active);
  3190.             this.active = false;
  3191.         },
  3192.  
  3193.         /**
  3194.          * Updates all CSRF tokens
  3195.          */
  3196.         refresh: function () {
  3197.             if (!XenForo._csrfRefreshUrl) {
  3198.                 return;
  3199.             }
  3200.  
  3201.             if (!XenForo._hasFocus) {
  3202.                 this.deactivate();
  3203.                 return;
  3204.             }
  3205.  
  3206.             XenForo.ajax(
  3207.                 XenForo._csrfRefreshUrl,
  3208.                 '',
  3209.                 function (ajaxData, textStatus) {
  3210.                     if (!ajaxData || ajaxData.csrfToken === undefined) {
  3211.                         return false;
  3212.                     }
  3213.  
  3214.                     var tokenInputs = $('input[name=_xfToken]').val(ajaxData.csrfToken);
  3215.  
  3216.                     XenForo._csrfToken = ajaxData.csrfToken;
  3217.  
  3218.                     if (tokenInputs.length) {
  3219.                         console.log('XenForo CSRF token updated in %d places (%s)', tokenInputs.length, ajaxData.csrfToken);
  3220.                     }
  3221.  
  3222.                     $(document).trigger(
  3223.                         {
  3224.                             type: 'CSRFRefresh',
  3225.                             ajaxData: ajaxData
  3226.                         });
  3227.                 },
  3228.                 { error: false, global: false }
  3229.             );
  3230.         }
  3231.     };
  3232.  
  3233.     // *********************************************************************
  3234.  
  3235.     /**
  3236.      * Stores the id of the currently active popup menu group
  3237.      *
  3238.      * @var string
  3239.      */
  3240.     XenForo._PopupMenuActiveGroup = null;
  3241.  
  3242.     /**
  3243.      * Popup menu system.
  3244.      *
  3245.      * Requires:
  3246.      * <el class="Popup">
  3247.      *        <a rel="Menu">control</a>
  3248.      *        <el class="Menu {Left} {Hider}">menu content</el>
  3249.      * </el>
  3250.      *
  3251.      * * .Menu.Left causes orientation of menu to reverse, away from scrollbar
  3252.      * * .Menu.Hider causes menu to appear over control instead of below
  3253.      *
  3254.      * @param jQuery *.Popup container element
  3255.      */
  3256.     XenForo.PopupMenu = function ($container) {
  3257.         this.__construct($container);
  3258.     };
  3259.     XenForo.PopupMenu.prototype =
  3260.     {
  3261.         __construct: function ($container) {
  3262.             // the container holds the control and the menu
  3263.             this.$container = $container;
  3264.             if (!$(".MenuContainer").length) {
  3265.                 $("<div class='MenuContainer' />").appendTo('body')
  3266.             }
  3267.  
  3268.             // take the menu, which will be a sibling of the control, and append/move it to the end of the body
  3269.             this.$menu = this.$container.find('.Menu').first().appendTo('.MenuContainer');
  3270.             this.$menu.data('XenForo.PopupMenu', this);
  3271.             this.menuVisible = false;
  3272.  
  3273.             // check that we have the necessary elements
  3274.             if (!this.$menu.length) {
  3275.                 console.warn('Unable to find menu for Popup %o', this.$container);
  3276.  
  3277.                 return false;
  3278.             }
  3279.  
  3280.             // add a unique id to the menu
  3281.             this.$menu.id = XenForo.uniqueId(this.$menu);
  3282.  
  3283.             // variables related to dynamic content loading
  3284.             this.contentSrc = this.$menu.data('contentsrc');
  3285.             this.contentDest = this.$menu.data('contentdest');
  3286.             this.loading = null;
  3287.             this.unreadDisplayTimeout = null;
  3288.             this.newlyOpened = false;
  3289.  
  3290.             // bind events to the menu control
  3291.             this.$clicker = $container.find('[rel="Menu"]').first().click($.context(this, 'controlClick'));
  3292.             this.$clicker.data('XenForo.PopupMenu', this);
  3293.  
  3294.             if (!XenForo.isTouchBrowser() && !$container.hasClass('DisableHover')) {
  3295.                 this.$clicker.mouseover($.context(this, 'controlHover')).hoverIntent(
  3296.                     {
  3297.                         sensitivity: 1,
  3298.                         interval: 50,
  3299.                         timeout: 0,
  3300.                         over: $.context(this, 'controlHoverIntent'),
  3301.                         out: function () {
  3302.                         }
  3303.                     });
  3304.             }
  3305.  
  3306.             this.$control = this.addPopupGadget(this.$clicker);
  3307.  
  3308.             // the popup group for this menu, if specified
  3309.             this.popupGroup = this.$control.closest('[data-popupgroup]').data('popupgroup');
  3310.  
  3311.             //console.log('Finished popup menu for %o', this.$control);
  3312.  
  3313.             this.useInfiniteScroll = $container.is('.alerts')
  3314.         },
  3315.  
  3316.         addPopupGadget: function ($control) {
  3317.             if (!$control.hasClass('NoPopupGadget') && !$control.hasClass('SplitCtrl')) {
  3318.                 $control.append('<span class="arrowWidget" />');
  3319.             }
  3320.  
  3321.             var $popupControl = $control.closest('.PopupControl');
  3322.             if ($popupControl.length) {
  3323.                 $control = $popupControl.addClass('PopupContainerControl');
  3324.             }
  3325.  
  3326.             $control.addClass('PopupControl');
  3327.  
  3328.             return $control;
  3329.         },
  3330.  
  3331.         /**
  3332.          * Opens or closes a menu, or navigates to another page, depending on menu status and control attributes.
  3333.          *
  3334.          * Clicking a control while the menu is hidden will open and show the menu.
  3335.          * If the control has an href attribute, clicking on it when the menu is open will navigate to the specified URL.
  3336.          * If the control does not have an href, a click will close the menu.
  3337.          *
  3338.          * @param event
  3339.          *
  3340.          * @return mixed
  3341.          */
  3342.         controlClick: function (e) {
  3343.             console.debug('%o control clicked. NewlyOpened: %s, Animated: %s', this.$control, this.newlyOpened, this.$menu.is(':animated'));
  3344.  
  3345.             if (!this.newlyOpened && !this.$menu.is(':animated')) {
  3346.                 console.info('control: %o', this.$control);
  3347.  
  3348.                 //if (this.$menu.is(':hidden')) {
  3349.                 if (!this.$menu.hasClass('MenuOpened')) {
  3350.                     this.showMenu(e, false);
  3351.                 } else if (this.$clicker.attr('href') && !XenForo.isPositive(this.$clicker.data('closemenu'))) {
  3352.                     console.warn('Following hyperlink from %o', this.$clicker);
  3353.                     return true;
  3354.                 } else {
  3355.                     this.hideMenu(e, false);
  3356.                 }
  3357.             } else {
  3358.                 console.debug('Click on control of newly-opened or animating menu, ignored');
  3359.             }
  3360.  
  3361.             e.preventDefault();
  3362.             e.target.blur();
  3363.             return false;
  3364.         },
  3365.  
  3366.         /**
  3367.          * Handles hover events on menu controls. Will normally do nothing,
  3368.          * unless there is a menu open and the control being hovered belongs
  3369.          * to the same popupGroup, in which case this menu will open instantly.
  3370.          *
  3371.          * @param event
  3372.          *
  3373.          * @return mixed
  3374.          */
  3375.         controlHover: function (e) {
  3376.             if (this.popupGroup != null && this.popupGroup == this.getActiveGroup()) {
  3377.                 this.showMenu(e, true);
  3378.  
  3379.                 return false;
  3380.             }
  3381.         },
  3382.  
  3383.         /**
  3384.          * Handles hover-intent events on menu controls. Menu will show
  3385.          * if the cursor is hovered over a control at low speed and for a duration
  3386.          *
  3387.          * @param event
  3388.          */
  3389.         controlHoverIntent: function (e) {
  3390.             var instant = false;//(this.popupGroup != null && this.popupGroup == this.getActiveGroup());
  3391.  
  3392.             if (this.$clicker.hasClass('SplitCtrl')) {
  3393.                 instant = true;
  3394.             }
  3395.  
  3396.             this.showMenu(e, instant);
  3397.         },
  3398.  
  3399.         /**
  3400.          * Opens and shows a popup menu.
  3401.          *
  3402.          * If the menu requires dynamic content to be loaded, this will load the content.
  3403.          * To define dynamic content, the .Menu element should have:
  3404.          * * data-contentSrc = URL to JSON that contains templateHtml to be inserted
  3405.          * * data-contentDest = jQuery selector specifying the element to which the templateHtml will be appended. Defaults to this.$menu.
  3406.          *
  3407.          * @param event
  3408.          * @param boolean Show instantly (true) or fade in (false)
  3409.          */
  3410.         showMenu: function (e, instant) {
  3411.             /*if (this.$menu.is(':visible')) {
  3412.                                      return false;
  3413.                              }*/
  3414.             if (this.$menu.hasClass('MenuOpened') || this.$menu.closest(".xenOverlay").hasClass('faster')) {
  3415.                 return false;
  3416.             }
  3417.  
  3418.             //console.log('Show menu event type = %s', e.type);
  3419.  
  3420.             var $eShow = new $.Event('PopupMenuShow');
  3421.             $eShow.$menu = this.$menu;
  3422.             $eShow.instant = instant;
  3423.             $(document).trigger($eShow);
  3424.  
  3425.             if ($eShow.isDefaultPrevented()) {
  3426.                 return false;
  3427.             }
  3428.  
  3429.             this.menuVisible = true;
  3430.  
  3431.             this.setMenuPosition('showMenu');
  3432.  
  3433.             if (this.$menu.hasClass('BottomControl')) {
  3434.                 instant = true;
  3435.             }
  3436.  
  3437.             if (this.contentSrc && !this.loading) {
  3438.                 this.loading = XenForo.ajax(
  3439.                     this.contentSrc, '',
  3440.                     $.context(this, 'loadSuccess'),
  3441.                     { type: 'GET' }
  3442.                 );
  3443.  
  3444.                 this.timeout = setTimeout(function () {
  3445.                     this.$menu.find('.Progress').addClass('InProgress');
  3446.                 }.bind(this), 500);
  3447.  
  3448.  
  3449.                 instant = true;
  3450.             }
  3451.  
  3452.             this.setActiveGroup();
  3453.  
  3454.             this.$control.addClass('PopupOpen').removeClass('PopupClosed');
  3455.  
  3456.             this.$menu.addClass('MenuOpened');
  3457.             this.menuShown();
  3458.  
  3459.             if (!this.menuEventsInitialized) {
  3460.                 var $html = $('html'),
  3461.                     t = this,
  3462.                     htmlSize = [$html.width(), $html.height()];
  3463.  
  3464.                 // TODO: make this global?
  3465.                 // TODO: touch interfaces don't like this
  3466.  
  3467.                 $(document).bind({
  3468.                     PopupMenuShow: $.context(this, 'hideIfOther'),
  3469.                     XFOverlay: $.context(this, 'menuHidden')
  3470.                 });
  3471.  
  3472.                 if (XenForo.isTouchBrowser()) {
  3473.                     for (const e of ['click', 'touchend'])
  3474.                         document.addEventListener(e, e => {
  3475.                             if (!this.menuVisible || !this.$menu.hasClass('MenuOpened')) return;
  3476.                             if (!e.screenX && !e.screenY && e.type === 'click') return; // fix for $().click()
  3477.                             if (!e.target.closest('nav') && !e.target.closest('.MenuOpened')) {
  3478.                                 e.preventDefault()
  3479.                                 e.stopImmediatePropagation()
  3480.                                 $(document).trigger('HideAllMenus')
  3481.                                 this.hideMenu($(e))
  3482.                             }
  3483.                         }, true)
  3484.                 }
  3485.  
  3486.                 // Webkit mobile kinda does not support document.click, bind to other elements
  3487.                 if (XenForo._isWebkitMobile) {
  3488.                     $(document.body.children).click($.context(this, 'hideMenu'));
  3489.                 } else {
  3490.                     $(document).click($.context(this, 'hideMenu'));
  3491.                 }
  3492.  
  3493.                 $(document).on('HideAllMenus', function (e) {
  3494.                     if (t.menuVisible) {
  3495.                         t._hideMenu(e, true);
  3496.                     }
  3497.                 });
  3498.  
  3499.                 let hovered = true;
  3500.                 t.$menu.hover(() => { hovered = true }, () => { hovered = false })
  3501.  
  3502.                 if (!XenForo.isTouchBrowser()) {
  3503.                     $(window).on('scroll', function (e) {
  3504.                         if (t.menuVisible && !t.$menu.hasClass('HeaderMenu') && !hovered) {
  3505.                             t._hideMenu(e, true);
  3506.                         }
  3507.                     });
  3508.                 }
  3509.  
  3510.  
  3511.                 $(window).bind(
  3512.                     {
  3513.                         resize: function (e) {
  3514.                             // only trigger close if the window size actually changed - some mobile browsers trigger without size change
  3515.                             var w = $html.width(), h = $html.height();
  3516.                             if (w != htmlSize[0] || h != htmlSize[1]) {
  3517.                                 htmlSize[0] = w;
  3518.                                 htmlSize[1] = h;
  3519.                                 t._hideMenu(e);
  3520.                             }
  3521.                         }
  3522.                     });
  3523.  
  3524.                 this.$menu.delegate('a', 'click', $.context(this, 'menuLinkClick'));
  3525.                 this.$menu.delegate('.MenuCloser', 'click', $.context(this, 'hideMenu'));
  3526.  
  3527.                 this.$control.parents('#AlertsDestinationWrapper').on('scroll touchmove', $.context(this, 'hideMenu'));
  3528.  
  3529.                 this.menuEventsInitialized = true;
  3530.             }
  3531.         },
  3532.  
  3533.         /**
  3534.          * Hides an open popup menu (conditionally)
  3535.          *
  3536.          * @param event
  3537.          * @param boolean Hide instantly (true) or fade out (false)
  3538.          */
  3539.         hideMenu: function (e, instant) {
  3540.             /*if (this.$menu.is(':visible') && this.triggersMenuHide(e)) {
  3541.                                      this._hideMenu(e, !instant);
  3542.                              }*/
  3543.  
  3544.             this.$control.parents('#AlertsDestinationWrapper').off('scroll touchmove hover', $.context(this, 'hideMenu'));
  3545.             if (this.$menu.hasClass('MenuOpened') && this.triggersMenuHide(e)) {
  3546.                 this._hideMenu(e, !instant);
  3547.             }
  3548.         },
  3549.  
  3550.         /**
  3551.          * Hides an open popup menu, without checking context or environment
  3552.          *
  3553.          * @param event
  3554.          * @param boolean Fade out the menu (true) or hide instantly out (false)
  3555.          */
  3556.         _hideMenu: function (e, fade) {
  3557.             //console.log('Hide menu \'%s\' %o TYPE = %s', this.$control.text(), this.$control, e.type);
  3558.             this.menuVisible = false;
  3559.  
  3560.             this.setActiveGroup(null);
  3561.  
  3562.             if (this.$menu.hasClass('BottomControl')) {
  3563.                 fade = false;
  3564.             }
  3565.  
  3566.             // stop any unread content fading into its read state
  3567.             clearTimeout(this.unreadDisplayTimeout);
  3568.             this.$menu.find('.Unread').stop();
  3569.  
  3570.             /*var top = this.$menu.css('top');
  3571.                              this.$menu.css('top', '+=10');
  3572.                              this.$menu.xfHide(0, $.context(this, 'menuHidden')).css('top');*/
  3573.  
  3574.             this.menuHidden();
  3575.         },
  3576.  
  3577.         /**
  3578.          * Fires when the menu showing animation is completed and the menu is displayed
  3579.          */
  3580.         menuShown: function () {
  3581.             // if the menu has a data-contentSrc attribute, we can assume that it requires dynamic content, which has not yet loaded
  3582.             var contentLoaded = (this.$menu.data('contentsrc') ? false : true),
  3583.                 $input = null;
  3584.  
  3585.             this.$control.addClass('PopupOpen').removeClass('PopupClosed');
  3586.             this.$menu.addClass('MenuOpened');
  3587.  
  3588.             this.newlyOpened = true;
  3589.             setTimeout($.context(function () {
  3590.                 this.newlyOpened = false;
  3591.             }, this), 50);
  3592.  
  3593.             this.$menu.trigger('ShowComplete', [contentLoaded]);
  3594.  
  3595.             this.setMenuPosition('menuShown');
  3596.  
  3597.             this.highlightUnreadContent();
  3598.  
  3599.             if (!XenForo.isTouchBrowser()) {
  3600.                 $input = this.$menu.find('input[type=text], input[type=search], textarea, select').first();
  3601.                 if ($input.length) {
  3602.                     if ($input.data('nofocus')) {
  3603.                         return;
  3604.                     }
  3605.  
  3606.                     $input.select();
  3607.                 }
  3608.             }
  3609.  
  3610.             const mm = $('#menu.mm--open')
  3611.             if (mm && mm.length) {
  3612.                 $('#navigation .mobileMenuButtonClose').click()
  3613.             }
  3614.         },
  3615.  
  3616.         /**
  3617.          * Fires when the menu hiding animations is completed and the menu is hidden
  3618.          */
  3619.         menuHidden: function () {
  3620.             this.$control.removeClass('PopupOpen').addClass('PopupClosed');
  3621.             this.$menu.removeClass('MenuOpened');
  3622.             this.hideTimeout = setTimeout(() => {
  3623.                 this.hideTimeout = null
  3624.                 // https://zelenka.guru/threads/4163365/
  3625.                 this.$menu.css('top', '0px').css('left', '0px')
  3626.             }, 150)
  3627.  
  3628.             this.$menu.trigger('MenuHidden');
  3629.         },
  3630.  
  3631.         /**
  3632.          * Fires in response to the document triggering 'PopupMenuShow' and hides the current menu
  3633.          * if the menu that fired the event is not itself.
  3634.          *
  3635.          * @param event
  3636.          */
  3637.         hideIfOther: function (e) {
  3638.             const eventMenuProp = e.$menu.prop($.expando);
  3639.             const contextMenuProp = this.$menu.prop($.expando);
  3640.  
  3641.             if (!eventMenuProp && !contextMenuProp || eventMenuProp != contextMenuProp) {
  3642.                 if (!e.$menu.data('XenForo.PopupMenu').$container.hasClass('PopupInPopup')) {
  3643.                     this.hideMenu(e, e.instant);
  3644.                 }
  3645.             }
  3646.         },
  3647.  
  3648.         /**
  3649.          * Checks to see if an event should hide the menu.
  3650.          *
  3651.          * Returns false if:
  3652.          * * Event target is a child of the menu, or is the menu itself
  3653.          *
  3654.          * @param event
  3655.          *
  3656.          * @return boolean
  3657.          */
  3658.         triggersMenuHide: function (e) {
  3659.             var $target = $(e.target);
  3660.  
  3661.             if (e.ctrlKey || e.shiftKey || e.altKey) {
  3662.                 return false;
  3663.             }
  3664.  
  3665.             if (e.which > 1) {
  3666.                 // right or middle click, don't close
  3667.                 return false;
  3668.             }
  3669.  
  3670.             if ($target.is('.MenuCloser')) {
  3671.                 return true;
  3672.             }
  3673.  
  3674.             // is the control a hyperlink that has not had its default action prevented?
  3675.             if ($target.is('a[href]') && !e.isDefaultPrevented()) {
  3676.                 return true;
  3677.             }
  3678.  
  3679.             if ($target.is('ul')) {
  3680.                 return false;
  3681.             }
  3682.  
  3683.             if (e.target === document || !$target.closest('#' + this.$menu.id).length) {
  3684.                 return true;
  3685.             }
  3686.  
  3687.             return false;
  3688.         },
  3689.  
  3690.         /**
  3691.          * Sets the position of the popup menu, based on the position of the control
  3692.          */
  3693.         setMenuPosition: function (caller) {
  3694.             if (this.hideTimeout) clearTimeout(this.hideTimeout)
  3695.             //console.info('setMenuPosition(%s)', caller);
  3696.  
  3697.             var $controlParent,
  3698.                 controlLayout, // control coordinates
  3699.                 menuLayout, // menu coordinates
  3700.                 contentLayout, // #content coordinates
  3701.                 $content,
  3702.                 $window,
  3703.                 proposedLeft,
  3704.                 proposedTop;
  3705.  
  3706.             controlLayout = this.$control.coords('outer');
  3707.  
  3708.             this.$menu.css('position', '').removeData('position');
  3709.  
  3710.             $controlParent = this.$control;
  3711.             while ($controlParent && $controlParent.length && $controlParent.get(0) != document) {
  3712.                 if ($controlParent.css('position') == 'fixed') {
  3713.                     controlLayout.top -= $(window).scrollTop();
  3714.                     controlLayout.left -= $(window).scrollLeft();
  3715.  
  3716.                     this.$menu.css('position', 'fixed').data('position', 'fixed');
  3717.                     break;
  3718.                 }
  3719.  
  3720.                 $controlParent = $controlParent.parent();
  3721.             }
  3722.  
  3723.             this.$control.removeClass('BottomControl');
  3724.  
  3725.             // set the menu to sit flush with the left of the control, immediately below it
  3726.             this.$menu.removeClass('BottomControl').css(
  3727.                 {
  3728.                     left: controlLayout.left,
  3729.                     top: controlLayout.top + controlLayout.height - 1 // fixes a weird thing where the menu doesn't join the control
  3730.                 });
  3731.  
  3732.             menuLayout = this.$menu.coords('outer');
  3733.  
  3734.             $content = $('#content .pageContent');
  3735.             if ($content.length) {
  3736.                 contentLayout = $content.coords('outer');
  3737.             } else {
  3738.                 contentLayout = $('body').coords('outer');
  3739.             }
  3740.  
  3741.             $window = $(window);
  3742.             var sT = $window.scrollTop(),
  3743.                 sL = $window.scrollLeft(),
  3744.                 windowWidth = $window.width();
  3745.  
  3746.             /*
  3747.                         * if the menu's right edge is off the screen, check to see if
  3748.                         * it would be better to position it flush with the right edge of the control.
  3749.                         * RTL displays will try to do this if possible.
  3750.                         */
  3751.             if (XenForo.isRTL() || menuLayout.left + menuLayout.width > contentLayout.left + contentLayout.width) {
  3752.                 proposedLeft = Math.max(0, controlLayout.left + controlLayout.width - menuLayout.width);
  3753.                 if (proposedLeft > sL) {
  3754.                     this.$menu.css('left', proposedLeft);
  3755.                 }
  3756.             }
  3757.  
  3758.             if (parseInt(this.$menu.css('left'), 10) + menuLayout.width > windowWidth + sL) {
  3759.                 this.$menu.css('left', Math.min(parseInt(this.$menu.css('left'), 10), windowWidth + sL - menuLayout.width));
  3760.             }
  3761.  
  3762.             /*
  3763.                         * if the menu's bottom edge is off the screen, check to see if
  3764.                         * it would be better to position it above the control
  3765.                         */
  3766.             if (menuLayout.top + menuLayout.height > $window.height() + sT) {
  3767.                 proposedTop = controlLayout.top - menuLayout.height;
  3768.                 if (proposedTop > sT) {
  3769.                     this.$control.addClass('BottomControl');
  3770.                     this.$menu.addClass('BottomControl');
  3771.                     this.$menu.css('top', controlLayout.top - this.$menu.outerHeight());
  3772.                 }
  3773.             }
  3774.         },
  3775.  
  3776.         /**
  3777.          * Fires when dynamic content for a popup menu has been loaded.
  3778.          *
  3779.          * Checks for errors and if there are none, appends the new HTML to the element selected by this.contentDest.
  3780.          *
  3781.          * @param object ajaxData
  3782.          * @param string textStatus
  3783.          */
  3784.         loadSuccess: function (ajaxData, textStatus) {
  3785.             if (XenForo.hasResponseError(ajaxData) || !XenForo.hasTemplateHtml(ajaxData)) {
  3786.                 this._hideMenu()
  3787.                 this.loading = null
  3788.                 return false;
  3789.             }
  3790.  
  3791.             // check for content destination
  3792.             if (!this.contentDest) {
  3793.                 console.warn('Menu content destination not specified, using this.$menu.');
  3794.  
  3795.                 this.contentDest = this.$menu;
  3796.             }
  3797.  
  3798.             console.info('Content destination: %o', this.contentDest);
  3799.  
  3800.             var self = this;
  3801.  
  3802.             new XenForo.ExtLoader(ajaxData, function (data) {
  3803.                 self.$menu.trigger('LoadComplete');
  3804.                 if (self.timeout) clearTimeout(self.timeout);
  3805.  
  3806.                 var $templateHtml = $(data.templateHtml);
  3807.  
  3808.                 // append the loaded content to the destination
  3809.                 $templateHtml.xfInsert(
  3810.                     self.$menu.data('insertfn') || 'appendTo',
  3811.                     self.contentDest,
  3812.                     'slideDown', 0,
  3813.                     function () {
  3814.                         self.$menu.css('min-width', '199px');
  3815.                         setTimeout(function () {
  3816.                             self.$menu.css('min-width', '');
  3817.                         }, 0);
  3818.                         if (self.$control.hasClass('PopupOpen')) {
  3819.                             self.menuShown();
  3820.                         }
  3821.                         self.$menu.addClass('Loaded');
  3822.                     }
  3823.                 );
  3824.  
  3825.                 if ($(self.contentDest).hasClass('Scrollbar')) {
  3826.                     $(self.contentDest).scrollbar();
  3827.                 }
  3828.  
  3829.                 self.$menu.find('.Progress').removeClass('InProgress');
  3830.                 if (self.useInfiniteScroll) {
  3831.                     var $scroll = self.$menu.find('#AlertPanels #AlertsDestinationWrapper')
  3832.                     var $status = $('\
  3833.                             <div class="infinite-scroll-status">\
  3834.                                 <div class="spinner infinite-scroll-request" style="display: none;">\
  3835.                                     <div class="bounce1"></div>\
  3836.                                     <div class="bounce2"></div>\
  3837.                                     <div class="bounce3"></div>\
  3838.                                 </div>\
  3839.                             </div>').appendTo($scroll)
  3840.                     $scroll.find('.alertsPopup > ol').infiniteScroll({
  3841.                         path: function () {
  3842.                             return XenForo.canonicalizeUrl(self.contentSrc + (/\?/.test(self.contentSrc) ? '&' : '?') + 'page=' + (this.pageIndex + 1))
  3843.                         },
  3844.                         status: $status[0],
  3845.                         append: '.alertsPopup > ol > li',
  3846.                         xf: true,
  3847.                         xfAjaxOptions: { global: false },
  3848.                         xfAjaxDataProcess: function (ajaxData) {
  3849.                             return ajaxData.templateHtml
  3850.                         },
  3851.                         history: false,
  3852.                         elementScroll: $scroll[0]
  3853.                     })
  3854.                 }
  3855.             });
  3856.         },
  3857.  
  3858.         resetLoader: function () {
  3859.             if (this.contentDest && this.loading) {
  3860.                 delete (this.loading);
  3861.                 $(this.contentDest).empty();
  3862.                 this.$menu.find('.Progress').addClass('InProgress');
  3863.             }
  3864.         },
  3865.  
  3866.         menuLinkClick: function (e) {
  3867.             this.hideMenu(e, true);
  3868.         },
  3869.  
  3870.         /**
  3871.          * Sets the name of the globally active popup group
  3872.          *
  3873.          * @param mixed If specified, active group will be set to this value.
  3874.          *
  3875.          * @return string Active group name
  3876.          */
  3877.         setActiveGroup: function (value) {
  3878.             var activeGroup = (value === undefined ? this.popupGroup : value);
  3879.  
  3880.             return XenForo._PopupMenuActiveGroup = activeGroup;
  3881.         },
  3882.  
  3883.         /**
  3884.          * Returns the name of the globally active popup group
  3885.          *
  3886.          * @return string Active group name
  3887.          */
  3888.         getActiveGroup: function () {
  3889.             return XenForo._PopupMenuActiveGroup;
  3890.         },
  3891.  
  3892.         /**
  3893.          * Fade return the background color of unread items to the normal background
  3894.          */
  3895.         highlightUnreadContent: function () {
  3896.             var $unreadContent = this.$menu.find('.Unread'),
  3897.                 defaultBackground = null,
  3898.                 counterSelector = null;
  3899.  
  3900.             if ($unreadContent.length) {
  3901.                 defaultBackground = $unreadContent.data('defaultbackground');
  3902.  
  3903.                 if (defaultBackground) {
  3904.                     $unreadContent.css('backgroundColor', null);
  3905.  
  3906.                     this.unreadDisplayTimeout = setTimeout($.context(function () {
  3907.                         // removes an item specified by data-removeCounter on the menu element
  3908.                         if (counterSelector = this.$menu.data('removecounter')) {
  3909.                             XenForo.balloonCounterUpdate($(counterSelector), 0);
  3910.                         }
  3911.  
  3912.                         $unreadContent.animate({ backgroundColor: defaultBackground }, 2000, $.context(function () {
  3913.                             $unreadContent.removeClass('Unread');
  3914.                             this.$menu.trigger('UnreadDisplayComplete');
  3915.                         }, this));
  3916.                     }, this), 1000);
  3917.                 }
  3918.             }
  3919.         },
  3920.  
  3921.         reload: function () {
  3922.             if (!this.contentSrc || (this.loading && this.loading.readyState !== 4)) return;
  3923.             this.resetLoader()
  3924.             this.loading = XenForo.ajax(
  3925.                 this.contentSrc, '',
  3926.                 $.context(this, 'loadSuccess'),
  3927.                 { type: 'GET' }
  3928.             );
  3929.         },
  3930.  
  3931.         addToMenu: function ($element) {
  3932.             this.$menu.find('.secondaryContent').append($element).xfActivate();
  3933.         },
  3934.  
  3935.         removeFromMenu: function ($selector) {
  3936.             this.$menu.find($selector).remove();
  3937.         }
  3938.     };
  3939.  
  3940.     // *********************************************************************
  3941.  
  3942.     /**
  3943.      * Shows and hides global request pending progress indicators for AJAX calls.
  3944.      *
  3945.      * Binds to the global ajaxStart and ajaxStop jQuery events.
  3946.      * Also binds to the PseudoAjaxStart and PseudoAjaxStop events,
  3947.      * see XenForo.AutoInlineUploader
  3948.      *
  3949.      * Initialized by XenForo.init()
  3950.      */
  3951.     XenForo.AjaxProgress = function () {
  3952.         var overlay = null,
  3953.  
  3954.             showOverlay = function () {
  3955.                 // mini indicators
  3956.                 //$('.Progress, .xenForm .ctrlUnit.submitUnit dt').addClass('InProgress');
  3957.  
  3958.                 // the overlay
  3959.                 if (!overlay) {
  3960.                     overlay = $('<div id="AjaxProgress" class="xenOverlay"><div class="content"><span class="close" /></div></div>')
  3961.                         .appendTo('body')
  3962.                         .css({
  3963.                             top: 0,
  3964.                             right: 0,
  3965.                             opacity: 0,
  3966.                             position: 'fixed',
  3967.                             display: 'block',
  3968.                             width: 'inherit'
  3969.                         }).animate({ opacity: 1 }, XenForo.speed.fast)
  3970.                 }
  3971.             },
  3972.  
  3973.             hideOverlay = function () {
  3974.                 // mini indicators
  3975.                 $('.Progress, .xenForm .ctrlUnit.submitUnit dt')
  3976.                     .removeClass('InProgress');
  3977.  
  3978.                 // the overlay
  3979.                 if (overlay) {
  3980.                     overlay.animate({ opacity: 0 }, XenForo.speed.fast, function () {
  3981.                         $(this).remove()
  3982.                     })
  3983.                     overlay = null
  3984.                 }
  3985.             };
  3986.  
  3987.         $(document).bind(
  3988.             {
  3989.                 ajaxStart: function (e) {
  3990.                     XenForo._AjaxProgress = true;
  3991.                     showOverlay();
  3992.                 },
  3993.  
  3994.                 ajaxStop: function (e) {
  3995.                     XenForo._AjaxProgress = false;
  3996.                     hideOverlay();
  3997.                 },
  3998.  
  3999.                 PseudoAjaxStart: function (e) {
  4000.                     showOverlay();
  4001.                 },
  4002.  
  4003.                 PseudoAjaxStop: function (e) {
  4004.                     hideOverlay();
  4005.                 }
  4006.             });
  4007.  
  4008.         if ($.browser.msie && $.browser.version < 7) {
  4009.             $(document).bind('scroll', function (e) {
  4010.                 if (overlay && overlay.isOpened() && !overlay.getConf().fixed) {
  4011.                     overlay.getOverlay().css('top', overlay.getConf().top + $(window).scrollTop());
  4012.                 }
  4013.             });
  4014.         }
  4015.     };
  4016.  
  4017.     // *********************************************************************
  4018.  
  4019.     /**
  4020.      * Handles the scrollable pagenav gadget, allowing selection of any page between 1 and (end)
  4021.      * while showing only {range*2+1} pages plus first and last at once.
  4022.      *
  4023.      * @param jQuery .pageNav
  4024.      */
  4025.     XenForo.PageNav = function ($pageNav) {
  4026.         this.__construct($pageNav);
  4027.     };
  4028.     XenForo.PageNav.prototype =
  4029.     {
  4030.         __construct: function ($pageNav) {
  4031.             if (XenForo.isRTL()) {
  4032.                 // scrollable doesn't support RTL yet
  4033.                 return false;
  4034.             }
  4035.  
  4036.             this.$pageNav = $pageNav;
  4037.             var $scroller = $pageNav.find('.scrollable');
  4038.             if (!$scroller.length) {
  4039.                 return false;
  4040.             }
  4041.  
  4042.             console.info('PageNav %o', $pageNav);
  4043.  
  4044.             this.start = parseInt($pageNav.data('start'));
  4045.             this.page = parseInt($pageNav.data('page'));
  4046.             this.end = parseInt($pageNav.data('end'));
  4047.             this.last = parseInt($pageNav.data('last'));
  4048.             this.range = parseInt($pageNav.data('range'));
  4049.             this.size = (this.range * 2 + 1);
  4050.  
  4051.             this.baseurl = $pageNav.data('baseurl');
  4052.             this.sentinel = $pageNav.data('sentinel');
  4053.  
  4054.             this.notShowPrevButton = false;
  4055.             this.notShowNextButton = false;
  4056.  
  4057.             $scroller.scrollable(
  4058.                 {
  4059.                     speed: XenForo.speed.slow,
  4060.                     easing: 'easeOutBounce',
  4061.                     keyboard: false,
  4062.                     prev: '#nullPrev',
  4063.                     next: '#nullNext',
  4064.                     touch: false
  4065.                 });
  4066.  
  4067.             this.api = $scroller.data('scrollable').onBeforeSeek($.context(this, 'beforeSeek'));
  4068.  
  4069.             this.$prevButton = $pageNav.find('.PageNavPrev').click($.context(this, 'prevPage'));
  4070.             this.$nextButton = $pageNav.find('.PageNavNext').click($.context(this, 'nextPage'));
  4071.  
  4072.             this.setControlVisibility(this.api.getIndex(), 0);
  4073.         },
  4074.  
  4075.         /**
  4076.          * Scrolls to the previous 'page' of page links, creating them if necessary
  4077.          *
  4078.          * @param Event e
  4079.          */
  4080.         prevPage: function (e) {
  4081.             if (this.api.getIndex() == 0 && this.start > 2) {
  4082.                 var i = 0,
  4083.                     minPage = Math.max(2, (this.start - this.size));
  4084.  
  4085.                 for (i = this.start - 1; i >= minPage; i--) {
  4086.                     this.prepend(i);
  4087.                 }
  4088.  
  4089.                 this.start = minPage;
  4090.             }
  4091.  
  4092.             this.api.seekTo(Math.max(this.api.getIndex() - this.size, 0));
  4093.         },
  4094.  
  4095.         /**
  4096.          * Scrolls to the next 'page' of page links, creating them if necessary
  4097.          *
  4098.          * @param Event e
  4099.          */
  4100.         nextPage: function (e) {
  4101.             if ((this.api.getIndex() + 1 + 2 * this.size) > this.api.getSize() && this.end < this.last - 1) {
  4102.                 var i = 0,
  4103.                     maxPage = Math.min(this.last - 1, this.end + this.size);
  4104.  
  4105.                 for (i = this.end + 1; i <= maxPage; i++) {
  4106.                     this.append(i);
  4107.                 }
  4108.  
  4109.                 this.end = maxPage;
  4110.             }
  4111.  
  4112.             this.api.seekTo(Math.min(this.api.getSize() - this.size, this.api.getIndex() + this.size));
  4113.         },
  4114.  
  4115.         /**
  4116.          * Adds an additional page link to the beginning of the scrollable section, out of sight
  4117.          *
  4118.          * @param integer page
  4119.          */
  4120.         prepend: function (page) {
  4121.             this.buildPageLink(page).prependTo(this.api.getItemWrap());
  4122.  
  4123.             this.api.next(0);
  4124.         },
  4125.  
  4126.         /**
  4127.          * Adds an additional page link to the end of the scrollable section, out of sight
  4128.          *
  4129.          * @param integer page
  4130.          */
  4131.         append: function (page) {
  4132.             this.buildPageLink(page).appendTo(this.api.getItemWrap());
  4133.         },
  4134.  
  4135.         /**
  4136.          * Buids a single page link
  4137.          *
  4138.          * @param integer page
  4139.          *
  4140.          * @return jQuery page link html
  4141.          */
  4142.         buildPageLink: function (page) {
  4143.             return $('<a />',
  4144.                 {
  4145.                     href: this.buildPageUrl(page),
  4146.                     text: page,
  4147.                     'class': (page > 999 ? 'gt999' : '')
  4148.                 });
  4149.         },
  4150.  
  4151.         /**
  4152.          * Converts the baseUrl into a page url by replacing the sentinel value
  4153.          *
  4154.          * @param integer page
  4155.          *
  4156.          * @return string page URL
  4157.          */
  4158.         buildPageUrl: function (page) {
  4159.             return this.baseurl
  4160.                 .replace(this.sentinel, page)
  4161.                 .replace(escape(this.sentinel), page);
  4162.         },
  4163.  
  4164.         /**
  4165.          * Runs immediately before the pagenav seeks to a new index,
  4166.          * Toggles visibility of the next/prev controls based on whether they are needed or not
  4167.          *
  4168.          * @param jQuery Event e
  4169.          * @param integer index
  4170.          */
  4171.         beforeSeek: function (e, index) {
  4172.             this.setControlVisibility(index, XenForo.speed.fast);
  4173.             this.$pageNav.trigger('seek');
  4174.         },
  4175.  
  4176.         /**
  4177.          * Sets the visibility of the scroll controls, based on whether using them would do anything
  4178.          * (hide the prev-page control if on the first page, etc.)
  4179.          *
  4180.          * @param integer Target index of the current scroll
  4181.          *
  4182.          * @param mixed Speed of animation
  4183.          */
  4184.         setControlVisibility: function (index, speed) {
  4185.             const notShowPrevButtonUpdated = index === 0 && this.start <= 2;
  4186.             const notShowNextButtonUpdated = this.api.getSize() - this.size <= index && this.end >= this.last - 1;
  4187.  
  4188.             if (notShowPrevButtonUpdated !== this.notShowPrevButton) {
  4189.                 this.notShowPrevButton = notShowPrevButtonUpdated;
  4190.                 if (this.notShowPrevButton) {
  4191.                     this.$prevButton.hide(speed);
  4192.                 } else {
  4193.                     this.$prevButton.show(speed);
  4194.                 }
  4195.             }
  4196.             if (notShowNextButtonUpdated !== this.notShowNextButton) {
  4197.                 this.notShowNextButton = notShowNextButtonUpdated;
  4198.                 if (this.notShowNextButton) {
  4199.                     this.$nextButton.hide(speed);
  4200.                 } else {
  4201.                     this.$nextButton.show(speed);
  4202.                 }
  4203.             }
  4204.         }
  4205.     };
  4206.  
  4207.     // *********************************************************************
  4208.  
  4209.     XenForo.ToggleTrigger = function ($trigger) {
  4210.         this.__construct($trigger);
  4211.     };
  4212.     XenForo.ToggleTrigger.prototype =
  4213.     {
  4214.         __construct: function ($trigger) {
  4215.             this.$trigger = $trigger;
  4216.             this.loaded = false;
  4217.             this.targetVisible = false;
  4218.             this.$target = null;
  4219.  
  4220.             if ($trigger.data('target')) {
  4221.                 var anchor = $trigger.closest('.ToggleTriggerAnchor');
  4222.                 if (!anchor.length) {
  4223.                     anchor = $('body');
  4224.                 }
  4225.                 var target = anchor.find($trigger.data('target'));
  4226.                 if (target.length) {
  4227.                     this.$target = target;
  4228.                     var toggleClass = target.data('toggle-class');
  4229.                     this.targetVisible = toggleClass ? target.hasClass(toggleClass) : target.is(':visible');
  4230.                 }
  4231.             }
  4232.  
  4233.             if ($trigger.data('only-if-hidden')
  4234.                 && XenForo.isPositive($trigger.data('only-if-hidden'))
  4235.                 && this.targetVisible
  4236.             ) {
  4237.                 return;
  4238.             }
  4239.  
  4240.             $trigger.click($.context(this, 'toggle'));
  4241.         },
  4242.  
  4243.         toggle: function (e) {
  4244.             e.preventDefault();
  4245.  
  4246.             var $trigger = this.$trigger,
  4247.                 $target = this.$target;
  4248.  
  4249.             if ($trigger.data('toggle-if-pointer') && XenForo.isPositive($trigger.data('toggle-if-pointer'))) {
  4250.                 if ($trigger.css('cursor') !== 'pointer') {
  4251.                     return;
  4252.                 }
  4253.             }
  4254.  
  4255.             $trigger.clearQueue().finish();
  4256.  
  4257.             if ($trigger.data('toggle-text')) {
  4258.                 var toggleText = $trigger.text();
  4259.                 $trigger.text($trigger.data('toggle-text'));
  4260.                 $trigger.data('toggle-text', toggleText);
  4261.             }
  4262.  
  4263.             if (e.pageX || e.pageY) {
  4264.                 $trigger.blur();
  4265.             }
  4266.  
  4267.             if ($target) {
  4268.                 $(document).trigger('ToggleTriggerEvent',
  4269.                     {
  4270.                         closing: this.targetVisible,
  4271.                         $target: $target
  4272.                     });
  4273.  
  4274.                 this.hideSelfIfNeeded();
  4275.  
  4276.                 var triggerTargetEvent = function () {
  4277.                     $target.trigger('elementResized');
  4278.                 };
  4279.  
  4280.                 var hideTrigger = function () {
  4281.                     $target.attr('style', 'display: block');
  4282.                     triggerTargetEvent();
  4283.                 }
  4284.  
  4285.                 var toggleClass = $target.data('toggle-class');
  4286.                 if (this.targetVisible) {
  4287.                     if (toggleClass) {
  4288.                         $target.removeClass(toggleClass);
  4289.                         triggerTargetEvent();
  4290.                     } else {
  4291.                         $target.stop(true, true).xfFadeUp(null, triggerTargetEvent);
  4292.                     }
  4293.                 } else {
  4294.                     if (toggleClass) {
  4295.                         $target.addClass(toggleClass);
  4296.                         hideTrigger();
  4297.                     } else {
  4298.                         $target.stop(true, true).xfFadeDown(null, hideTrigger);
  4299.                     }
  4300.                 }
  4301.                 this.targetVisible = !this.targetVisible;
  4302.             } else {
  4303.                 this.load();
  4304.             }
  4305.         },
  4306.  
  4307.         hideSelfIfNeeded: function () {
  4308.             var hideSel = this.$trigger.data('hide');
  4309.  
  4310.             if (!hideSel) {
  4311.                 return false;
  4312.             }
  4313.  
  4314.             var $el;
  4315.  
  4316.             if (hideSel == 'self') {
  4317.                 $el = this.$trigger;
  4318.             } else {
  4319.                 var anchor = this.$trigger.closest('.ToggleTriggerAnchor');
  4320.                 if (!anchor.length) {
  4321.                     anchor = $('body');
  4322.                 }
  4323.                 $el = anchor.find(hideSel);
  4324.             }
  4325.  
  4326.             $el.hide();
  4327.             return;
  4328.             //$el.xfFadeUp();
  4329.         },
  4330.  
  4331.         load: function () {
  4332.             if (this.loading || !this.$trigger.attr('href')) {
  4333.                 return;
  4334.             }
  4335.  
  4336.             var self = this;
  4337.  
  4338.             var $position = $(this.$trigger.data('position'));
  4339.             if (!$position.length) {
  4340.                 $position = this.$trigger.closest('.ToggleTriggerAnchor');
  4341.                 if (!$position.length) {
  4342.                     console.warn("Could not match toggle target position selector %s", this.$trigger.data('position'));
  4343.                     return false;
  4344.                 }
  4345.             }
  4346.  
  4347.             var method = this.$trigger.data('position-method') || 'insertAfter';
  4348.  
  4349.             this.loading = true;
  4350.  
  4351.             XenForo.ajax(this.$trigger.attr('href'), {}, function (ajaxData) {
  4352.                 self.loading = false;
  4353.  
  4354.                 if (XenForo.hasResponseError(ajaxData)) {
  4355.                     return false;
  4356.                 }
  4357.  
  4358.                 // received a redirect rather than a view - follow it.
  4359.                 if (ajaxData._redirectStatus && ajaxData._redirectTarget) {
  4360.                     var fn = function () {
  4361.                         XenForo.redirect(ajaxData._redirectTarget);
  4362.                     };
  4363.  
  4364.                     if (XenForo._manualDeferOverlay) {
  4365.                         $(document).one('ManualDeferComplete', fn);
  4366.                     } else {
  4367.                         fn();
  4368.                     }
  4369.                     return false;
  4370.                 }
  4371.  
  4372.                 if (!ajaxData.templateHtml) {
  4373.                     return false;
  4374.                 }
  4375.  
  4376.                 new XenForo.ExtLoader(ajaxData, function (data) {
  4377.                     self.$target = $(data.templateHtml);
  4378.  
  4379.                     self.$target.xfInsert(method, $position);
  4380.                     self.targetVisible = true;
  4381.                     self.hideSelfIfNeeded();
  4382.                 });
  4383.             });
  4384.         }
  4385.     };
  4386.  
  4387.     // *********************************************************************
  4388.  
  4389.     /**
  4390.      * Triggers an overlay from a regular link or button
  4391.      * Triggers can provide an optional data-cacheOverlay attribute
  4392.      * to allow multiple trigers to access the same overlay.
  4393.      *
  4394.      * @param jQuery .OverlayTrigger
  4395.      */
  4396.     XenForo.OverlayTrigger = function ($trigger, options) {
  4397.         this.__construct($trigger, options);
  4398.     };
  4399.     XenForo.OverlayTrigger.prototype =
  4400.     {
  4401.         __construct: function ($trigger, options) {
  4402.             this.$trigger = $trigger.click($.context(this, 'show'));
  4403.             this.options = options;
  4404.         },
  4405.  
  4406.         /**
  4407.          * Begins the process of loading and showing an overlay
  4408.          *
  4409.          * @param event e
  4410.          */
  4411.         show: function (e) {
  4412.             //xfActivate
  4413.             var parentOverlay = this.$trigger.closest('.xenOverlay').data('overlay'),
  4414.                 cache,
  4415.                 options,
  4416.                 isUserLink = (this.$trigger.is('.username, .avatar')),
  4417.                 cardHref;
  4418.  
  4419.             if (!parseInt(XenForo._enableOverlays)) {
  4420.                 // if no overlays, use <a href /> by preference
  4421.                 if (this.$trigger.attr('href')) {
  4422.                     return true;
  4423.                 } else if (this.$trigger.data('href')) {
  4424.                     if (this.$trigger.closest('.AttachmentUploader, #AttachmentUploader').length == 0) {
  4425.                         // open the overlay target as a regular link, unless it's the attachment uploader
  4426.                         XenForo.redirect(this.$trigger.data('href'));
  4427.                         return false;
  4428.                     }
  4429.                 } else {
  4430.                     // can't do anything - should not happen
  4431.                     console.warn('No alternative action found for OverlayTrigger %o', this.$trigger);
  4432.                     return true;
  4433.                 }
  4434.             }
  4435.  
  4436.             // abort if this is a username / avatar overlay with NoOverlay specified
  4437.             if (isUserLink && this.$trigger.hasClass('NoOverlay')) {
  4438.                 return true;
  4439.             }
  4440.  
  4441.             // abort if the event has a modifier key
  4442.             if (e.ctrlKey || e.shiftKey || e.altKey) {
  4443.                 return true;
  4444.             }
  4445.  
  4446.             // abort if the event is a middle or right-button click
  4447.             if (e.which > 1) {
  4448.                 return true;
  4449.             }
  4450.  
  4451.             if (this.options && this.options.onBeforeTrigger) {
  4452.                 var newE = $.Event();
  4453.                 newE.clickEvent = e;
  4454.                 this.options.onBeforeTrigger(newE);
  4455.                 if (newE.isDefaultPrevented()) {
  4456.                     return;
  4457.                 }
  4458.             }
  4459.  
  4460.             var beforeE = $.Event('BeforeOverlayTrigger');
  4461.             this.$trigger.trigger(beforeE);
  4462.             if (beforeE.isDefaultPrevented()) {
  4463.                 return;
  4464.             }
  4465.  
  4466.             e.preventDefault();
  4467.             e.stopPropagation();
  4468.             e.stopImmediatePropagation();
  4469.  
  4470.             if (parentOverlay && parentOverlay.isOpened()) {
  4471.                 var self = this;
  4472.                 parentOverlay.getTrigger().one('onClose', function (innerE) {
  4473.                     setTimeout(function () {
  4474.                         self.show(innerE);
  4475.                     }, 0);
  4476.                 });
  4477.                 parentOverlay.getConf().mask.closeSpeed = 0;
  4478.                 parentOverlay.close();
  4479.                 return;
  4480.             }
  4481.  
  4482.             $('#exposeMask').remove();
  4483.  
  4484.             if (!this.OverlayLoader) {
  4485.                 options = (typeof this.options == 'object' ? this.options : {});
  4486.                 options = $.extend(options, this.$trigger.data('overlayoptions'));
  4487.  
  4488.                 cache = this.$trigger.data('cacheoverlay');
  4489.                 if (cache !== undefined) {
  4490.                     if (XenForo.isPositive(cache)) {
  4491.                         cache = true;
  4492.                     } else {
  4493.                         cache = false;
  4494.                         options.onClose = $.context(this, 'deCache');
  4495.                     }
  4496.                 } else if (this.$trigger.is('input:submit')) {
  4497.                     cache = false;
  4498.                     options.onClose = $.context(this, 'deCache');
  4499.                 }
  4500.  
  4501.                 if (isUserLink && !this.$trigger.hasClass('OverlayTrigger')) {
  4502.                     if (!this.$trigger.data('cardurl') && this.$trigger.attr('href')) {
  4503.                         cardHref = this.$trigger.attr('href').replace(/#.*$/, '');
  4504.                         if (cardHref.indexOf('?') >= 0) {
  4505.                             cardHref += '&card=1';
  4506.                         } else {
  4507.                             cardHref += '?card=1';
  4508.                         }
  4509.  
  4510.                         this.$trigger.data('cardurl', cardHref);
  4511.                     }
  4512.  
  4513.                     cache = true;
  4514.                     options.speed = XenForo.speed.fast;
  4515.                 }
  4516.  
  4517.                 this.OverlayLoader = new XenForo.OverlayLoader(this.$trigger, cache, options);
  4518.                 this.OverlayLoader.load();
  4519.  
  4520.                 e.preventDefault();
  4521.                 return true;
  4522.             }
  4523.  
  4524.             this.OverlayLoader.show();
  4525.         },
  4526.  
  4527.         deCache: function () {
  4528.             if (this.OverlayLoader && this.OverlayLoader.overlay) {
  4529.                 console.info('DeCache %o', this.OverlayLoader.overlay.getOverlay());
  4530.                 this.OverlayLoader.overlay.getTrigger().removeData('overlay');
  4531.                 this.OverlayLoader.overlay.getOverlay().empty().remove();
  4532.             }
  4533.             delete (this.OverlayLoader);
  4534.         }
  4535.     };
  4536.  
  4537.     // *********************************************************************
  4538.  
  4539.     XenForo.LightBoxTrigger = function ($link) {
  4540.         $link.attr('data-fancybox', 'gallery');
  4541.         $link.on('click', function (e) {
  4542.             if (!XenForo.Fancybox) {
  4543.                 XenForo.Fancybox = true;
  4544.                 e.preventDefault();
  4545.                 XenForo.scriptLoader.loadMultiple([{ 'type': 'js', 'url': 'js/fancybox/fancybox.umd.js' }, { 'type': 'css', 'list': ['fancybox'] }], function () {
  4546.                     $link.children().click()
  4547.                 })
  4548.             }
  4549.         })
  4550.     };
  4551.  
  4552.     // *********************************************************************
  4553.  
  4554.     XenForo.OverlayLoaderCache = {};
  4555.  
  4556.     /**
  4557.      * Loads HTML and related external resources for an overlay
  4558.      *
  4559.      * @param jQuery Overlay trigger object
  4560.      * @param boolean If true, cache the overlay HTML for this URL
  4561.      * @param object Object of options for the overlay
  4562.      */
  4563.     XenForo.OverlayLoader = function ($trigger, cache, options) {
  4564.         this.__construct($trigger, options, cache);
  4565.     };
  4566.     XenForo.OverlayLoader.prototype =
  4567.     {
  4568.         __construct: function ($trigger, options, cache) {
  4569.             this.$trigger = $trigger;
  4570.             this.cache = cache;
  4571.             this.options = options;
  4572.         },
  4573.  
  4574.         /**
  4575.          * Initiates the loading of the overlay, or returns it from cache
  4576.          *
  4577.          * @param function Callback to run on successful load
  4578.          */
  4579.         load: function (callback) {
  4580.             // special case for submit buttons
  4581.             if (this.$trigger.is('input:submit') || this.$trigger.is('button:submit')) {
  4582.                 this.cache = false;
  4583.  
  4584.                 if (!this.xhr) {
  4585.                     var $form = this.$trigger.closest('form'),
  4586.  
  4587.                         serialized = $form.serializeArray();
  4588.  
  4589.                     serialized.push(
  4590.                         {
  4591.                             name: this.$trigger.attr('name'),
  4592.                             value: this.$trigger.attr('value')
  4593.                         });
  4594.  
  4595.                     this.xhr = XenForo.ajax(
  4596.                         $form.attr('action'),
  4597.                         serialized,
  4598.                         $.context(this, 'loadSuccess')
  4599.                     );
  4600.                 }
  4601.  
  4602.                 return;
  4603.             }
  4604.  
  4605.             //TODO: ability to point to extant overlay HTML, rather than loading via AJAX
  4606.             this.href = this.$trigger.data('cardurl') || this.$trigger.data('href') || this.$trigger.attr('href');
  4607.  
  4608.             if (!this.href) {
  4609.                 console.warn('No overlay href found for control %o', this.$trigger);
  4610.                 return false;
  4611.             }
  4612.  
  4613.             console.info('OverlayLoader for %s', this.href);
  4614.  
  4615.             this.callback = callback;
  4616.  
  4617.             if (this.cache && XenForo.OverlayLoaderCache[this.href]) {
  4618.                 XenForo.OverlayLoaderCache[this.href].load()
  4619.             } else if (!this.xhr) {
  4620.                 this.xhr = XenForo.ajax(
  4621.                     this.href, '',
  4622.                     $.context(this, 'loadSuccess'), { type: 'GET' }
  4623.                 );
  4624.             }
  4625.         },
  4626.  
  4627.         /**
  4628.          * Handles the returned ajaxdata from an overlay xhr load,
  4629.          * Stores the template HTML then inits externals (js, css) loading
  4630.          *
  4631.          * @param object ajaxData
  4632.          * @param string textStatus
  4633.          */
  4634.         loadSuccess: function (ajaxData, textStatus) {
  4635.             delete (this.xhr);
  4636.  
  4637.             if (XenForo.hasResponseError(ajaxData)) {
  4638.                 return false;
  4639.             }
  4640.  
  4641.             // received a redirect rather than a view - follow it.
  4642.             if (ajaxData._redirectStatus && ajaxData._redirectTarget) {
  4643.                 var fn = function () {
  4644.                     XenForo.redirect(ajaxData._redirectTarget);
  4645.                 };
  4646.  
  4647.                 if (XenForo._manualDeferOverlay) {
  4648.                     $(document).one('ManualDeferComplete', fn);
  4649.                 } else {
  4650.                     fn();
  4651.                 }
  4652.                 return false;
  4653.             }
  4654.  
  4655.             this.options.title = ajaxData.h1 || ajaxData.title;
  4656.  
  4657.             new XenForo.ExtLoader(ajaxData, $.context(this, 'createOverlay'));
  4658.         },
  4659.  
  4660.         /**
  4661.          * Creates an overlay containing the appropriate template HTML,
  4662.          * runs the callback specified in .load() and then shows the overlay.
  4663.          *
  4664.          * @param jQuery Cached $overlay object
  4665.          */
  4666.         createOverlay: function ($overlay) {
  4667.             var contents = ($overlay && $overlay.templateHtml) ? $overlay.templateHtml : $overlay;
  4668.  
  4669.             this.overlay = XenForo.createOverlay(this.$trigger, contents, this.options);
  4670.  
  4671.             if (this.cache && this.overlay) {
  4672.                 XenForo.OverlayLoaderCache[this.href] = this.overlay
  4673.             }
  4674.  
  4675.             if (typeof this.callback == 'function') {
  4676.                 this.callback();
  4677.             }
  4678.  
  4679.             this.show();
  4680.         },
  4681.  
  4682.         /**
  4683.          * Shows a finished overlay
  4684.          */
  4685.         show: function () {
  4686.             if (!this.overlay && this.href === "misc/lightbox") {
  4687.                 return;
  4688.             }
  4689.  
  4690.             if (!this.overlay) {
  4691.                 console.warn('Attempted to call XenForo.OverlayLoader.show() for %s before overlay is created', this.href);
  4692.                 this.load(this.callback);
  4693.                 return;
  4694.             }
  4695.  
  4696.             this.overlay.load();
  4697.             $(document).trigger({
  4698.                 type: 'XFOverlay',
  4699.                 overlay: this.overlay,
  4700.                 trigger: this.$trigger
  4701.             });
  4702.         }
  4703.     };
  4704.  
  4705.     // *********************************************************************
  4706.  
  4707.     XenForo.LoginBar = function ($loginBar) {
  4708.         var $form = $('#login').appendTo($loginBar.find('.pageContent')),
  4709.  
  4710.             /**
  4711.              * Opens the login form
  4712.              *
  4713.              * @param event
  4714.              */
  4715.             openForm = function (e) {
  4716.                 e.preventDefault();
  4717.  
  4718.                 XenForo.chromeAutoFillFix($form);
  4719.  
  4720.                 $form.xfSlideIn(XenForo.speed.slow, 'easeOutBack', function () {
  4721.                     $('#LoginControl').select();
  4722.  
  4723.                     $loginBar.expose($.extend(XenForo._overlayConfig.mask,
  4724.                         {
  4725.                             loadSpeed: XenForo.speed.slow,
  4726.                             onBeforeLoad: function (e) {
  4727.                                 $form.css('outline', '0px solid black');
  4728.                             },
  4729.                             onLoad: function (e) {
  4730.                                 $form.css('outline', '');
  4731.                             },
  4732.                             onBeforeClose: function (e) {
  4733.                                 closeForm(false, true);
  4734.                                 return true;
  4735.                             }
  4736.                         }));
  4737.                 });
  4738.             },
  4739.  
  4740.             /**
  4741.              * Closes the login form
  4742.              *
  4743.              * @param event
  4744.              * @param boolean
  4745.              */
  4746.             closeForm = function (e, isMaskClosing) {
  4747.                 if (e) e.target.blur();
  4748.  
  4749.                 $form.xfSlideOut(XenForo.speed.fast);
  4750.  
  4751.                 if (!isMaskClosing && $.mask) {
  4752.                     $.mask.close();
  4753.                 }
  4754.             };
  4755.  
  4756.         /**
  4757.          * Toggles the login form
  4758.          */
  4759.         $('label[for="LoginControl"]').click(function (e) {
  4760.             if ($(this).closest('#login').length == 0) {
  4761.                 e.preventDefault();
  4762.  
  4763.                 if ($form._xfSlideWrapper(true)) {
  4764.                     closeForm(e);
  4765.                 } else {
  4766.                     $(XenForo.getPageScrollTagName()).scrollTop(0);
  4767.  
  4768.                     openForm(e);
  4769.                 }
  4770.             }
  4771.         });
  4772.  
  4773.         /**
  4774.          * Changes the text of the Log in / Sign up submit button depending on state
  4775.          */
  4776.         $loginBar.delegate('input[name="register"]', 'click', function (e) {
  4777.             var $button = $form.find('input.button.primary'),
  4778.                 register = $form.find('input[name="register"]:checked').val();
  4779.  
  4780.             $form.find('input.button.primary').val(register == '1'
  4781.                 ? $button.data('signupphrase')
  4782.                 : $button.data('loginphrase'));
  4783.  
  4784.             $form.find('label.rememberPassword').css('visibility', (register == '1' ? 'hidden' : 'visible'));
  4785.         });
  4786.  
  4787.         // close form if any .click elements within it are clicked
  4788.         $loginBar.delegate('.close', 'click', closeForm);
  4789.     };
  4790.  
  4791.     // *********************************************************************
  4792.  
  4793.     XenForo.QuickSearch = function ($target) { this.__construct($target); };
  4794.     XenForo.QuickSearch.prototype = {
  4795.         $target: null,
  4796.  
  4797.         $form: null,
  4798.  
  4799.         $queryInput: null,
  4800.         $clearButton: null,
  4801.  
  4802.         $searchResults: null,
  4803.  
  4804.         updateTimer: null,
  4805.         xhr: null,
  4806.  
  4807.         runCount: 0,
  4808.  
  4809.         __construct: function ($target) {
  4810.             this.$target = $target;
  4811.  
  4812.             const $form = this.$form = $target.find('form');
  4813.  
  4814.             this.$queryInput = $form.find('.QuickSearchQuery')
  4815.                 .on('focus', $.context(this, 'focus'))
  4816.                 .on('input', $.context(this, 'input'));
  4817.             this.$clearButton = $form.find('.QuickSearchClear')
  4818.                 .on('click', $.context(this, 'clearQueryInput'));
  4819.  
  4820.             this.$searchResults = $('.SearchResultsList');
  4821.  
  4822.             $('#QuickSearchPlaceholder').on('click', $.context(this, 'placeholderClick'));
  4823.         },
  4824.  
  4825.         placeholderClick: function (e) {
  4826.             e.preventDefault();
  4827.  
  4828.             setTimeout(function () {
  4829.                 $('#QuickSearch').addClass('show');
  4830.                 $('#QuickSearchPlaceholder').addClass('hide');
  4831.                 $('#QuickSearchQuery').focus();
  4832.                 if (XenForo.isTouchBrowser()) {
  4833.                     $('#QuickSearchQuery').blur();
  4834.                 }
  4835.             });
  4836.         },
  4837.  
  4838.         focus: function (e) {
  4839.             const $target = this.$target,
  4840.                 runCount = ++this.runCount;
  4841.  
  4842.             console.log('Show quick search menu (%d)', runCount);
  4843.  
  4844.             if (runCount === 1) {
  4845.                 if ($.browser.msie && $.browser.version < 9) {
  4846.                     const $form = this.$form;
  4847.                     $form.find('input').on('keydown', function (e) {
  4848.                         if (e.keyCode === 13) {
  4849.                             if (e.which === 13) {
  4850.                                 clearTimeout(this.updateTimer);
  4851.                             }
  4852.  
  4853.                             $form.submit();
  4854.  
  4855.                             return false;
  4856.                         }
  4857.                     });
  4858.                 }
  4859.  
  4860.                 $(XenForo._isWebkitMobile ? document.body.children : document).on(
  4861.                     'click',
  4862.                     function (clickEvent) {
  4863.                         if (!$(clickEvent.target).closest('.QuickSearch').length) {
  4864.                             console.log('Hide quick search menu');
  4865.  
  4866.                             $('#QuickSearch').removeClass('show');
  4867.                             $('#QuickSearchPlaceholder').removeClass('hide');
  4868.  
  4869.                             $target.find('.secondaryControls').slideUp(XenForo.speed.xfast, function () {
  4870.                                 $target.removeClass('active');
  4871.                                 if ($.browser.msie) {
  4872.                                     $('body').css('zoom', 1);
  4873.                                     setTimeout(function () {
  4874.                                         $('body').css('zoom', '');
  4875.                                     }, 100);
  4876.                                 }
  4877.                             });
  4878.                         }
  4879.                     }
  4880.                 );
  4881.             }
  4882.  
  4883.             $target.addClass('active');
  4884.             $target.find('.secondaryControls').slideDown(0);
  4885.         },
  4886.  
  4887.         input: function (e) {
  4888.             if ($(e.currentTarget).val()) {
  4889.                 this.$target.addClass('hasInputValue');
  4890.                 this.$clearButton.addClass('show')
  4891.             }
  4892.             else {
  4893.                 this.$target.removeClass('hasInputValue');
  4894.                 this.$clearButton.removeClass('show')
  4895.             }
  4896.  
  4897.             if (this.updateTimer) {
  4898.                 clearTimeout(this.updateTimer);
  4899.             }
  4900.  
  4901.             const self = this;
  4902.             this.updateTimer = setTimeout(function () {
  4903.                 if (self.$queryInput.val().length < 3) {
  4904.                     self.$searchResults.html('');
  4905.  
  4906.                     return;
  4907.                 }
  4908.  
  4909.                 this.xhr = XenForo.ajax(
  4910.                     self.$target.data('member-search-url'),
  4911.                     {
  4912.                         username: self.$queryInput.val()
  4913.                     },
  4914.                     function (data, status) {
  4915.                         if (XenForo.hasResponseError(data, status)) {
  4916.                             return false;
  4917.                         }
  4918.                         else {
  4919.                             self.$searchResults.html('');
  4920.                             $(data.templateHtml).xfInsert('appendTo', self.$searchResults);
  4921.                         }
  4922.                     }
  4923.                 );
  4924.             }, 500);
  4925.         },
  4926.  
  4927.         clearQueryInput: function () {
  4928.             this.$queryInput.val('').trigger('input');
  4929.             this.$searchResults.html('');
  4930.  
  4931.             if (this.xhr) {
  4932.                 this.xhr.abort();
  4933.                 this.xhr = null;
  4934.             }
  4935.         }
  4936.     };
  4937.  
  4938.     // *********************************************************************
  4939.  
  4940.     XenForo.configureTooltipRtl = function (config) {
  4941.         if (config.offset !== undefined) {
  4942.             config.offset = XenForo.switchOffsetRTL(config.offset);
  4943.         }
  4944.  
  4945.         if (config.position !== undefined) {
  4946.             config.position = XenForo.switchStringRTL(config.position);
  4947.         }
  4948.  
  4949.         return config;
  4950.     };
  4951.  
  4952.     /**
  4953.      * Wrapper for jQuery Tools Tooltip
  4954.      *
  4955.      * @param $element
  4956.      */
  4957.     XenForo.Tooltip = function ($element) {
  4958.         if ($element.hasClass('PopupTooltip')) {
  4959.             var isFirstShown = true;
  4960.             var isElementLoaded = false;
  4961.             var next = $element.next($($element.data('content')))
  4962.             var $content = $('<div>').append(next.clone()).html();
  4963.             if (!next.hasClass('control')) {
  4964.                 next.remove()
  4965.             }
  4966.  
  4967.             const like_link = $element.data('likes-url');
  4968.  
  4969.             if (like_link) {
  4970.                 tippy($element.get(), {
  4971.                     content: "",
  4972.                     theme: 'popup',
  4973.                     interactive: true,
  4974.                     arrow: true,
  4975.                     maxWidth: $element.data('width') || 250,
  4976.                     animation: 'shift-toward',
  4977.                     hideOnClick: XenForo.isTouchBrowser(),
  4978.                     placement: $element.data('placement') || 'top',
  4979.                     zIndex: $element.closest('.xenOverlay').length ? 11111 : 9000, // overlay fix
  4980.                     trigger: XenForo.isTouchBrowser() ? 'click' : 'mouseenter focus', // Touch fix
  4981.                     onTrigger: function () {
  4982.                         if (!isElementLoaded || !$element.get(0)._tippy.props.content) {
  4983.                             setTimeout(() => {
  4984.                                 $element.get(0)._tippy.hide();
  4985.                             }, 0)
  4986.  
  4987.                             XenForo.ajax(like_link, {}, function (ajaxData) {
  4988.                                 if (ajaxData.templateHtml) {
  4989.                                     isElementLoaded = true;
  4990.                                     $element.get(0)._tippy.setContent(ajaxData.templateHtml);
  4991.                                     if ($element.is(':hover')) {
  4992.                                         $element.get(0)._tippy.show();
  4993.                                     }
  4994.                                 }
  4995.                             }.bind(this));
  4996.                         }
  4997.                     },
  4998.                     onShown: function () {
  4999.                         if ($('body').hasClass('iOS')) {
  5000.                             $element.click();
  5001.                         }
  5002.                         if (!isFirstShown) return;
  5003.                         $('.' + $(this.content).attr('class')).xfActivate();
  5004.                         isFirstShown = false;
  5005.                     }
  5006.                 });
  5007.             } else {
  5008.                 tippy($element.get(), {
  5009.                     content: $content,
  5010.                     theme: 'popup',
  5011.                     interactive: true,
  5012.                     arrow: true,
  5013.                     maxWidth: $element.data('width') || 250,
  5014.                     animation: 'shift-toward',
  5015.                     hideOnClick: XenForo.isTouchBrowser(),
  5016.                     placement: $element.data('placement') || 'top',
  5017.                     zIndex: $element.closest('.xenOverlay').length ? 11111 : 9000, // overlay fix
  5018.                     trigger: XenForo.isTouchBrowser() ? 'click' : 'mouseenter focus', // Touch fix
  5019.  
  5020.                     onShown: function () {
  5021.                         if ($('body').hasClass('iOS')) {
  5022.                             $element.click();
  5023.                         }
  5024.  
  5025.                         if (!isFirstShown) return;
  5026.                         $('.' + $(this.content).attr('class')).xfActivate();
  5027.                         isFirstShown = false;
  5028.                     }
  5029.                 });
  5030.             }
  5031.         } else {
  5032.             var title = $element.attr('title');
  5033.             let cachedtitle = $element.attr('data-cachedtitle')
  5034.             if (!title && cachedtitle) {
  5035.                 title = cachedtitle  // if restored from cache (title is empty)
  5036.             }
  5037.             $element.attr('data-cachedtitle', title)
  5038.             $element.attr('title', '');
  5039.             tippy($element.get(), {
  5040.                 arrow: true,
  5041.                 animation: 'shift-toward',
  5042.                 distance: 5,
  5043.                 maxWidth: 250,
  5044.                 zIndex: 11111,
  5045.                 placement: $element.data('placement') || 'top',
  5046.                 content: XenForo.htmlspecialchars(title),
  5047.                 trigger: XenForo.isTouchBrowser() ? 'click' : 'mouseenter focus', // Touch fix
  5048.  
  5049.                 onShow: function () {
  5050.                     if ($('body').hasClass('iOS')) {
  5051.                         $element.click();
  5052.                     }
  5053.                 },
  5054.             });
  5055.         }
  5056.     };
  5057.     // *********************************************************************
  5058.  
  5059.     /*XenForo.StatusTooltip = function ($element) {
  5060.                  if ($element.attr('title')) {
  5061.                          var title = XenForo.htmlspecialchars($element.attr('title'));
  5062.  
  5063.                          $element.attr('title', title).tooltip(XenForo.configureTooltipRtl(
  5064.                                  {
  5065.                                          effect: 'slide',
  5066.                                          slideOffset: 30,
  5067.                                          position: 'bottom left',
  5068.                                          offset: [10, 1],
  5069.                                          tipClass: 'xenTooltip statusTip',
  5070.                                          layout: '<div><span class="arrow" /></div>'
  5071.                                  }));
  5072.                  }
  5073.          };*/
  5074.  
  5075.     // *********************************************************************
  5076.  
  5077.     /*XenForo.NodeDescriptionTooltip = function ($title) {
  5078.                  var description = $title.data('description');
  5079.  
  5080.                  if (description && $(description).length) {
  5081.                          var $description = $(description)
  5082.                          .addClass('xenTooltip nodeDescriptionTip')
  5083.                          .appendTo('body')
  5084.                          .append('<span class="arrow" />');
  5085.  
  5086.                          $title.tooltip(XenForo.configureTooltipRtl(
  5087.                                  {
  5088.                                          effect: 'slide',
  5089.                                          slideOffset: 30,
  5090.                                          offset: [30, 10],
  5091.                                          slideInSpeed: XenForo.speed.xfast,
  5092.                                          slideOutSpeed: 50 * XenForo._animationSpeedMultiplier,
  5093.  
  5094.                                          /!*effect: 'fade',
  5095.                                  fadeInSpeed: XenForo.speed.xfast,
  5096.                                  fadeOutSpeed: XenForo.speed.xfast,*!/
  5097.  
  5098.                                          predelay: 250,
  5099.                                          position: 'bottom right',
  5100.                                          tip: description,
  5101.  
  5102.                                          onBeforeShow: function () {
  5103.                                                  if (!$title.data('tooltip-shown')) {
  5104.                                                          if ($(window).width() < 600) {
  5105.                                                                  var conf = $title.data('tooltip').getConf();
  5106.                                                                  conf.slideOffset = 0;
  5107.                                                                  conf.effect = 'toggle';
  5108.                                                                  conf.offset = [20, -$title.width()];
  5109.                                                                  conf.position = ['top', 'right'];
  5110.  
  5111.                                                                  if (XenForo.isRTL()) {
  5112.                                                                          conf.offset[1] *= -1;
  5113.                                                                          conf.position[1] = 'left';
  5114.                                                                  }
  5115.  
  5116.                                                                  $description.addClass('arrowBottom');
  5117.                                                          }
  5118.  
  5119.                                                          $title.data('tooltip-shown', true);
  5120.                                                  }
  5121.                                          }
  5122.                                  }));
  5123.                          $title.click(function () {
  5124.                                  $(this).data('tooltip').hide();
  5125.                          });
  5126.                  }
  5127.          };*/
  5128.  
  5129.     // *********************************************************************
  5130.  
  5131.     XenForo.AccountMenu = function ($menu) {
  5132.         $menu.find('.submitUnit').hide();
  5133.  
  5134.         $menu.find('.StatusEditor').focus(function (e) {
  5135.             if ($menu.is(':visible')) {
  5136.                 $menu.find('.submitUnit').show();
  5137.             }
  5138.         });
  5139.     };
  5140.  
  5141.     // *********************************************************************
  5142.  
  5143.     XenForo.FollowLink = function ($link) {
  5144.         $link.click(function (e) {
  5145.             e.preventDefault();
  5146.  
  5147.             $link.get(0).blur();
  5148.  
  5149.             XenForo.ajax(
  5150.                 $link.attr('href'),
  5151.                 { _xfConfirm: 1 },
  5152.                 function (ajaxData, textStatus) {
  5153.                     if (XenForo.hasResponseError(ajaxData)) {
  5154.                         return false;
  5155.                     }
  5156.  
  5157.                     $link.xfFadeOut(XenForo.speed.fast, function () {
  5158.                         $link
  5159.                             .attr('href', ajaxData.linkUrl)
  5160.                             .html(ajaxData.linkPhrase)
  5161.                             .xfFadeIn(XenForo.speed.fast);
  5162.                     });
  5163.                 }
  5164.             );
  5165.         });
  5166.     };
  5167.  
  5168.     // *********************************************************************
  5169.  
  5170.     /**
  5171.      * Allows relative hash links to smoothly scroll into place,
  5172.      * Primarily used for 'x posted...' messages on bb code quote.
  5173.      *
  5174.      * @param jQuery a.AttributionLink
  5175.      */
  5176.     XenForo.AttributionLink = function ($link) {
  5177.         $link.click(function (e) {
  5178.             if ($(this.hash).length) {
  5179.                 try {
  5180.                     var hash = this.hash,
  5181.                         top = $(this.hash).offset().top,
  5182.                         scroller = XenForo.getPageScrollTagName();
  5183.  
  5184.                     if ("pushState" in window.history) {
  5185.                         window.history.pushState({}, '', window.location.toString().replace(/#.*$/, '') + hash);
  5186.                     }
  5187.  
  5188.                     var menu_height = $('#navigation').get(0).getBoundingClientRect().height || 45;
  5189.  
  5190.                     $(scroller).animate({ scrollTop: top - menu_height }, XenForo.speed.normal, 'easeOutBack', function () {
  5191.                         if (!window.history.pushState) {
  5192.                             window.location.hash = hash;
  5193.                         }
  5194.                         XenForo.animateBackgroundColor($(hash));
  5195.                     });
  5196.                 } catch (e) {
  5197.                     window.location.hash = this.hash;
  5198.                 }
  5199.  
  5200.                 e.preventDefault();
  5201.             }
  5202.         });
  5203.     };
  5204.  
  5205.     // *********************************************************************
  5206.  
  5207.     /**
  5208.      * Allows clicks on one element to trigger the click event of another
  5209.      *
  5210.      * @param jQuery .ClickProxy[rel="{selectorForTarget}"]
  5211.      *
  5212.      * @return boolean false - prevents any direct action for the proxy element on click
  5213.      */
  5214.     XenForo.ClickProxy = function ($element) {
  5215.         $element.click(function (e) {
  5216.             $($element.attr('rel')).click();
  5217.  
  5218.             if (!$element.data('allowdefault')) {
  5219.                 return false;
  5220.             }
  5221.         });
  5222.     };
  5223.  
  5224.     // *********************************************************************
  5225.  
  5226.     /**
  5227.      * ReCaptcha wrapper
  5228.      */
  5229.     XenForo.ReCaptcha = function ($captcha) {
  5230.         this.__construct($captcha);
  5231.     };
  5232.     XenForo.ReCaptcha.prototype =
  5233.     {
  5234.         __construct: function ($captcha) {
  5235.             if (XenForo.ReCaptcha.instance) {
  5236.                 XenForo.ReCaptcha.instance.remove();
  5237.             }
  5238.             XenForo.ReCaptcha.instance = this;
  5239.  
  5240.             this.publicKey = $captcha.data('publickey');
  5241.             if (!this.publicKey) {
  5242.                 return;
  5243.             }
  5244.  
  5245.             $captcha.siblings('noscript').remove();
  5246.  
  5247.             $captcha.uniqueId();
  5248.             this.$captcha = $captcha;
  5249.             this.type = 'image';
  5250.  
  5251.             $captcha.find('.ReCaptchaReload').click($.context(this, 'reload'));
  5252.             $captcha.find('.ReCaptchaSwitch').click($.context(this, 'switchType'));
  5253.  
  5254.             this.load();
  5255.  
  5256.             $captcha.closest('form.AutoValidator').bind(
  5257.                 {
  5258.                     AutoValidationDataReceived: $.context(this, 'reload')
  5259.                 });
  5260.         },
  5261.  
  5262.         load: function () {
  5263.             if (window.Recaptcha) {
  5264.                 this.create();
  5265.             } else {
  5266.                 var f = $.context(this, 'create'),
  5267.                     delay = ($.browser.msie && $.browser.version <= 6 ? 250 : 0); // helps IE6 loading
  5268.  
  5269.                 $.getScript('https://www.google.com/recaptcha/api/js/recaptcha_ajax.js',
  5270.                     function () {
  5271.                         setTimeout(f, delay);
  5272.                     }
  5273.                 );
  5274.             }
  5275.         },
  5276.  
  5277.         create: function () {
  5278.             var $c = this.$captcha;
  5279.  
  5280.             window.Recaptcha.create(this.publicKey, $c.attr('id'),
  5281.                 {
  5282.                     theme: 'custom',
  5283.                     callback: function () {
  5284.                         $c.show();
  5285.                         $('#ReCaptchaLoading').remove();
  5286.                         // webkit seems to overwrite this value using the back button
  5287.                         $('#recaptcha_challenge_field').val(window.Recaptcha.get_challenge());
  5288.                     }
  5289.                 });
  5290.         },
  5291.  
  5292.         reload: function (e) {
  5293.             if (!window.Recaptcha) {
  5294.                 return;
  5295.             }
  5296.  
  5297.             if (!$(e.target).is('form')) {
  5298.                 e.preventDefault();
  5299.             }
  5300.             window.Recaptcha.reload();
  5301.         },
  5302.  
  5303.         switchType: function (e) {
  5304.             e.preventDefault();
  5305.             this.type = (this.type == 'image' ? 'audio' : 'image');
  5306.             window.Recaptcha.switch_type(this.type);
  5307.         },
  5308.  
  5309.         remove: function () {
  5310.             this.$captcha.empty().remove();
  5311.             if (window.Recaptcha) {
  5312.                 window.Recaptcha.destroy();
  5313.             }
  5314.         }
  5315.     };
  5316.     XenForo.ReCaptcha.instance = null;
  5317.  
  5318.     XenForo.NoCaptcha = function ($captcha) {
  5319.         this.__construct($captcha);
  5320.     };
  5321.     XenForo.NoCaptcha.prototype =
  5322.     {
  5323.         __construct: function ($captcha) {
  5324.             this.$captcha = $captcha;
  5325.             this.noCaptchaId = null;
  5326.  
  5327.             $captcha.closest('form.AutoValidator').bind(
  5328.                 {
  5329.                     AutoValidationDataReceived: $.context(this, 'reload')
  5330.                 });
  5331.  
  5332.             if (window.grecaptcha) {
  5333.                 this.create();
  5334.             } else {
  5335.                 XenForo.NoCaptcha._callbacks.push($.context(this, 'create'));
  5336.                 $.getScript('https://www.google.com/recaptcha/api.js?onload=XFNoCaptchaCallback&render=explicit');
  5337.             }
  5338.         },
  5339.  
  5340.         create: function () {
  5341.             if (!window.grecaptcha) {
  5342.                 return;
  5343.             }
  5344.  
  5345.             this.noCaptchaId = grecaptcha.render(this.$captcha[0], { sitekey: this.$captcha.data('sitekey') });
  5346.         },
  5347.  
  5348.         reload: function () {
  5349.             if (!window.grecaptcha || this.noCaptchaId === null) {
  5350.                 return;
  5351.             }
  5352.  
  5353.             grecaptcha.reset(this.noCaptchaId);
  5354.         }
  5355.     };
  5356.     XenForo.NoCaptcha._callbacks = [];
  5357.     window.XFNoCaptchaCallback = function () {
  5358.         var cb = XenForo.NoCaptcha._callbacks;
  5359.  
  5360.         for (var i = 0; i < cb.length; i++) {
  5361.             cb[i]();
  5362.         }
  5363.     };
  5364.  
  5365.     // *********************************************************************
  5366.  
  5367.     XenForo.SolveMediaCaptcha = function ($captcha) {
  5368.         this.__construct($captcha);
  5369.     };
  5370.     XenForo.SolveMediaCaptcha.prototype =
  5371.     {
  5372.         __construct: function ($captcha) {
  5373.             if (XenForo.SolveMediaCaptcha.instance) {
  5374.                 XenForo.SolveMediaCaptcha.instance.remove();
  5375.             }
  5376.             XenForo.SolveMediaCaptcha.instance = this;
  5377.  
  5378.             this.cKey = $captcha.data('c-key');
  5379.             if (!this.cKey) {
  5380.                 return;
  5381.             }
  5382.  
  5383.             $captcha.siblings('noscript').remove();
  5384.  
  5385.             $captcha.uniqueId();
  5386.             this.$captcha = $captcha;
  5387.             this.type = 'image';
  5388.  
  5389.             this.load();
  5390.  
  5391.             $captcha.closest('form.AutoValidator').bind(
  5392.                 {
  5393.                     AutoValidationDataReceived: $.context(this, 'reload')
  5394.                 });
  5395.         },
  5396.  
  5397.         load: function () {
  5398.             if (window.ACPuzzle) {
  5399.                 this.create();
  5400.             } else {
  5401.                 var prefix = window.location.protocol == 'https:' ? 'https://api-secure' : 'http://api';
  5402.  
  5403.                 window.ACPuzzleOptions = {
  5404.                     onload: $.context(this, 'create')
  5405.                 };
  5406.                 XenForo.loadJs(prefix + '.solvemedia.com/papi/challenge.ajax');
  5407.             }
  5408.         },
  5409.  
  5410.         create: function () {
  5411.             var $c = this.$captcha;
  5412.  
  5413.             window.ACPuzzle.create(this.cKey, $c.attr('id'), {
  5414.                 theme: $c.data('theme') || 'white',
  5415.                 lang: $('html').attr('lang').substr(0, 2) || 'en'
  5416.             });
  5417.         },
  5418.  
  5419.         reload: function (e) {
  5420.             if (!window.ACPuzzle) {
  5421.                 return;
  5422.             }
  5423.  
  5424.             if (!$(e.target).is('form')) {
  5425.                 e.preventDefault();
  5426.             }
  5427.             window.ACPuzzle.reload();
  5428.         },
  5429.  
  5430.         remove: function () {
  5431.             this.$captcha.empty().remove();
  5432.             if (window.ACPuzzle) {
  5433.                 window.ACPuzzle.destroy();
  5434.             }
  5435.         }
  5436.     };
  5437.     XenForo.SolveMediaCaptcha.instance = null;
  5438.  
  5439.     // *********************************************************************
  5440.  
  5441.     XenForo.KeyCaptcha = function ($captcha) {
  5442.         this.__construct($captcha);
  5443.     };
  5444.     XenForo.KeyCaptcha.prototype =
  5445.     {
  5446.         __construct: function ($captcha) {
  5447.             this.$captcha = $captcha;
  5448.  
  5449.             this.$form = $captcha.closest('form');
  5450.             this.$form.uniqueId();
  5451.  
  5452.             this.$codeEl = this.$form.find('input[name=keycaptcha_code]');
  5453.             this.$codeEl.uniqueId();
  5454.  
  5455.             this.load();
  5456.             $captcha.closest('form.AutoValidator').bind({
  5457.                 AutoValidationDataReceived: $.context(this, 'reload')
  5458.             });
  5459.         },
  5460.  
  5461.         load: function () {
  5462.             if (window.s_s_c_onload) {
  5463.                 this.create();
  5464.             } else {
  5465.                 var $captcha = this.$captcha;
  5466.  
  5467.                 window.s_s_c_user_id = $captcha.data('user-id');
  5468.                 window.s_s_c_session_id = $captcha.data('session-id');
  5469.                 window.s_s_c_captcha_field_id = this.$codeEl.attr('id');
  5470.                 window.s_s_c_submit_button_id = 'sbutton-#-r';
  5471.                 window.s_s_c_web_server_sign = $captcha.data('sign');
  5472.                 window.s_s_c_web_server_sign2 = $captcha.data('sign2');
  5473.                 document.s_s_c_element = this.$form[0];
  5474.                 document.s_s_c_debugmode = 1;
  5475.  
  5476.                 var $div = $('#div_for_keycaptcha');
  5477.                 if (!$div.length) {
  5478.                     $('body').append('<div id="div_for_keycaptcha" />');
  5479.                 }
  5480.  
  5481.                 XenForo.loadJs('https://backs.keycaptcha.com/swfs/cap.js');
  5482.             }
  5483.         },
  5484.  
  5485.         create: function () {
  5486.             window.s_s_c_onload(this.$form.attr('id'), this.$codeEl.attr('id'), 'sbutton-#-r');
  5487.         },
  5488.  
  5489.         reload: function (e) {
  5490.             if (!window.s_s_c_onload) {
  5491.                 return;
  5492.             }
  5493.  
  5494.             if (!$(e.target).is('form')) {
  5495.                 e.preventDefault();
  5496.             }
  5497.             this.load();
  5498.         }
  5499.     };
  5500.  
  5501.     // *********************************************************************
  5502.  
  5503.     /**
  5504.      * Loads a new (non-ReCaptcha) CAPTCHA upon verification failure
  5505.      *
  5506.      * @param jQuery #Captcha
  5507.      */
  5508.     XenForo.Captcha = function ($container) {
  5509.         var $form = $container.closest('form');
  5510.  
  5511.         $form.off('AutoValidationDataReceived.captcha').on('AutoValidationDataReceived.captcha', function (e) {
  5512.             $container.fadeTo(XenForo.speed.fast, 0.5);
  5513.  
  5514.             XenForo.ajax($container.data('source'), {}, function (ajaxData, textStatus) {
  5515.                 if (XenForo.hasResponseError(ajaxData)) {
  5516.                     return false;
  5517.                 }
  5518.  
  5519.                 if (XenForo.hasTemplateHtml(ajaxData)) {
  5520.                     $container.xfFadeOut(XenForo.speed.xfast, function () {
  5521.                         $(ajaxData.templateHtml).xfInsert('replaceAll', $container, 'xfFadeIn', XenForo.speed.xfast);
  5522.                     });
  5523.                 }
  5524.             });
  5525.         });
  5526.     };
  5527.  
  5528.     // *********************************************************************
  5529.  
  5530.     /**
  5531.      * Handles resizing of BB code [img] tags that would overflow the page
  5532.      *
  5533.      * @param jQuery img.bbCodeImage
  5534.      */
  5535.     XenForo.BbCodeImage = function ($image) {
  5536.         this.__construct($image);
  5537.     };
  5538.     XenForo.BbCodeImage.prototype =
  5539.     {
  5540.         __construct: function ($image) {
  5541.             this.$image = $image;
  5542.             this.actualWidth = 0;
  5543.  
  5544.             if ($image.closest('a').length) {
  5545.                 return;
  5546.             }
  5547.  
  5548.             $image
  5549.                 .attr('title', XenForo.phrases.click_image_show_full_size_version || 'Show full size')
  5550.                 .click($.context(this, 'toggleFullSize'));
  5551.  
  5552.             if (!XenForo.isTouchBrowser()) {
  5553.                 this.$image.tooltip(XenForo.configureTooltipRtl({
  5554.                     effect: 'slide',
  5555.                     slideOffset: 30,
  5556.                     position: 'top center',
  5557.                     offset: [45, 0],
  5558.                     tipClass: 'xenTooltip bbCodeImageTip',
  5559.                     onBeforeShow: $.context(this, 'isResized'),
  5560.                     onShow: $.context(this, 'addTipClick')
  5561.                 }));
  5562.             }
  5563.  
  5564.             if (!this.getImageWidth()) {
  5565.                 var src = $image.attr('src');
  5566.  
  5567.                 $image.bind({
  5568.                     load: $.context(this, 'getImageWidth')
  5569.                 });
  5570.                 //$image.attr('src', 'about:blank');
  5571.                 $image.attr('src', src);
  5572.             }
  5573.         },
  5574.  
  5575.         /**
  5576.          * Attempts to store the un-resized width of the image
  5577.          *
  5578.          * @return integer
  5579.          */
  5580.         getImageWidth: function () {
  5581.             this.$image.css({ 'max-width': 'none', 'max-height': 'none' });
  5582.             this.actualWidth = this.$image.width();
  5583.             this.$image.css({ 'max-width': '', 'max-height': '' });
  5584.  
  5585.             //console.log('BB Code Image %o has width %s', this.$image, this.actualWidth);
  5586.  
  5587.             return this.actualWidth;
  5588.         },
  5589.  
  5590.         /**
  5591.          * Shows and hides a full-size version of the image
  5592.          *
  5593.          * @param event
  5594.          */
  5595.         toggleFullSize: function (e) {
  5596.             if (this.actualWidth == 0) {
  5597.                 this.getImageWidth();
  5598.             }
  5599.  
  5600.             var currentWidth = this.$image.width(),
  5601.                 offset, cssOffset, scale,
  5602.                 scrollLeft, scrollTop,
  5603.                 layerX, layerY,
  5604.                 $fullSizeImage,
  5605.                 speed = window.navigator.userAgent.match(/Android|iOS|iPhone|iPad|Mobile Safari/i) ? 0 : XenForo.speed.normal,
  5606.                 easing = 'easeInOutQuart';
  5607.  
  5608.             if (this.actualWidth > currentWidth) {
  5609.                 offset = this.$image.offset();
  5610.                 cssOffset = offset;
  5611.                 scale = this.actualWidth / currentWidth;
  5612.                 layerX = e.pageX - offset.left;
  5613.                 layerY = e.pageY - offset.top;
  5614.  
  5615.                 if (XenForo.isRTL()) {
  5616.                     cssOffset.right = $('html').width() - cssOffset.left - currentWidth;
  5617.                     cssOffset.left = 'auto';
  5618.                 }
  5619.  
  5620.                 $fullSizeImage = $('<img />', { src: this.$image.attr('src') })
  5621.                     .addClass('bbCodeImageFullSize')
  5622.                     .css('width', currentWidth)
  5623.                     .css(cssOffset)
  5624.                     .click(function () {
  5625.                         $(this).remove();
  5626.                         $(XenForo.getPageScrollTagName()).scrollLeft(0).scrollTop(offset.top);
  5627.                     })
  5628.                     .appendTo('body')
  5629.                     .animate({ width: this.actualWidth }, speed, easing);
  5630.  
  5631.                 // remove full size image if an overlay is about to open
  5632.                 $(document).one('OverlayOpening', function () {
  5633.                     $fullSizeImage.remove();
  5634.                 });
  5635.  
  5636.                 // remove full-size image if the source image is contained by a ToggleTrigger target that is closing
  5637.                 $(document).bind('ToggleTriggerEvent', $.context(function (e, args) {
  5638.                     if (args.closing && args.$target.find(this.$image).length) {
  5639.                         console.info('Target is parent of this image %o', this.$image);
  5640.                         $fullSizeImage.remove();
  5641.                     }
  5642.                 }, this));
  5643.  
  5644.                 if (e.target == this.$image.get(0)) {
  5645.                     scrollLeft = offset.left + (e.pageX - offset.left) * scale - $(window).width() / 2;
  5646.                     scrollTop = offset.top + (e.pageY - offset.top) * scale - $(window).height() / 2;
  5647.                 } else {
  5648.                     scrollLeft = offset.left + (this.actualWidth / 2) - $(window).width() / 2;
  5649.                     scrollTop = offset.top + (this.$image.height() * scale / 2) - $(window).height() / 2;
  5650.                 }
  5651.  
  5652.                 $(XenForo.getPageScrollTagName()).animate(
  5653.                     {
  5654.                         scrollLeft: scrollLeft,
  5655.                         scrollTop: scrollTop
  5656.                     }, speed, easing, $.context(function () {
  5657.                         var tooltip = this.$image.data('tooltip');
  5658.                         if (tooltip) {
  5659.                             tooltip.hide();
  5660.                         }
  5661.                     }, this));
  5662.             } else {
  5663.                 console.log('BBCodeImage: this.actualWidth = %d, currentWidth = %d', this.actualWidth, currentWidth);
  5664.             }
  5665.         },
  5666.  
  5667.         isResized: function (e) {
  5668.             var width = this.$image.width();
  5669.  
  5670.             if (!width) {
  5671.                 return false;
  5672.             }
  5673.  
  5674.             if (this.getImageWidth() <= width) {
  5675.                 //console.log('Image is not resized %o', this.$image);
  5676.                 return false;
  5677.             }
  5678.         },
  5679.  
  5680.         addTipClick: function (e) {
  5681.             if (!this.tipClickAdded) {
  5682.                 $(this.$image.data('tooltip').getTip()).click($.context(this, 'toggleFullSize'));
  5683.                 this.tipClickAdded = true;
  5684.             }
  5685.         }
  5686.     };
  5687.  
  5688.     // *********************************************************************
  5689.  
  5690.     /**
  5691.      * Wrapper for the jQuery Tools Tabs system
  5692.      *
  5693.      * @param jQuery .Tabs
  5694.      */
  5695.     XenForo.Tabs = function ($tabContainer) {
  5696.         this.__construct($tabContainer);
  5697.     };
  5698.     XenForo.Tabs.prototype =
  5699.     {
  5700.         __construct: function ($tabContainer) {
  5701.             // var useHistory = XenForo.isPositive($tabContainer.data('history'));
  5702.             // TODO: disabled until base tag issues are resolved
  5703.             var useHistory = false;
  5704.  
  5705.             this.$tabContainer = $tabContainer;
  5706.             this.$panes = $($tabContainer.data('panes'));
  5707.  
  5708.             /*if (useHistory)
  5709.                      {
  5710.                              $tabContainer.find('a[href]').each(function()
  5711.                              {
  5712.                                      var $this = $(this), hrefParts = $this.attr('href').split('#');
  5713.                                      if (hrefParts[1] && location.pathname == hrefParts[0])
  5714.                                      {
  5715.                                              $this.attr('href', '#' + hrefParts[1]);
  5716.                                      }
  5717.                              });
  5718.                      }*/
  5719.  
  5720.             var $tabs = $tabContainer.find('a');
  5721.             if (!$tabs.length) {
  5722.                 $tabs = $tabContainer.children();
  5723.             }
  5724.  
  5725.             var $active = $tabs.filter('.active'),
  5726.                 initialIndex = 0;
  5727.  
  5728.             if ($active.length) {
  5729.                 $tabs.each(function () {
  5730.                     if (this == $active.get(0)) {
  5731.                         return false;
  5732.                     }
  5733.  
  5734.                     initialIndex++;
  5735.                 });
  5736.             }
  5737.  
  5738.             if (window.location.hash.length > 1) {
  5739.                 var id = window.location.hash.substr(1),
  5740.                     matchIndex = -1,
  5741.                     matched = false;
  5742.  
  5743.                 this.$panes.each(function () {
  5744.                     matchIndex++;
  5745.                     if ($(this).attr('id') === id) {
  5746.                         matched = true;
  5747.                         return false;
  5748.                     }
  5749.                     return true;
  5750.                 });
  5751.                 if (matched) {
  5752.                     initialIndex = matchIndex;
  5753.                 }
  5754.             }
  5755.  
  5756.             $tabContainer.tabs(this.$panes, {
  5757.                 current: 'active',
  5758.                 history: useHistory,
  5759.                 initialIndex: initialIndex,
  5760.                 onBeforeClick: $.context(this, 'onBeforeClick')
  5761.             });
  5762.             this.api = $tabContainer.data('tabs');
  5763.             this.useInfiniteScroll = !!this.$panes.parents('#AlertPanels').length
  5764.         },
  5765.  
  5766.         getCurrentTab: function () {
  5767.             return this.api.getIndex();
  5768.         },
  5769.  
  5770.         click: function (index) {
  5771.             this.api.click(index);
  5772.         },
  5773.  
  5774.         onBeforeClick: function (e, index) {
  5775.             var self = this
  5776.             this.$tabContainer.children().each(function (i) {
  5777.                 if (index == i) {
  5778.                     $(this).addClass('active');
  5779.                 } else {
  5780.                     $(this).removeClass('active');
  5781.                 }
  5782.             });
  5783.  
  5784.             var $pane = $(this.$panes.get(index)),
  5785.                 loadUrl = $pane.data('loadurl');
  5786.  
  5787.             if (loadUrl) {
  5788.                 $pane.data('loadurl', '');
  5789.  
  5790.                 XenForo.ajax(loadUrl, {}, function (ajaxData) {
  5791.                     if (XenForo.hasTemplateHtml(ajaxData) || XenForo.hasTemplateHtml(ajaxData, 'message')) {
  5792.                         new XenForo.ExtLoader(ajaxData, function (ajaxData) {
  5793.                             var $html;
  5794.  
  5795.                             if (ajaxData.templateHtml) {
  5796.                                 $html = $(ajaxData.templateHtml);
  5797.                             } else if (ajaxData.message) {
  5798.                                 $html = $('<div class="section" />').html(ajaxData.message);
  5799.                             }
  5800.  
  5801.                             $pane.html('');
  5802.  
  5803.                             if ($html) {
  5804.                                 $html.xfInsert('appendTo', $pane, 'xfFadeIn', 0);
  5805.                             }
  5806.  
  5807.                             if ($pane.hasClass('Scrollbar')) {
  5808.                                 $pane.scrollbar();
  5809.                             }
  5810.  
  5811.                             if ($html.length > 1) {
  5812.                                 $pane.parent().xfActivate()
  5813.                             }
  5814.  
  5815.                             if ($pane.find('form.InlineModForm').length) {
  5816.                                 XenForo.create('XenForo.InlineModForm', $pane.find('form.InlineModForm'));
  5817.                             }
  5818.  
  5819.                             if (self.useInfiniteScroll) {
  5820.                                 var $status = $('<div class="infinite-scroll-status">\
  5821.                                         <div class="spinner infinite-scroll-request" style="display: none;">\
  5822.                                             <div class="bounce1"></div>\
  5823.                                             <div class="bounce2"></div>\
  5824.                                             <div class="bounce3"></div>\
  5825.                                         </div>\
  5826.                                     </div>').appendTo($pane)
  5827.                                 $pane.find('.alertsPopup > ol').infiniteScroll({
  5828.                                     path: function () {
  5829.                                         return XenForo.canonicalizeUrl(loadUrl + (/\?/.test(loadUrl) ? '&' : '?') + 'page=' + (this.pageIndex + 1))
  5830.                                     },
  5831.                                     status: $status[0],
  5832.                                     append: '.alertsPopup > ol > li',
  5833.                                     xf: true,
  5834.                                     xfAjaxOptions: { global: false },
  5835.                                     xfAjaxDataProcess: function (ajaxData) {
  5836.                                         return ajaxData.templateHtml
  5837.                                     },
  5838.                                     history: false,
  5839.                                     elementScroll: $pane[0]
  5840.                                 })
  5841.                             }
  5842.                         });
  5843.                     } else if (XenForo.hasResponseError(ajaxData)) {
  5844.                         return false;
  5845.                     }
  5846.                 }, { type: 'GET' });
  5847.             }
  5848.         }
  5849.     };
  5850.  
  5851.     // *********************************************************************
  5852.  
  5853.     /**
  5854.      * Handles a like / unlike link being clicked
  5855.      *
  5856.      * @param jQuery a.LikeLink
  5857.      */
  5858.     XenForo.LikeLink = function ($link) {
  5859.         $link.click(function (e) {
  5860.             e.preventDefault();
  5861.  
  5862.             var $link = $(this);
  5863.  
  5864.             XenForo.ajax(this.href, {}, function (ajaxData, textStatus) {
  5865.                 if (XenForo.hasResponseError(ajaxData)) {
  5866.                     return false;
  5867.                 }
  5868.  
  5869.                 $link.stop(true, true);
  5870.  
  5871.                 if (ajaxData.term) // term = Like / Unlike
  5872.                 {
  5873.                     $link.find('.LikeLabel').html(ajaxData.term);
  5874.  
  5875.                     if (ajaxData.cssClasses) {
  5876.                         $.each(ajaxData.cssClasses, function (className, action) {
  5877.                             $link[action === '+' ? 'addClass' : 'removeClass'](className);
  5878.                         });
  5879.                     }
  5880.                 }
  5881.  
  5882.                 const tooltip = $link[0]._tippy;
  5883.                 console.log(tooltip);
  5884.  
  5885.                 if (ajaxData.templateHtml === '') {
  5886.                     if (tooltip) {
  5887.                         tooltip.hide()
  5888.                         setTimeout(() => {
  5889.                             $link.data('XenForo.Tooltip', null);
  5890.                         }, 275)
  5891.                     }
  5892.                 }
  5893.                 else {
  5894.                     if (tooltip) {
  5895.                         tooltip.setContent(ajaxData.templateHtml);
  5896.                         $('.tippy-content').xfActivate();
  5897.                     }
  5898.                     else {
  5899.                         $link.addClass('Tooltip PopupTooltip');
  5900.                         $('<div>').addClass('TooltipContent').html(ajaxData.templateHtml).insertAfter($link);
  5901.                         $link.parent().xfActivate();
  5902.                         $link[0]._tippy.show();
  5903.                         $link[0]._tippy.setContent(ajaxData.templateHtml);
  5904.                         $('.tippy-content').xfActivate();
  5905.                     }
  5906.                 }
  5907.  
  5908.  
  5909.                 /*var id = $link.data('container').substr(1);
  5910.                                  $(document.createElement('div')).attr('id', id).html(ajaxData.templateHtml).hide().appendTo('body');
  5911.                                  $link.data('XenForo.Tooltip', null);
  5912.                                  XenForo.register($link, 'XenForo.Tooltip');
  5913.                                  console.log($link.data());*/
  5914.  
  5915.                 /*if (ajaxData.templateHtml === '') {
  5916.                                         $($link.data('container')).xfFadeUp(XenForo.speed.fast, function () {
  5917.                                                  $(this).empty().xfFadeDown(0);
  5918.                                          });
  5919.                                  } else {
  5920.                                          var $container = $($link.data('container')),
  5921.                                                  $likeText = $container.find('.LikeText'),
  5922.                                                  $templateHtml = $(ajaxData.templateHtml);
  5923.  
  5924.                                          if ($likeText.length) {
  5925.                                                  // we already have the likes_summary template in place, so just replace the text
  5926.                                                  $likeText.xfFadeOut(50, function () {
  5927.                                                          var textContainer = this.parentNode;
  5928.  
  5929.                                                          $(this).remove();
  5930.  
  5931.                                                          $templateHtml.find('.LikeText').xfInsert('appendTo', textContainer, 'xfFadeIn', 50);
  5932.                                                  });
  5933.                                          } else {
  5934.                                                  new XenForo.ExtLoader(ajaxData, function () {
  5935.                                                          $templateHtml.xfInsert('appendTo', $container);
  5936.                                                  });
  5937.                                          }
  5938.                                  }*/
  5939.             });
  5940.         });
  5941.     };
  5942.  
  5943.     // *********************************************************************
  5944.  
  5945.     XenForo.Facebook =
  5946.     {
  5947.         initialized: false,
  5948.         loading: false,
  5949.         parsed: false,
  5950.         appId: '',
  5951.         fbUid: 0,
  5952.         authResponse: {},
  5953.         locale: 'en-US',
  5954.  
  5955.         init: function () {
  5956.             if (XenForo.Facebook.initialized) {
  5957.                 return;
  5958.             }
  5959.             XenForo.Facebook.initialized = true;
  5960.             XenForo.Facebook.loading = false;
  5961.  
  5962.             $(document.body).append($('<div id="fb-root" />'));
  5963.  
  5964.             var fbInfo = {
  5965.                 version: 'v2.10',
  5966.                 xfbml: false,
  5967.                 oauth: true,
  5968.                 channelUrl: XenForo.canonicalizeUrl('fb_channel.php?l=' + XenForo.Facebook.locale)
  5969.             };
  5970.             if (XenForo.Facebook.appId) {
  5971.                 fbInfo.appId = XenForo.Facebook.appId;
  5972.             }
  5973.  
  5974.             FB.init(fbInfo);
  5975.             if (XenForo.Facebook.appId && XenForo.Facebook.fbUid) {
  5976.                 FB.Event.subscribe('auth.authResponseChange', XenForo.Facebook.sessionChange);
  5977.                 FB.getLoginStatus(XenForo.Facebook.sessionChange);
  5978.  
  5979.                 if (XenForo.visitor.user_id) {
  5980.                     $(document).delegate('a.LogOut:not(.OverlayTrigger)', 'click', XenForo.Facebook.eLogOutClick);
  5981.                 }
  5982.             }
  5983.  
  5984.             FB.XFBML.parse(null, function () {
  5985.                 XenForo.Facebook.parsed = true;
  5986.             });
  5987.         },
  5988.  
  5989.         start: function () {
  5990.             var cookieUid = $.getCookie('fbUid');
  5991.             if (cookieUid && cookieUid.length) {
  5992.                 XenForo.Facebook.fbUid = parseInt(cookieUid, 10);
  5993.             }
  5994.  
  5995.             if ($('.fb-post, .fb-video, .fb-page, .fb-like, .fb-share-button').length) {
  5996.                 XenForo.Facebook.forceInit = true;
  5997.             }
  5998.  
  5999.             if (!XenForo.Facebook.forceInit && (!XenForo.Facebook.appId || !XenForo.Facebook.fbUid)) {
  6000.                 return;
  6001.             }
  6002.  
  6003.             XenForo.Facebook.load();
  6004.         },
  6005.  
  6006.         load: function () {
  6007.             if (XenForo.Facebook.parsed) {
  6008.                 FB.XFBML.parse();
  6009.                 return;
  6010.             }
  6011.  
  6012.             if (XenForo.Facebook.loading) {
  6013.                 return;
  6014.             }
  6015.             XenForo.Facebook.loading = true;
  6016.  
  6017.             XenForo.Facebook.locale = $('html').attr('lang').replace('-', '_');
  6018.             if (!XenForo.Facebook.locale) {
  6019.                 XenForo.Facebook.locale = 'en_US';
  6020.             }
  6021.  
  6022.             var e = document.createElement('script'),
  6023.                 locale = XenForo.Facebook.locale.replace('-', '_');
  6024.             e.src = 'https://connect.facebook.net/' + XenForo.Facebook.locale + '/sdk.js';
  6025.             e.async = true;
  6026.  
  6027.             window.fbAsyncInit = XenForo.Facebook.init;
  6028.             document.getElementsByTagName('head')[0].appendChild(e);
  6029.         },
  6030.  
  6031.         sessionChange: function (response) {
  6032.             if (!XenForo.Facebook.fbUid) {
  6033.                 return;
  6034.             }
  6035.  
  6036.             var authResponse = response.authResponse, visitor = XenForo.visitor;
  6037.             XenForo.Facebook.authResponse = authResponse;
  6038.  
  6039.             if (authResponse && !visitor.user_id) {
  6040.                 if (!XenForo._noSocialLogin) {
  6041.                     // facebook user, connect!
  6042.                     XenForo.alert(XenForo.phrases.logging_in + '...', '', 8000);
  6043.                     setTimeout(function () {
  6044.                         XenForo.redirect(
  6045.                             'register/facebook&t=' + escape(authResponse.accessToken)
  6046.                             + '&redirect=' + escape(window.location)
  6047.                         );
  6048.                     }, 250);
  6049.                 }
  6050.             }
  6051.         },
  6052.  
  6053.         logout: function (fbData, returnPage) {
  6054.             var location = $('a.LogOut:not(.OverlayTrigger)').attr('href');
  6055.             if (!location) {
  6056.                 location = 'logout/&_xfToken=' + XenForo._csrfToken;
  6057.             }
  6058.             if (returnPage) {
  6059.                 location += (location.indexOf('?') >= 0 ? '&' : '?') + 'redirect=' + escape(window.location);
  6060.             }
  6061.             XenForo.redirect(location);
  6062.         },
  6063.  
  6064.         eLogOutClick: function (e) {
  6065.             if (XenForo.Facebook.authResponse && XenForo.Facebook.authResponse.userID) {
  6066.                 FB.logout(XenForo.Facebook.logout);
  6067.                 return false;
  6068.             }
  6069.         }
  6070.     };
  6071.  
  6072.     // *********************************************************************
  6073.     /**
  6074.      * Turns an :input into a Prompt
  6075.      *
  6076.      * @param {Object} :input[placeholder]
  6077.      */
  6078.     XenForo.Prompt = function ($input) {
  6079.         this.__construct($input);
  6080.     };
  6081.     if ('placeholder' in document.createElement('input')) {
  6082.         // native placeholder support
  6083.         XenForo.Prompt.prototype =
  6084.         {
  6085.             __construct: function ($input) {
  6086.                 this.$input = $input;
  6087.             },
  6088.  
  6089.             isEmpty: function () {
  6090.                 return (this.$input.strval() === '');
  6091.             },
  6092.  
  6093.             val: function (value, focus) {
  6094.                 if (value === undefined) {
  6095.                     return this.$input.val();
  6096.                 } else {
  6097.                     if (focus) {
  6098.                         this.$input.focus();
  6099.                     }
  6100.  
  6101.                     return this.$input.val(value);
  6102.                 }
  6103.             }
  6104.         };
  6105.     } else {
  6106.         // emulate placeholder support
  6107.         XenForo.Prompt.prototype =
  6108.         {
  6109.             __construct: function ($input) {
  6110.                 console.log('Emulating placeholder behaviour for %o', $input);
  6111.  
  6112.                 this.placeholder = $input.attr('placeholder');
  6113.  
  6114.                 this.$input = $input.bind(
  6115.                     {
  6116.                         focus: $.context(this, 'setValueMode'),
  6117.                         blur: $.context(this, 'setPromptMode')
  6118.                     });
  6119.  
  6120.                 this.$input.closest('form').bind(
  6121.                     {
  6122.                         submit: $.context(this, 'eFormSubmit'),
  6123.                         AutoValidationBeforeSubmit: $.context(this, 'eFormSubmit'),
  6124.                         AutoValidationComplete: $.context(this, 'eFormSubmitted')
  6125.                     });
  6126.  
  6127.                 this.setPromptMode();
  6128.             },
  6129.  
  6130.             /**
  6131.              * If the prompt box contains no text, or contains the prompt text (only) it is 'empty'
  6132.              *
  6133.              * @return boolean
  6134.              */
  6135.             isEmpty: function () {
  6136.                 var val = this.$input.val();
  6137.  
  6138.                 return (val === '' || val == this.placeholder);
  6139.             },
  6140.  
  6141.             /**
  6142.              * When exiting the prompt box, update its contents if necessary
  6143.              */
  6144.             setPromptMode: function () {
  6145.                 if (this.isEmpty()) {
  6146.                     this.$input.val(this.placeholder).addClass('prompt');
  6147.                 }
  6148.             },
  6149.  
  6150.             /**
  6151.              * When entering the prompt box, clear its contents if it is 'empty'
  6152.              */
  6153.             setValueMode: function () {
  6154.                 if (this.isEmpty()) {
  6155.                     this.$input.val('').removeClass('prompt').select();
  6156.                 }
  6157.             },
  6158.  
  6159.             /**
  6160.              * Gets or sets the value of the prompt and puts it into the correct mode for its contents
  6161.              *
  6162.              * @param string value
  6163.              */
  6164.             val: function (value, focus) {
  6165.                 // get value
  6166.                 if (value === undefined) {
  6167.                     if (this.isEmpty()) {
  6168.                         return '';
  6169.                     } else {
  6170.                         return this.$input.val();
  6171.                     }
  6172.                 }
  6173.  
  6174.                 // clear value
  6175.                 else if (value === '') {
  6176.                     this.$input.val('');
  6177.  
  6178.                     if (focus === undefined) {
  6179.                         this.setPromptMode();
  6180.                     }
  6181.                 }
  6182.  
  6183.                 // set value
  6184.                 else {
  6185.                     this.setValueMode();
  6186.                     this.$input.val(value);
  6187.                 }
  6188.             },
  6189.  
  6190.             /**
  6191.              * When the form is submitted, empty the prompt box if it is 'empty'
  6192.              *
  6193.              * @return boolean true;
  6194.              */
  6195.             eFormSubmit: function () {
  6196.                 if (this.isEmpty()) {
  6197.                     this.$input.val('');
  6198.                 }
  6199.  
  6200.                 return true;
  6201.             },
  6202.  
  6203.             /**
  6204.              * Fires immediately after the form has sent its AJAX submission
  6205.              */
  6206.             eFormSubmitted: function () {
  6207.                 this.setPromptMode();
  6208.             }
  6209.         };
  6210.     }
  6211.     ;
  6212.  
  6213.     // *********************************************************************
  6214.  
  6215.     /**
  6216.      * Turn in input:text.SpinBox into a Spin Box
  6217.      * Requires a parameter class of 'SpinBox' and an attribute of 'data-step' with a numeric step value.
  6218.      * data-max and data-min parameters are optional.
  6219.      *
  6220.      * @param {Object} $input
  6221.      */
  6222.     XenForo.SpinBox = function ($input) {
  6223.         this.__construct($input);
  6224.     };
  6225.     XenForo.SpinBox.prototype =
  6226.     {
  6227.         __construct: function ($input) {
  6228.             var param,
  6229.                 inputWidth,
  6230.                 $plusButton,
  6231.                 $minusButton;
  6232.  
  6233.             if ($input.attr('step') === undefined) {
  6234.                 console.warn('ERROR: No data-step attribute specified for spinbox.');
  6235.                 return;
  6236.             }
  6237.  
  6238.             this.parameters = { step: null, min: null, max: null };
  6239.  
  6240.             for (param in this.parameters) {
  6241.                 if ($input.attr(param) === undefined) {
  6242.                     delete this.parameters[param];
  6243.                 } else {
  6244.                     this.parameters[param] = parseFloat($input.attr(param));
  6245.                 }
  6246.             }
  6247.  
  6248.             inputWidth = $input.width();
  6249.  
  6250.             $plusButton = $('<input type="button" class="button spinBoxButton up" value="+" data-plusminus="+" tabindex="-1" />')
  6251.                 .insertAfter($input)
  6252.                 .focus($.context(this, 'eFocusButton'))
  6253.                 .click($.context(this, 'eClickButton'))
  6254.                 .mouseenter($.context(this, 'eMouseEnter'))
  6255.                 .mousedown($.context(this, 'eMousedownButton'))
  6256.                 .on('mouseleave mouseup', $.context(this, 'eMouseupButton'));
  6257.             $minusButton = $('<input type="button" class="button spinBoxButton down" value="-" data-plusminus="-" tabindex="-1" />')
  6258.                 .insertAfter($plusButton)
  6259.                 .focus($.context(this, 'eFocusButton'))
  6260.                 .click($.context(this, 'eClickButton'))
  6261.                 .mouseenter($.context(this, 'eMouseEnter'))
  6262.                 .mousedown($.context(this, 'eMousedownButton'))
  6263.                 .on('mouseleave mouseup', $.context(this, 'eMouseupButton'));
  6264.  
  6265.             // set up the input
  6266.             this.$input = $input
  6267.                 .attr('autocomplete', 'off')
  6268.                 .blur($.context(this, 'eBlurInput'))
  6269.                 .keyup($.context(this, 'eKeyupInput'));
  6270.  
  6271.             // force validation to occur on form submit
  6272.             this.$input.closest('form').bind('submit', $.context(this, 'eBlurInput'));
  6273.  
  6274.             // initial constraint
  6275.             this.$input.val(this.constrain(this.getValue()));
  6276.  
  6277.             this.mouseTarget = null;
  6278.         },
  6279.  
  6280.         /**
  6281.          * Returns the (numeric) value of the spinbox
  6282.          *
  6283.          * @return float
  6284.          */
  6285.         getValue: function () {
  6286.             var value = parseFloat(this.$input.val());
  6287.  
  6288.             value = (isNaN(value)) ? parseFloat(this.$input.val().replace(/[^0-9.]/g, '')) : value;
  6289.  
  6290.             return (isNaN(value) ? 0 : value);
  6291.         },
  6292.  
  6293.         /**
  6294.          * Asserts that the value of the spinbox is within defined min and max parameters.
  6295.          *
  6296.          * @param float Spinbox value
  6297.          *
  6298.          * @return float
  6299.          */
  6300.         constrain: function (value) {
  6301.             if (this.parameters.min !== undefined && value < this.parameters.min) {
  6302.                 console.warn('Minimum value for SpinBox = %s\n %o', this.parameters.min, this.$input);
  6303.                 return this.parameters.min;
  6304.             } else if (this.parameters.max !== undefined && value > this.parameters.max) {
  6305.                 console.warn('Maximum value for SpinBox = %s\n %o', this.parameters.max, this.$input);
  6306.                 return this.parameters.max;
  6307.             } else {
  6308.                 return value;
  6309.             }
  6310.         },
  6311.  
  6312.         /**
  6313.          * Takes the value of the SpinBox input to the nearest step.
  6314.          *
  6315.          * @param string +/- Take the value up or down
  6316.          */
  6317.         stepValue: function (plusMinus) {
  6318.             if (this.$input.prop('readonly')) {
  6319.                 return false;
  6320.             }
  6321.  
  6322.             var val = this.getValue(),
  6323.                 mod = val % this.parameters.step,
  6324.                 posStep = (plusMinus == '+'),
  6325.                 newVal = val - mod;
  6326.  
  6327.             if (!mod || (posStep && mod > 0) || (!posStep && mod < 0)) {
  6328.                 newVal = newVal + this.parameters.step * (posStep ? 1 : -1);
  6329.             }
  6330.  
  6331.             this.$input.val(this.constrain(newVal));
  6332.             this.$input.triggerHandler('change');
  6333.         },
  6334.  
  6335.         /**
  6336.          * Handles the input being blurred. Removes the 'pseudofocus' class and constrains the spinbox value.
  6337.          *
  6338.          * @param Event e
  6339.          */
  6340.         eBlurInput: function (e) {
  6341.             this.$input.val(this.constrain(this.getValue()));
  6342.         },
  6343.  
  6344.         /**
  6345.          * Handles key events on the spinbox input. Up and down arrows perform a value step.
  6346.          *
  6347.          * @param Event e
  6348.          *
  6349.          * @return false|undefined
  6350.          */
  6351.         eKeyupInput: function (e) {
  6352.             switch (e.which) {
  6353.                 case 38: // up
  6354.                     {
  6355.                         this.stepValue('+');
  6356.                         this.$input.select();
  6357.                         return false;
  6358.                     }
  6359.  
  6360.                 case 40: // down
  6361.                     {
  6362.                         this.stepValue('-');
  6363.                         this.$input.select();
  6364.                         return false;
  6365.                     }
  6366.             }
  6367.         },
  6368.  
  6369.         /**
  6370.          * Handles focus events on spinbox buttons.
  6371.          *
  6372.          * Does not allow buttons to keep focus, returns focus to the input.
  6373.          *
  6374.          * @param Event e
  6375.          *
  6376.          * @return boolean false
  6377.          */
  6378.         eFocusButton: function (e) {
  6379.             return false;
  6380.         },
  6381.  
  6382.         /**
  6383.          * Handles click events on spinbox buttons.
  6384.          *
  6385.          * The buttons are assumed to have data-plusMinus attributes of + or -
  6386.          *
  6387.          * @param Event e
  6388.          */
  6389.         eClickButton: function (e) {
  6390.             this.stepValue($(e.target).data('plusminus'));
  6391.             this.$input.focus();
  6392.             this.$input.select();
  6393.         },
  6394.  
  6395.         /**
  6396.          * Handles a mouse-down event on a spinbox button in order to allow rapid repeats.
  6397.          *
  6398.          * @param Event e
  6399.          */
  6400.         eMousedownButton: function (e) {
  6401.             this.eMouseupButton(e); // don't orphan
  6402.             this.mouseTarget = e.target;
  6403.  
  6404.             this.holdTimeout = setTimeout(
  6405.                 $.context(function () {
  6406.                     this.holdInterval = setInterval($.context(function () {
  6407.                         this.stepValue(e.target.value);
  6408.                     }, this), 75);
  6409.                 }, this
  6410.                 ), 500);
  6411.         },
  6412.  
  6413.         /**
  6414.          * Handles re-entering while holding the mouse.
  6415.          *
  6416.          * @param e
  6417.          */
  6418.         eMouseEnter: function (e) {
  6419.             if (e.which && e.target == this.mouseTarget) {
  6420.                 this.holdInterval = setInterval($.context(function () {
  6421.                     this.stepValue(e.target.value);
  6422.                 }, this), 75);
  6423.             }
  6424.         },
  6425.  
  6426.         /**
  6427.          * Handles a mouse-up event on a spinbox button in order to halt rapid repeats.
  6428.          *
  6429.          * @param Event e
  6430.          */
  6431.         eMouseupButton: function (e) {
  6432.             clearTimeout(this.holdTimeout);
  6433.             clearInterval(this.holdInterval);
  6434.             if (e.type == 'mouseup') {
  6435.                 this.mouseTarget = null;
  6436.             }
  6437.         }
  6438.     };
  6439.  
  6440.     // *********************************************************************
  6441.  
  6442.     /**
  6443.      * Allows an input:checkbox or input:radio to disable subsidiary controls
  6444.      * based on its own state
  6445.      *
  6446.      * @param {Object} $input
  6447.      */
  6448.     XenForo.Disabler = function ($input) {
  6449.         /**
  6450.          * Sets the disabled state of form elements being controlled by this disabler.
  6451.          *
  6452.          * @param Event e
  6453.          * @param boolean If true, this is the initialization call
  6454.          */
  6455.         var setStatus = function (e, init) {
  6456.             //console.info('Disabler %o for child container: %o', $input, $childContainer);
  6457.  
  6458.             var $childControls = $childContainer.find('input, select, textarea, button, .inputWrapper, .taggingInput'),
  6459.                 speed = init ? 0 : XenForo.speed.fast,
  6460.                 select = function (e) {
  6461.                     $childContainer.find('input:not([type=hidden], [type=file]), textarea, select, button').first().focus().select();
  6462.                 };
  6463.  
  6464.             if ($input.is(':checked:enabled')) {
  6465.                 $childContainer
  6466.                     .removeAttr('disabled')
  6467.                     .removeClass('disabled')
  6468.                     .trigger('DisablerDisabled');
  6469.  
  6470.                 $childControls
  6471.                     .removeAttr('disabled')
  6472.                     .removeClass('disabled');
  6473.  
  6474.                 if ($input.hasClass('Hider')) {
  6475.                     if (init) {
  6476.                         $childContainer.show();
  6477.                     } else {
  6478.                         $childContainer.xfFadeDown(speed, init ? null : select);
  6479.                     }
  6480.                 } else if (!init) {
  6481.                     select.call();
  6482.                 }
  6483.             } else {
  6484.                 if ($input.hasClass('Hider')) {
  6485.                     if (init) {
  6486.                         $childContainer.hide();
  6487.                     } else {
  6488.                         $childContainer.xfFadeUp(speed, null, speed, 'easeInBack');
  6489.                     }
  6490.                 }
  6491.  
  6492.                 $childContainer
  6493.                     .prop('disabled', true)
  6494.                     .addClass('disabled')
  6495.                     .trigger('DisablerEnabled');
  6496.  
  6497.                 $childControls
  6498.                     .prop('disabled', true)
  6499.                     .addClass('disabled')
  6500.                     .each(function (i, ctrl) {
  6501.                         var $ctrl = $(ctrl),
  6502.                             disabledVal = $ctrl.data('disabled');
  6503.  
  6504.                         if (disabledVal !== null && typeof (disabledVal) != 'undefined') {
  6505.                             $ctrl.val(disabledVal);
  6506.                         }
  6507.                     });
  6508.             }
  6509.         },
  6510.  
  6511.             $childContainer = $(
  6512.                 '#' + $input.attr('id') + '_Disabler' + (
  6513.                     $input.data('disabler-target') ? ', ' + $input.data('disabler-target') : ''
  6514.                 )
  6515.             ),
  6516.             $form = $input.closest('form');
  6517.  
  6518.         var setStatusDelayed = function () {
  6519.             setTimeout(setStatus, 0);
  6520.         };
  6521.  
  6522.         if ($input.is(':radio')) {
  6523.             $form.find('input:radio[name="' + $input.fieldName() + '"]').click(setStatusDelayed);
  6524.         } else {
  6525.             $input.click(setStatusDelayed);
  6526.         }
  6527.  
  6528.         $form.bind('reset', setStatusDelayed);
  6529.         $form.bind('XFRecalculate', function () {
  6530.             setStatus(null, true);
  6531.         });
  6532.  
  6533.         setStatus(null, true);
  6534.  
  6535.         $childContainer.find('label, input, select, textarea').click(function (e) {
  6536.             if (!$input.is(':checked')) {
  6537.                 $input.prop('checked', true);
  6538.                 setStatus();
  6539.             }
  6540.         });
  6541.  
  6542.         this.setStatus = setStatus;
  6543.     };
  6544.  
  6545.     // *********************************************************************
  6546.  
  6547.     /**
  6548.      * Quick way to check or toggle all specified items. Works in one of two ways:
  6549.      * 1) If the control is a checkbox, a data-target attribute specified a jQuery
  6550.      *    selector for a container within which all checkboxes will be toggled
  6551.      * 2) If the control is something else, the data-target attribute specifies a
  6552.      *    jQuery selector for the elements themselves that will be selected.
  6553.      *
  6554.      *  @param jQuery .CheckAll
  6555.      */
  6556.     XenForo.CheckAll = function ($control) {
  6557.         if ($control.is(':checkbox')) {
  6558.             var $target = $control.data('target') ? $($control.data('target')) : false;
  6559.             if (!$target || !$target.length) {
  6560.                 $target = $control.closest('form');
  6561.             }
  6562.  
  6563.             var getCheckBoxes = function () {
  6564.                 var $checkboxes,
  6565.                     filter = $control.data('filter');
  6566.  
  6567.                 $checkboxes = filter
  6568.                     ? $target.find(filter).filter('input:checkbox')
  6569.                     : $target.find('input:checkbox');
  6570.  
  6571.                 return $checkboxes;
  6572.             };
  6573.  
  6574.             var setSelectAllState = function () {
  6575.                 var $checkboxes = getCheckBoxes(),
  6576.                     allSelected = $checkboxes.length > 0;
  6577.  
  6578.                 $checkboxes.each(function () {
  6579.                     if ($(this).is($control)) {
  6580.                         return true;
  6581.                     }
  6582.  
  6583.                     if (!$(this).prop('checked')) {
  6584.                         allSelected = false;
  6585.                         return false;
  6586.                     }
  6587.                 });
  6588.  
  6589.                 $control.prop('checked', allSelected);
  6590.             };
  6591.             setSelectAllState();
  6592.  
  6593.             var toggleAllRunning = false;
  6594.  
  6595.             $target.on('click', 'input:checkbox', function (e) {
  6596.                 if (toggleAllRunning) {
  6597.                     return;
  6598.                 }
  6599.  
  6600.                 var $target = $(e.target);
  6601.                 if ($target.is($control)) {
  6602.                     return;
  6603.                 }
  6604.  
  6605.                 if ($control.data('filter')) {
  6606.                     if (!$target.closest($control.data('filter')).length) {
  6607.                         return;
  6608.                     }
  6609.                 }
  6610.  
  6611.                 setSelectAllState();
  6612.             });
  6613.  
  6614.             $control.click(function (e) {
  6615.                 if (toggleAllRunning) {
  6616.                     return;
  6617.                 }
  6618.  
  6619.                 toggleAllRunning = true;
  6620.                 getCheckBoxes().prop('checked', e.target.checked).triggerHandler('click');
  6621.                 toggleAllRunning = false;
  6622.             });
  6623.  
  6624.             $control.on('XFRecalculate', setSelectAllState);
  6625.         } else {
  6626.             $control.click(function (e) {
  6627.                 var target = $control.data('target');
  6628.  
  6629.                 if (target) {
  6630.                     $(target).prop('checked', true);
  6631.                 }
  6632.             });
  6633.         }
  6634.     };
  6635.  
  6636.     // *********************************************************************
  6637.  
  6638.     /**
  6639.      * Method to allow an input (usually a checkbox) to alter the selection of others.
  6640.      * When checking the target checkbox, it will also check any controls matching data-check
  6641.      * and un-check any controls matching data-uncheck
  6642.      *
  6643.      * @param jQuery input.AutoChecker[data-check, data-uncheck]
  6644.      */
  6645.     XenForo.AutoChecker = function ($control) {
  6646.         $control.click(function (e) {
  6647.             if (this.checked) {
  6648.                 var selector = null;
  6649.  
  6650.                 $.each({ check: true, uncheck: false }, function (dataField, checkState) {
  6651.                     if (selector = $control.data(dataField)) {
  6652.                         $(selector).each(function () {
  6653.                             this.checked = checkState;
  6654.  
  6655.                             var Disabler = $(this).data('XenForo.Disabler');
  6656.  
  6657.                             if (typeof Disabler == 'object') {
  6658.                                 Disabler.setStatus();
  6659.                             }
  6660.                         });
  6661.                     }
  6662.                 });
  6663.             }
  6664.         });
  6665.     };
  6666.  
  6667.     // *********************************************************************
  6668.  
  6669.     /**
  6670.      * Converts a checkbox/radio plus label into a toggle button.
  6671.      *
  6672.      * @param jQuery label.ToggleButton
  6673.      */
  6674.     XenForo.ToggleButton = function ($label) {
  6675.         var $button,
  6676.  
  6677.             setCheckedClasses = function () {
  6678.                 $button[($input.is(':checked') ? 'addClass' : 'removeClass')]('checked');
  6679.             },
  6680.  
  6681.             $input = $label.hide().find('input:checkbox, input:radio').first(),
  6682.  
  6683.             $list = $label.closest('ul, ol').bind('toggleButtonClick', setCheckedClasses);
  6684.  
  6685.         if (!$input.length && $label.attr('for')) {
  6686.             $input = $('#' + $label.attr('for'));
  6687.         }
  6688.  
  6689.         $button = $('<a />')
  6690.             .text($label.attr('title') || $label.text())
  6691.             .insertBefore($label)
  6692.             .attr(
  6693.                 {
  6694.                     'class': 'button ' + $label.attr('class'),
  6695.                     'title': $label.text()
  6696.                 })
  6697.             .click(function (e) {
  6698.                 $input.click();
  6699.  
  6700.                 if ($list.length) {
  6701.                     $list.triggerHandler('toggleButtonClick');
  6702.                 } else {
  6703.                     setCheckedClasses();
  6704.                 }
  6705.  
  6706.                 return false;
  6707.             });
  6708.  
  6709.         $label.closest('form').bind('reset', function (e) {
  6710.             setTimeout(setCheckedClasses, 100);
  6711.         });
  6712.  
  6713.         setCheckedClasses();
  6714.     };
  6715.  
  6716.     // *********************************************************************
  6717.  
  6718.     /**
  6719.      * Allows files to be uploaded in-place without a page refresh
  6720.      *
  6721.      * @param jQuery form.AutoInlineUploader
  6722.      */
  6723.     /**
  6724.      * Allows files to be uploaded in-place without a page refresh
  6725.      *
  6726.      * @param jQuery form.AutoInlineUploader
  6727.      */
  6728.     XenForo.AutoInlineUploader = function ($form) {
  6729.         /**
  6730.          * Fires when the contents of an input:file change.
  6731.          * Submits the form into a temporary iframe.
  6732.          *
  6733.          * @param event e
  6734.          */
  6735.         var $uploader = $form.find('input:file').each(function () {
  6736.             var $target = $(this).change(function (e) {
  6737.                 if ($(e.target).val() != '') {
  6738.                     var $iframe,
  6739.                         $hiddenInput;
  6740.                     $('.bootstrap-filestyle').find('label').changeElementType('fake_label');
  6741.                     $iframe = $('<iframe src="about:blank" style="display:none; background-color: white" name="AutoInlineUploader"></iframe>')
  6742.                         .insertAfter($(e.target))
  6743.                         .load(function (e) {
  6744.                             $('.bootstrap-filestyle').find('fake_label').changeElementType('label');
  6745.                             var $iframe = $(e.target);
  6746.                             $iframe.contents().find('style').remove();
  6747.                             var ajaxData = $iframe.contents().text(),
  6748.                                 eComplete = null;
  6749.  
  6750.                             // Opera fires this function when it's not done with no data
  6751.                             if (!ajaxData) {
  6752.                                 return false;
  6753.                             }
  6754.  
  6755.                             // alert the global progress indicator that the transfer is complete
  6756.                             $(document).trigger('PseudoAjaxStop');
  6757.  
  6758.                             $uploader = $uploaderOrig.clone(true).replaceAll($target);
  6759.  
  6760.                             // removing the iframe after a delay to prevent Firefox' progress indicator staying active
  6761.                             setTimeout(function () {
  6762.                                 $iframe.remove();
  6763.                             }, 500);
  6764.  
  6765.                             try {
  6766.                                 ajaxData = $.parseJSON(ajaxData);
  6767.                                 console.info('Inline file upload completed successfully. Data: %o', ajaxData);
  6768.                             } catch (e) {
  6769.                                 console.error(ajaxData);
  6770.                                 return false;
  6771.                             }
  6772.  
  6773.                             if (XenForo.hasResponseError(ajaxData)) {
  6774.                                 return false;
  6775.                             }
  6776.  
  6777.                             $('input:submit', this.$form).removeAttr('disabled');
  6778.  
  6779.                             eComplete = new $.Event('AutoInlineUploadComplete');
  6780.                             eComplete.$form = $form;
  6781.                             eComplete.ajaxData = ajaxData;
  6782.  
  6783.                             $form.trigger(eComplete);
  6784.  
  6785.                             $('#ctrl_avatar').attr('style', 'position: absolute; clip: rect(0px, 0px, 0px, 0px);');
  6786.                             $form.xfActivate();
  6787.                             if (!eComplete.isDefaultPrevented() && (ajaxData.message || ajaxData._redirectMessage)) {
  6788.                                 XenForo.alert(ajaxData.message || ajaxData._redirectMessage, '', 2500);
  6789.                             }
  6790.                         });
  6791.  
  6792.                     $hiddenInput = $('<span>'
  6793.                         + '<input type="hidden" name="_xfNoRedirect" value="1" />'
  6794.                         + '<input type="hidden" name="_xfResponseType" value="json-text" />'
  6795.                         + '<input type="hidden" name="_xfUploader" value="1" />'
  6796.                         + '</span>')
  6797.                         .appendTo($form);
  6798.  
  6799.                     $form.attr('target', 'AutoInlineUploader')
  6800.                         .submit()
  6801.                         .trigger('AutoInlineUploadStart');
  6802.  
  6803.                     $hiddenInput.remove();
  6804.  
  6805.                     // fire the event that will be caught by the global progress indicator
  6806.                     $(document).trigger('PseudoAjaxStart');
  6807.  
  6808.                     $form.find('input:submit').prop('disabled', true);
  6809.                 }
  6810.             }),
  6811.  
  6812.                 $uploaderOrig = $target.clone(true);
  6813.         });
  6814.     };
  6815.  
  6816.     // *********************************************************************
  6817.  
  6818.     XenForo.MultiSubmitFix = function ($form) {
  6819.         const selector = 'input:submit, input:reset, input.PreviewButton, input.DisableOnSubmit',
  6820.             enable = function () {
  6821.                 $(window).unbind('pagehide', enable);
  6822.                 $(document).unbind('ajaxStop', enable)
  6823.  
  6824.                 $form.trigger('EnableSubmitButtons').find(selector)
  6825.                     .removeClass('disabled')
  6826.                     .removeAttr('disabled');
  6827.             },
  6828.             disable = function () {
  6829.                 setTimeout(function () {
  6830.                     /**
  6831.                      * Workaround for a Firefox issue that prevents resubmission after back button,
  6832.                      * however the workaround triggers a webkit rendering bug.
  6833.                      */
  6834.                     if (!$.browser.webkit) {
  6835.                         $(window).bind('pagehide', enable);
  6836.                     }
  6837.  
  6838.                     $form.trigger('DisableSubmitButtons').find(selector)
  6839.                         .prop('disabled', true)
  6840.                         .addClass('disabled');
  6841.                 }, 0);
  6842.  
  6843.                 $(document).one('ajaxStop', function () {
  6844.                     setTimeout(enable, 3000);
  6845.                 });
  6846.             };
  6847.  
  6848.         $form.data('MultiSubmitEnable', enable)
  6849.             .data('MultiSubmitDisable', disable)
  6850.             .submit(disable);
  6851.  
  6852.         return enable;
  6853.     };
  6854.  
  6855.     // *********************************************************************
  6856.  
  6857.     /**
  6858.      * Handler for radio/checkbox controls that cause the form to submit when they are altered
  6859.      *
  6860.      * @param jQuery input:radio.SubmitOnChange, input:checkbox.SubmitOnChange, label.SubmitOnChange
  6861.      */
  6862.     XenForo.SubmitOnChange = function ($input) {
  6863.         if ($input.is('label')) {
  6864.             $input = $input.find('input:radio, input:checkbox');
  6865.             if (!$input.length) {
  6866.                 return;
  6867.             }
  6868.         }
  6869.  
  6870.         $input.click(function (e) {
  6871.             clearTimeout(e.target.form.submitTimeout);
  6872.  
  6873.             e.target.form.submitTimeout = setTimeout(function () {
  6874.                 $(e.target).closest('form').submit();
  6875.             }, 500);
  6876.         });
  6877.     };
  6878.  
  6879.     // *********************************************************************
  6880.  
  6881.     /**
  6882.      * Handler for automatic AJAX form validation and error management
  6883.      *
  6884.      * Forms to be auto-validated require the following attributes:
  6885.      *
  6886.      * * data-fieldValidatorUrl: URL of a JSON-returning validator for a single field, using _POST keys of 'name' and 'value'
  6887.      * * data-optInOut: (Optional - default = OptOut) Either OptIn or OptOut, depending on the validation mode. Fields with a class of OptIn are included in opt-in mode, while those with OptOut are excluded in opt-out mode.
  6888.      * * data-exitUrl: (Optional - no default) If defined, any form reset event will redirect to this URL.
  6889.      * * data-existingDataKey: (Optional) Specifies the primary key of the data being manipulated. If this is not present, a hidden input with class="ExistingDataKey" is searched for.
  6890.      * * data-redirect: (Optional) If set, the browser will redirect to the returned _redirectTarget from the ajaxData response after validation
  6891.      *
  6892.      * @param jQuery form.AutoValidator
  6893.      */
  6894.     XenForo.AutoValidator = function ($form) {
  6895.         this.__construct($form);
  6896.     };
  6897.     XenForo.AutoValidator.prototype =
  6898.     {
  6899.         __construct: function ($form) {
  6900.             this.$form = $form.bind(
  6901.                 {
  6902.                     submit: $.context(this, 'ajaxSave'),
  6903.                     reset: $.context(this, 'formReset'),
  6904.                     BbCodeWysiwygEditorAutoSave: $.context(this, 'editorAutoSave')
  6905.                 });
  6906.  
  6907.             this.$form.find('input[type="submit"]').click($.context(this, 'setClickedSubmit'));
  6908.  
  6909.             this.fieldValidatorUrl = this.$form.data('fieldvalidatorurl');
  6910.             this.optInMode = this.$form.data('optinout') || 'OptOut';
  6911.             this.ajaxSubmit = (XenForo.isPositive(this.$form.data('normalsubmit')) ? false : true);
  6912.             this.submitPending = false;
  6913.  
  6914.             this.fieldValidationTimeouts = {};
  6915.             this.fieldValidationRequests = {};
  6916.         },
  6917.  
  6918.         /**
  6919.          * Fetches the value of the form's existing data key.
  6920.          *
  6921.          * This could either be a data-existingDataKey attribute on the form itself,
  6922.          * or a hidden input with class 'ExistingDataKey'
  6923.          *
  6924.          * @return string
  6925.          */
  6926.         getExistingDataKey: function () {
  6927.             var val = this.$form.find('input.ExistingDataKey, select.ExistingDataKey, textarea.ExistingDataKey, button.ExistingDataKey').val();
  6928.             if (val === undefined) {
  6929.                 val = this.$form.data('existingdatakey');
  6930.                 if (val === undefined) {
  6931.                     val = '';
  6932.                 }
  6933.             }
  6934.  
  6935.             return val;
  6936.         },
  6937.  
  6938.         /**
  6939.          * Intercepts form reset events.
  6940.          * If the form specifies a data-exitUrl, the browser will navigate there before resetting the form.
  6941.          *
  6942.          * @param event e
  6943.          */
  6944.         formReset: function (e) {
  6945.             var exitUrl = this.$form.data('exiturl');
  6946.  
  6947.             if (exitUrl) {
  6948.                 XenForo.redirect(exitUrl);
  6949.             }
  6950.         },
  6951.  
  6952.         /**
  6953.          * Fires whenever a submit button is clicked, in order to store the clicked control
  6954.          *
  6955.          * @param event e
  6956.          */
  6957.         setClickedSubmit: function (e) {
  6958.             this.$form.data('clickedsubmitbutton', e.target);
  6959.         },
  6960.  
  6961.         editorAutoSave: function (e) {
  6962.             if (this.submitPending) {
  6963.                 e.preventDefault();
  6964.             }
  6965.         },
  6966.  
  6967.         /**
  6968.          * Intercepts form submit events.
  6969.          * Attempts to save the form with AJAX, after cancelling any pending validation tasks.
  6970.          *
  6971.          * @param event e
  6972.          *
  6973.          * @return boolean false
  6974.          */
  6975.         ajaxSave: function (e) {
  6976.             if (!this.ajaxSubmit || !XenForo._enableAjaxSubmit) {
  6977.                 // do normal validation
  6978.                 return true;
  6979.             }
  6980.  
  6981.             this.abortPendingFieldValidation();
  6982.  
  6983.             var clickedSubmitButton = this.$form.data('clickedsubmitbutton'),
  6984.                 serialized,
  6985.                 $clickedSubmitButton,
  6986.  
  6987.                 /**
  6988.                  * Event listeners for this event can:
  6989.                  *    e.preventSubmit = true; to prevent any submission
  6990.                  *    e.preventDefault(); to disable ajax sending
  6991.                  */
  6992.                 eDataSend = $.Event('AutoValidationBeforeSubmit');
  6993.             eDataSend.formAction = this.$form.attr('action');
  6994.             eDataSend.clickedSubmitButton = clickedSubmitButton;
  6995.             eDataSend.preventSubmit = false;
  6996.             eDataSend.ajaxOptions = {};
  6997.  
  6998.             this.$form.trigger(eDataSend);
  6999.  
  7000.             this.$form.removeData('clickedSubmitButton');
  7001.  
  7002.             if (eDataSend.preventSubmit) {
  7003.                 return false;
  7004.             } else if (!eDataSend.isDefaultPrevented()) {
  7005.                 serialized = this.$form.serializeArray();
  7006.                 if (clickedSubmitButton) {
  7007.                     $clickedSubmitButton = $(clickedSubmitButton);
  7008.                     if ($clickedSubmitButton.attr('name')) {
  7009.                         serialized.push({
  7010.                             name: $clickedSubmitButton.attr('name'),
  7011.                             value: $clickedSubmitButton.attr('value')
  7012.                         });
  7013.                     }
  7014.                 }
  7015.  
  7016.                 this.submitPending = true;
  7017.  
  7018.                 XenForo.ajax(
  7019.                     eDataSend.formAction,
  7020.                     serialized,
  7021.                     $.context(this, 'ajaxSaveResponse'),
  7022.                     eDataSend.ajaxOptions
  7023.                 );
  7024.  
  7025.                 e.preventDefault();
  7026.             }
  7027.         },
  7028.  
  7029.         /**
  7030.          * Handles the AJAX response from ajaxSave().
  7031.          *
  7032.          * @param ajaxData
  7033.          * @param textStatus
  7034.          * @return
  7035.          */
  7036.         ajaxSaveResponse: function (ajaxData, textStatus) {
  7037.             this.submitPending = false;
  7038.  
  7039.             if (!ajaxData) {
  7040.                 console.warn('No ajax data returned.');
  7041.                 return false;
  7042.             }
  7043.  
  7044.             var eDataRecv,
  7045.                 eError,
  7046.                 eComplete,
  7047.                 $trigger;
  7048.  
  7049.             eDataRecv = $.Event('AutoValidationDataReceived');
  7050.             eDataRecv.ajaxData = ajaxData;
  7051.             eDataRecv.textStatus = textStatus;
  7052.             eDataRecv.validationError = [];
  7053.             console.group('Event: %s', eDataRecv.type);
  7054.             this.$form.trigger(eDataRecv);
  7055.             console.groupEnd();
  7056.             if (eDataRecv.isDefaultPrevented()) {
  7057.                 return false;
  7058.             }
  7059.  
  7060.             // if the submission has failed validation, show the error overlay
  7061.             if (!this.validates(eDataRecv)) {
  7062.                 eError = $.Event('AutoValidationError');
  7063.                 eError.ajaxData = ajaxData;
  7064.                 eError.textStatus = textStatus;
  7065.                 eError.validationError = eDataRecv.validationError;
  7066.                 console.group('Event: %s', eError.type);
  7067.                 this.$form.trigger(eError);
  7068.                 console.groupEnd();
  7069.                 if (eError.isDefaultPrevented()) {
  7070.                     return false;
  7071.                 }
  7072.  
  7073.                 if (this.$form.closest('.xenOverlay').length) {
  7074.                     this.$form.closest('.xenOverlay').data('overlay').close();
  7075.                 }
  7076.  
  7077.                 if (ajaxData.errorTemplateHtml) {
  7078.                     new XenForo.ExtLoader(ajaxData, function (data) {
  7079.                         var $overlayHtml = XenForo.alert(
  7080.                             ajaxData.errorTemplateHtml,
  7081.                             XenForo.phrases.following_error_occurred + ':'
  7082.                         );
  7083.                         if ($overlayHtml) {
  7084.                             $overlayHtml.find('div.errorDetails').removeClass('baseHtml');
  7085.                             if (ajaxData.errorOverlayType) {
  7086.                                 $overlayHtml.closest('.errorOverlay').removeClass('errorOverlay').addClass(ajaxData.errorOverlayType);
  7087.                             }
  7088.                         }
  7089.                     });
  7090.                 } else if (ajaxData.templateHtml) {
  7091.                     setTimeout($.context(function () {
  7092.                         this.$error = XenForo.createOverlay(null, this.prepareError(ajaxData.templateHtml)).load();
  7093.                     }, this), 250);
  7094.                 } else if (ajaxData.error !== undefined) {
  7095.                     if (typeof ajaxData.error === 'object') {
  7096.                         var key;
  7097.                         for (key in ajaxData.error) {
  7098.                             break;
  7099.                         }
  7100.                         ajaxData.error = ajaxData.error[key];
  7101.                     }
  7102.  
  7103.                     XenForo.alert(
  7104.                         ajaxData.error + '\n'
  7105.                         + (ajaxData.traceHtml !== undefined ? '<ol class="traceHtml">\n' + ajaxData.traceHtml + '</ol>' : ''),
  7106.                         XenForo.phrases.following_error_occurred + ':'
  7107.                     );
  7108.                 }
  7109.  
  7110.                 return false;
  7111.             }
  7112.  
  7113.             eComplete = $.Event('AutoValidationComplete'),
  7114.                 eComplete.ajaxData = ajaxData;
  7115.             eComplete.textStatus = textStatus;
  7116.             eComplete.$form = this.$form;
  7117.             console.group('Event: %s', eComplete.type);
  7118.             this.$form.trigger(eComplete);
  7119.             console.groupEnd();
  7120.             if (eComplete.isDefaultPrevented()) {
  7121.                 return false;
  7122.             }
  7123.  
  7124.             // if the form is in an overlay, close it
  7125.             if (this.$form.parents('.xenOverlay').length) {
  7126.                 var $overlay = this.$form.parents('.xenOverlay').data('overlay');
  7127.  
  7128.                 if (ajaxData.linkPhrase) {
  7129.                     $trigger = $overlay.getTrigger();
  7130.                     $trigger.xfFadeOut(XenForo.speed.fast, function () {
  7131.                         if (ajaxData.linkUrl && $trigger.is('a')) {
  7132.                             $trigger.attr('href', ajaxData.linkUrl);
  7133.                         }
  7134.  
  7135.                         $trigger
  7136.                             .text(ajaxData.linkPhrase)
  7137.                             .xfFadeIn(XenForo.speed.fast);
  7138.                     });
  7139.                 }
  7140.  
  7141.                 // if animations are disabled, decaching can happen too quickly
  7142.                 setTimeout(function () {
  7143.                     $overlay.close();
  7144.                 }, 0);
  7145.             }
  7146.  
  7147.             if (XenForo.isPositive(this.$form.data('reset'))) {
  7148.                 this.$form[0].reset();
  7149.             }
  7150.  
  7151.             if (ajaxData.message) {
  7152.                 XenForo.alert(ajaxData.message, '', 4000);
  7153.                 return;
  7154.             }
  7155.  
  7156.             // if a redirect message was not specified, redirect immediately
  7157.             if (ajaxData._redirectMessage == '') {
  7158.                 this.submitPending = true;
  7159.                 return this.redirect(ajaxData._redirectTarget);
  7160.             }
  7161.  
  7162.             // show the redirect message, then redirect if a redirect target was specified
  7163.             this.submitPending = true;
  7164.             XenForo.alert(ajaxData._redirectMessage, '', 1000, $.context(function () {
  7165.                 this.redirect(ajaxData._redirectTarget);
  7166.             }, this));
  7167.         },
  7168.  
  7169.         /**
  7170.          * Checks for the presence of validation errors in the given event
  7171.          *
  7172.          * @param event e
  7173.          *
  7174.          * @return boolean
  7175.          */
  7176.         validates: function (e) {
  7177.             return ($.isEmptyObject(e.validationErrors) && !e.ajaxData.error);
  7178.         },
  7179.  
  7180.         /**
  7181.          * Attempts to match labels to errors for the error overlay
  7182.          *
  7183.          * @param string html
  7184.          *
  7185.          * @return jQuery
  7186.          */
  7187.         prepareError: function (html) {
  7188.             $html = $(html);
  7189.  
  7190.             // extract labels that correspond to the error fields and insert their text next to the error message
  7191.             $html.find('label').each(function (i, label) {
  7192.                 var $ctrlLabel = $('#' + $(label).attr('for'))
  7193.                     .closest('.ctrlUnit')
  7194.                     .find('dt > label');
  7195.  
  7196.                 if ($ctrlLabel.length) {
  7197.                     $(label).prepend($ctrlLabel.text() + '<br />');
  7198.                 }
  7199.             });
  7200.  
  7201.             return $html;
  7202.         },
  7203.  
  7204.         /**
  7205.          * Redirect the browser to redirectTarget if it is specified
  7206.          *
  7207.          * @param string redirectTarget
  7208.          *
  7209.          * @return boolean
  7210.          */
  7211.         redirect: function (redirectTarget) {
  7212.             if (XenForo.isPositive(this.$form.data('redirect')) || !parseInt(XenForo._enableOverlays)) {
  7213.                 var $AutoValidationRedirect = new $.Event('AutoValidationRedirect');
  7214.                 $AutoValidationRedirect.redirectTarget = redirectTarget;
  7215.  
  7216.                 this.$form.trigger($AutoValidationRedirect);
  7217.  
  7218.                 if (!$AutoValidationRedirect.isDefaultPrevented() && $AutoValidationRedirect.redirectTarget) {
  7219.                     var fn = function () {
  7220.                         XenForo.redirect(redirectTarget);
  7221.                     };
  7222.  
  7223.                     if (XenForo._manualDeferOverlay) {
  7224.                         $(document).one('ManualDeferComplete', fn);
  7225.                     } else {
  7226.                         fn();
  7227.                     }
  7228.  
  7229.                     return true;
  7230.                 }
  7231.             }
  7232.  
  7233.             return false;
  7234.         },
  7235.  
  7236.         // ---------------------------------------------------
  7237.         // Field validation methods...
  7238.  
  7239.         /**
  7240.          * Sets a timeout before an AJAX field validation request will be fired
  7241.          * (Prevents AJAX floods)
  7242.          *
  7243.          * @param string Name of field to be validated
  7244.          * @param function Callback to fire when the timeout elapses
  7245.          */
  7246.         setFieldValidationTimeout: function (name, callback) {
  7247.             if (!this.hasFieldValidator(name)) {
  7248.                 return false;
  7249.             }
  7250.  
  7251.             console.log('setTimeout %s', name);
  7252.  
  7253.             this.clearFieldValidationTimeout(name);
  7254.  
  7255.             this.fieldValidationTimeouts[name] = setTimeout(callback, 250);
  7256.         },
  7257.  
  7258.         /**
  7259.          * Cancels a timeout set with setFieldValidationTimeout()
  7260.          *
  7261.          * @param string name
  7262.          */
  7263.         clearFieldValidationTimeout: function (name) {
  7264.             if (this.fieldValidationTimeouts[name]) {
  7265.                 console.log('Clear field validation timeout: %s', name);
  7266.  
  7267.                 clearTimeout(this.fieldValidationTimeouts[name]);
  7268.                 delete (this.fieldValidationTimeouts[name]);
  7269.             }
  7270.         },
  7271.  
  7272.         /**
  7273.          * Fires an AJAX field validation request
  7274.          *
  7275.          * @param string Name of variable to be verified
  7276.          * @param jQuery Input field to be validated
  7277.          * @param function Callback function to fire on success
  7278.          */
  7279.         startFieldValidationRequest: function (name, $input, callback) {
  7280.             if (!this.hasFieldValidator(name)) {
  7281.                 return false;
  7282.             }
  7283.  
  7284.             // abort any existing AJAX validation requests from this $input
  7285.             this.abortFieldValidationRequest(name);
  7286.  
  7287.             // fire the AJAX request and register it in the fieldValidationRequests
  7288.             // object so it can be cancelled by subsequent requests
  7289.             this.fieldValidationRequests[name] = XenForo.ajax(this.fieldValidatorUrl,
  7290.                 {
  7291.                     name: name,
  7292.                     value: $input.fieldValue(),
  7293.                     existingDataKey: this.getExistingDataKey()
  7294.                 }, callback,
  7295.                 {
  7296.                     global: false // don't show AJAX progress indicators for inline validation
  7297.                 });
  7298.         },
  7299.  
  7300.         /**
  7301.          * Aborts an AJAX field validation request set up by startFieldValidationRequest()
  7302.          *
  7303.          * @param string name
  7304.          */
  7305.         abortFieldValidationRequest: function (name) {
  7306.             if (this.fieldValidationRequests[name]) {
  7307.                 console.log('Abort field validation request: %s', name);
  7308.  
  7309.                 this.fieldValidationRequests[name].abort();
  7310.                 delete (this.fieldValidationRequests[name]);
  7311.             }
  7312.         },
  7313.  
  7314.         /**
  7315.          * Cancels any pending timeouts or ajax field validation requests
  7316.          */
  7317.         abortPendingFieldValidation: function () {
  7318.             $.each(this.fieldValidationTimeouts, $.context(this, 'clearFieldValidationTimeout'));
  7319.             $.each(this.fieldValidationRequests, $.context(this, 'abortFieldValidationRequest'));
  7320.         },
  7321.  
  7322.         /**
  7323.          * Throws a warning if this.fieldValidatorUrl is not valid
  7324.          *
  7325.          * @param string Name of field to be validated
  7326.          *
  7327.          * @return boolean
  7328.          */
  7329.         hasFieldValidator: function (name) {
  7330.             if (this.fieldValidatorUrl) {
  7331.                 return true;
  7332.             }
  7333.  
  7334.             //console.warn('Unable to request validation for field "%s" due to lack of fieldValidatorUrl in form tag.', name);
  7335.             return false;
  7336.         }
  7337.     };
  7338.  
  7339.     // *********************************************************************
  7340.  
  7341.     /**
  7342.      * Handler for individual fields in an AutoValidator form.
  7343.      * Manages individual field validation and inline error display.
  7344.      *
  7345.      * @param jQuery input [text-type]
  7346.      */
  7347.     XenForo.AutoValidatorControl = function ($input) {
  7348.         this.__construct($input);
  7349.     };
  7350.     XenForo.AutoValidatorControl.prototype =
  7351.     {
  7352.         __construct: function ($input) {
  7353.             this.$form = $input.closest('form.AutoValidator').bind(
  7354.                 {
  7355.                     AutoValidationDataReceived: $.context(this, 'handleFormValidation')
  7356.                 });
  7357.  
  7358.             this.$input = $input.bind(
  7359.                 {
  7360.                     change: $.context(this, 'change'),
  7361.                     AutoValidationError: $.context(this, 'showError'),
  7362.                     AutoValidationPass: $.context(this, 'hideError')
  7363.                 });
  7364.  
  7365.             this.name = $input.data('validatorname') || $input.attr('name');
  7366.             this.autoValidate = $input.hasClass('NoAutoValidate') ? false : true;
  7367.         },
  7368.  
  7369.         /**
  7370.          * When the value of a field changes, initiate validation
  7371.          *
  7372.          * @param event e
  7373.          */
  7374.         change: function (e) {
  7375.             if (this.autoValidate) {
  7376.                 this.$form.data('XenForo.AutoValidator')
  7377.                     .setFieldValidationTimeout(this.name, $.context(this, 'validate'));
  7378.             }
  7379.         },
  7380.  
  7381.         /**
  7382.          * Fire a validation AJAX request
  7383.          */
  7384.         validate: function () {
  7385.             if (this.autoValidate) {
  7386.                 this.$form.data('XenForo.AutoValidator')
  7387.                     .startFieldValidationRequest(this.name, this.$input, $.context(this, 'handleValidation'));
  7388.             }
  7389.         },
  7390.  
  7391.         /**
  7392.          * Handle the data returned from an AJAX validation request fired in validate().
  7393.          * Fires 'AutoValidationPass' or 'AutoValidationError' for the $input according to the validation state.
  7394.          *
  7395.          * @param object ajaxData
  7396.          * @param string textStatus
  7397.          *
  7398.          * @return boolean
  7399.          */
  7400.         handleValidation: function (ajaxData, textStatus) {
  7401.             if (ajaxData && ajaxData.error && ajaxData.error.hasOwnProperty(this.name)) {
  7402.                 this.$input.trigger({
  7403.                     type: 'AutoValidationError',
  7404.                     errorMessage: ajaxData.error[this.name]
  7405.                 });
  7406.                 return false;
  7407.             } else {
  7408.                 this.$input.trigger('AutoValidationPass');
  7409.                 return true;
  7410.             }
  7411.         },
  7412.  
  7413.         /**
  7414.          * Shows an inline error message, text contained within a .errorMessage property of the event passed
  7415.          *
  7416.          * @param event e
  7417.          */
  7418.         showError: function (e) {
  7419.             console.warn('%s: %s', this.name, e.errorMessage);
  7420.  
  7421.             var error = this.fetchError(e.errorMessage).css('display', 'inline-block');
  7422.             this.positionError(error);
  7423.         },
  7424.  
  7425.         /**
  7426.          * Hides any inline error message shown with this input
  7427.          */
  7428.         hideError: function () {
  7429.             console.info('%s: Okay', this.name);
  7430.  
  7431.             if (this.$error) {
  7432.                 this.fetchError()
  7433.                     .hide();
  7434.             }
  7435.         },
  7436.  
  7437.         /**
  7438.          * Fetches or creates (as necessary) the error HTML object for this field
  7439.          *
  7440.          * @param string Error message
  7441.          *
  7442.          * @return jQuery this.$error
  7443.          */
  7444.         fetchError: function (message) {
  7445.             if (!this.$error) {
  7446.                 this.$error = $('<label for="' + this.$input.attr('id') + '" class="formValidationInlineError">WHoops</label>').insertAfter(this.$input);
  7447.             }
  7448.  
  7449.             if (message) {
  7450.                 this.$error.html(message).xfActivate();
  7451.             }
  7452.  
  7453.             return this.$error;
  7454.         },
  7455.  
  7456.         /**
  7457.          * Returns an object containing top and left properties, used to position the inline error message
  7458.          */
  7459.         positionError: function ($error) {
  7460.             $error.removeClass('inlineError');
  7461.  
  7462.             var coords = this.$input.coords('outer', 'position'),
  7463.                 screenCoords = this.$input.coords('outer'),
  7464.                 $window = $(window),
  7465.                 outerWidth = $error.outerWidth(),
  7466.                 absolute,
  7467.                 position = { top: coords.top };
  7468.  
  7469.             if (!screenCoords.width || !screenCoords.height) {
  7470.                 absolute = false;
  7471.             } else {
  7472.                 if (XenForo.isRTL()) {
  7473.                     position.left = coords.left - outerWidth - 10;
  7474.                     absolute = (screenCoords.left - outerWidth - 10 > 0);
  7475.                 } else {
  7476.                     var screenLeft = screenCoords.left + screenCoords.width + 10;
  7477.  
  7478.                     absolute = screenLeft + outerWidth < ($window.width() + $window.scrollLeft());
  7479.                     position.left = coords.left + coords.width + 10;
  7480.                 }
  7481.             }
  7482.  
  7483.             if (absolute) {
  7484.                 $error.css(position);
  7485.             } else {
  7486.                 $error.addClass('inlineError');
  7487.             }
  7488.         },
  7489.  
  7490.         /**
  7491.          * Handles validation for this field passed down from a submission of the whole AutoValidator
  7492.          * form, and passes the relevant data into the handler for this field specifically.
  7493.          *
  7494.          * @param event e
  7495.          */
  7496.         handleFormValidation: function (e) {
  7497.             if (!this.handleValidation(e.ajaxData, e.textStatus)) {
  7498.                 e.validationError.push(this.name);
  7499.             }
  7500.         }
  7501.     };
  7502.  
  7503.     // *********************************************************************
  7504.  
  7505.     /**
  7506.      * Checks a form field to see if it is part of an AutoValidator form,
  7507.      * and if so, whether or not it is subject to autovalidation.
  7508.      *
  7509.      * @param object Form control to be tested
  7510.      *
  7511.      * @return boolean
  7512.      */
  7513.     XenForo.isAutoValidatorField = function (ctrl) {
  7514.         var AutoValidator, $ctrl, $form = $(ctrl.form);
  7515.  
  7516.         if (!$form.hasClass('AutoValidator')) {
  7517.             return false;
  7518.         }
  7519.  
  7520.         AutoValidator = $form.data('XenForo.AutoValidator');
  7521.  
  7522.         if (AutoValidator) {
  7523.             $ctrl = $(ctrl);
  7524.  
  7525.             switch (AutoValidator.optInMode) {
  7526.                 case 'OptIn': {
  7527.                     return ($ctrl.hasClass('OptIn') || $ctrl.closest('.ctrlUnit').hasClass('OptIn'));
  7528.                 }
  7529.                 default: {
  7530.                     return (!$ctrl.hasClass('OptOut') && !$ctrl.closest('.ctrlUnit').hasClass('OptOut'));
  7531.                 }
  7532.             }
  7533.         }
  7534.  
  7535.         return false;
  7536.     };
  7537.  
  7538.     // *********************************************************************
  7539.  
  7540.     XenForo.PreviewForm = function ($form) {
  7541.         var previewUrl = $form.data('previewurl');
  7542.         if (!previewUrl) {
  7543.             console.warn('PreviewForm has no data-previewUrl: %o', $form);
  7544.             return;
  7545.         }
  7546.  
  7547.         $form.find('.PreviewButton').click(function (e) {
  7548.             var $button = $(this);
  7549.  
  7550.             XenForo.ajax(previewUrl, $form.serialize(), function (ajaxData) {
  7551.                 if (XenForo.hasResponseError(ajaxData) || !XenForo.hasTemplateHtml(ajaxData)) {
  7552.                     return false;
  7553.                 }
  7554.  
  7555.                 new XenForo.ExtLoader(ajaxData, function (ajaxData) {
  7556.                     var $preview = $form.find('.PreviewContainer').first();
  7557.                     if ($preview.length) {
  7558.                         $preview.xfFadeOut(XenForo.speed.fast, function () {
  7559.                             $preview.html(ajaxData.templateHtml).xfActivate();
  7560.                         });
  7561.                     } else {
  7562.                         $preview = $('<div />', { 'class': 'PreviewContainer' })
  7563.                             .hide()
  7564.                             .html(ajaxData.templateHtml)
  7565.                             .prependTo($form)
  7566.                             .xfActivate();
  7567.                     }
  7568.  
  7569.                     var overlay = $button.data('overlay');
  7570.                     if (overlay) {
  7571.                         $preview.show();
  7572.                         XenForo.createOverlay($preview, $preview.html(ajaxData.templateHtml)).load();
  7573.                     } else {
  7574.                         $preview.xfFadeIn(XenForo.speed.fast);
  7575.                         $preview.get(0).scrollIntoView(true);
  7576.                     }
  7577.                 });
  7578.             });
  7579.         });
  7580.     };
  7581.  
  7582.     // *********************************************************************
  7583.  
  7584.     /**
  7585.      * Allows a text input field to rewrite the H1 (or equivalent) tag's contents
  7586.      *
  7587.      * @param jQuery input[data-liveTitleTemplate]
  7588.      */
  7589.     XenForo.LiveTitle = function ($input) {
  7590.         var $title = $input.closest('.formOverlay').find('h2.h1'), setTitle;
  7591.  
  7592.         if (!$title.length) {
  7593.             $title = $('.titleBar h1').first();
  7594.         }
  7595.         console.info('Title Element: %o', $title);
  7596.         $title.data('originalhtml', $title.html());
  7597.  
  7598.         setTitle = function (value) {
  7599.             $input.trigger('LiveTitleSet', [value]);
  7600.  
  7601.             $title.html(value === ''
  7602.                 ? $title.data('originalhtml')
  7603.                 : $input.data('livetitletemplate').replace(/%s/, $('<div />').text(value).html())
  7604.             );
  7605.         };
  7606.  
  7607.         if (!$input.hasClass('prompt')) {
  7608.             setTitle($input.strval());
  7609.         }
  7610.  
  7611.         $('.modal').on('hidden.bs.modal', function () {
  7612.             setTitle('')
  7613.         })
  7614.  
  7615.         $input.bind('keyup focus', function (e) {
  7616.             setTitle($input.strval());
  7617.         })
  7618.             .on('paste', function (e) {
  7619.                 setTimeout(function () {
  7620.                     setTitle($input.strval());
  7621.                 }, 0);
  7622.             })
  7623.             .closest('form').bind('reset', function (e) {
  7624.                 setTitle('');
  7625.             });
  7626.     };
  7627.  
  7628.     // *********************************************************************
  7629.  
  7630.     XenForo.TextareaElastic = function ($input) {
  7631.         this.__construct($input);
  7632.     };
  7633.     XenForo.TextareaElastic.prototype =
  7634.     {
  7635.         __construct: function ($input) {
  7636.             this.$input = $input;
  7637.             this.curHeight = 0;
  7638.  
  7639.             $input.bind('keyup focus XFRecalculate', $.context(this, 'recalculate'));
  7640.             $input.bind('paste', $.context(this, 'paste'));
  7641.  
  7642.             if ($input.val() !== '') {
  7643.                 this.recalculate();
  7644.             }
  7645.         },
  7646.  
  7647.         recalculate: function () {
  7648.             var $input = this.$input,
  7649.                 input = $input.get(0),
  7650.                 clone,
  7651.                 height,
  7652.                 pos;
  7653.  
  7654.             if ($input.val() === '') {
  7655.                 $input.css({
  7656.                     'overflow-y': 'hidden',
  7657.                     'height': ''
  7658.                 });
  7659.                 this.curHeight = 0;
  7660.                 return;
  7661.             }
  7662.  
  7663.             if (!input.clientWidth) {
  7664.                 return;
  7665.             }
  7666.  
  7667.             if (!this.minHeight) {
  7668.                 this.borderBox = ($input.css('-moz-box-sizing') == 'border-box' || $input.css('box-sizing') == 'border-box');
  7669.                 this.minHeight = (this.borderBox ? $input.outerHeight() : input.clientHeight);
  7670.  
  7671.                 if (!this.minHeight) {
  7672.                     return;
  7673.                 }
  7674.  
  7675.                 this.maxHeight = parseInt($input.css('max-height'), 10);
  7676.                 this.spacing = (this.borderBox ? $input.outerHeight() - $input.innerHeight() : 0);
  7677.             }
  7678.  
  7679.             if (!this.$clone) {
  7680.                 this.$clone = $('<textarea />').css({
  7681.                     position: 'absolute',
  7682.                     left: (XenForo.isRTL() ? '9999em' : '-9999em'),
  7683.                     top: 0,
  7684.                     visibility: 'hidden',
  7685.                     width: input.clientWidth,
  7686.                     height: '1px',
  7687.                     'font-size': $input.css('font-size'),
  7688.                     'font-family': $input.css('font-family'),
  7689.                     'font-weight': $input.css('font-weight'),
  7690.                     'line-height': $input.css('line-height'),
  7691.                     'word-wrap': $input.css('word-wrap')
  7692.                 }).attr('tabindex', -1).val(' ');
  7693.  
  7694.                 this.$clone.appendTo(document.body);
  7695.  
  7696.                 this.lineHeight = this.$clone.get(0).scrollHeight;
  7697.             }
  7698.  
  7699.             this.$clone.val($input.val());
  7700.             clone = this.$clone.get(0);
  7701.  
  7702.             height = Math.max(this.minHeight, clone.scrollHeight + this.lineHeight + this.spacing);
  7703.  
  7704.             if (height < this.maxHeight) {
  7705.                 if (this.curHeight != height) {
  7706.                     input = $input.get(0);
  7707.                     if (this.curHeight == this.maxHeight && input.setSelectionRange) {
  7708.                         pos = input.selectionStart;
  7709.                     }
  7710.  
  7711.                     $input.css({
  7712.                         'overflow-y': 'hidden',
  7713.                         'height': height + 'px'
  7714.                     });
  7715.  
  7716.                     if (this.curHeight == this.maxHeight && input.setSelectionRange) {
  7717.                         try {
  7718.                             input.setSelectionRange(pos, pos);
  7719.                         } catch (e) {
  7720.                         }
  7721.                     }
  7722.  
  7723.                     this.curHeight = height;
  7724.                 }
  7725.             } else {
  7726.                 if (this.curHeight != this.maxHeight) {
  7727.                     input = $input.get(0);
  7728.                     if (input.setSelectionRange) {
  7729.                         pos = input.selectionStart;
  7730.                     }
  7731.  
  7732.                     $input.css({
  7733.                         'overflow-y': 'auto',
  7734.                         'height': this.maxHeight + 'px'
  7735.                     });
  7736.  
  7737.                     if (input.setSelectionRange) {
  7738.                         try {
  7739.                             input.setSelectionRange(pos, pos);
  7740.                         } catch (e) {
  7741.                         }
  7742.                     }
  7743.  
  7744.                     this.curHeight = this.maxHeight;
  7745.                 }
  7746.             }
  7747.         },
  7748.  
  7749.         paste: function () {
  7750.             setTimeout($.context(this, 'recalculate'), 100);
  7751.         }
  7752.     };
  7753.  
  7754.     // *********************************************************************
  7755.  
  7756.     XenForo.AutoTimeZone = function ($element) {
  7757.         var now = new Date(),
  7758.             jan1 = new Date(now.getFullYear(), 0, 1), // 0 = jan
  7759.             jun1 = new Date(now.getFullYear(), 5, 1), // 5 = june
  7760.             jan1offset = Math.round(jan1.getTimezoneOffset()),
  7761.             jun1offset = Math.round(jun1.getTimezoneOffset());
  7762.  
  7763.         // opera doesn't report TZ offset differences in jan/jun correctly
  7764.         if ($.browser.opera) {
  7765.             return false;
  7766.         }
  7767.  
  7768.         if (XenForo.AutoTimeZone.map[jan1offset + ',' + jun1offset]) {
  7769.             $element.val(XenForo.AutoTimeZone.map[jan1offset + ',' + jun1offset]);
  7770.             return true;
  7771.         } else {
  7772.             return false;
  7773.         }
  7774.     };
  7775.  
  7776.     XenForo.AutoTimeZone.map =
  7777.     {
  7778.         '660,660': 'Pacific/Midway',
  7779.         '600,600': 'Pacific/Honolulu',
  7780.         '570,570': 'Pacific/Marquesas',
  7781.         '540,480': 'America/Anchorage',
  7782.         '480,420': 'America/Los_Angeles',
  7783.         '420,360': 'America/Denver',
  7784.         '420,420': 'America/Phoenix',
  7785.         '360,300': 'America/Chicago',
  7786.         '360,360': 'America/Belize',
  7787.         '300,240': 'America/New_York',
  7788.         '300,300': 'America/Bogota',
  7789.         '270,270': 'America/Caracas',
  7790.         '240,180': 'America/Halifax',
  7791.         '180,240': 'America/Cuiaba',
  7792.         '240,240': 'America/La_Paz',
  7793.         '210,150': 'America/St_Johns',
  7794.         '180,180': 'America/Argentina/Buenos_Aires',
  7795.         '120,180': 'America/Sao_Paulo',
  7796.         '180,120': 'America/Miquelon',
  7797.         '120,120': 'America/Noronha',
  7798.         '60,60': 'Atlantic/Cape_Verde',
  7799.         '60,0': 'Atlantic/Azores',
  7800.         '0,-60': 'Europe/London',
  7801.         '0,0': 'Atlantic/Reykjavik',
  7802.         '-60,-120': 'Europe/Amsterdam',
  7803.         '-60,-60': 'Africa/Algiers',
  7804.         '-120,-60': 'Africa/Windhoek',
  7805.         '-120,-180': 'Europe/Athens',
  7806.         '-120,-120': 'Africa/Johannesburg',
  7807.         '-180,-240': 'Africa/Nairobi',
  7808.         '-180,-180': 'Europe/Moscow',
  7809.         '-210,-270': 'Asia/Tehran',
  7810.         '-240,-300': 'Asia/Yerevan',
  7811.         '-270,-270': 'Asia/Kabul',
  7812.         '-300,-300': 'Asia/Tashkent',
  7813.         '-330,-330': 'Asia/Kolkata',
  7814.         '-345,-345': 'Asia/Kathmandu',
  7815.         '-360,-360': 'Asia/Dhaka',
  7816.         '-390,-390': 'Asia/Rangoon',
  7817.         '-420,-420': 'Asia/Bangkok',
  7818.         '-420,-480': 'Asia/Krasnoyarsk',
  7819.         '-480,-480': 'Asia/Hong_Kong',
  7820.         '-540,-540': 'Asia/Tokyo',
  7821.         '-630,-570': 'Australia/Adelaide',
  7822.         '-570,-570': 'Australia/Darwin',
  7823.         '-660,-600': 'Australia/Sydney',
  7824.         '-600,-600': 'Asia/Vladivostok',
  7825.         '-690,-690': 'Pacific/Norfolk',
  7826.         '-780,-720': 'Pacific/Auckland',
  7827.         '-825,-765': 'Pacific/Chatham',
  7828.         '-780,-780': 'Pacific/Tongatapu',
  7829.         '-840,-840': 'Pacific/Kiritimati'
  7830.     };
  7831.  
  7832.     // *********************************************************************
  7833.  
  7834.     XenForo.DatePicker = function ($input) {
  7835.         if (!XenForo.DatePicker.$root) {
  7836.             $.tools.dateinput.localize('_f',
  7837.                 {
  7838.                     months: XenForo.phrases._months,
  7839.                     shortMonths: '1,2,3,4,5,6,7,8,9,10,11,12',
  7840.                     days: 's,m,t,w,t,f,s',
  7841.                     shortDays: XenForo.phrases._daysShort
  7842.                 });
  7843.         }
  7844.  
  7845.         var $date = $input.dateinput(
  7846.             {
  7847.                 lang: '_f',
  7848.                 format: 'yyyy-mm-dd', // rfc 3339 format, required by html5 date element
  7849.                 speed: 0,
  7850.                 yearRange: [-100, 100],
  7851.                 onShow: function (e) {
  7852.                     var $root = XenForo.DatePicker.$root,
  7853.                         offset = $date.offset(),
  7854.                         maxZIndex = 0,
  7855.                         position = { top: offset.top + $date.outerHeight() };
  7856.  
  7857.                     if (XenForo.isRTL()) {
  7858.                         position.right = $('html').width() - offset.left - $date.outerWidth();
  7859.                     } else {
  7860.                         position.left = offset.left;
  7861.                     }
  7862.  
  7863.                     $root.css(position);
  7864.  
  7865.                     $date.parents().each(function (i, el) {
  7866.                         var zIndex = parseInt($(el).css('z-index'), 10);
  7867.                         if (zIndex > maxZIndex) {
  7868.                             maxZIndex = zIndex;
  7869.                         }
  7870.                     });
  7871.  
  7872.                     $root.css('z-index', maxZIndex + 1000);
  7873.                 }
  7874.             });
  7875.  
  7876.         $date.addClass($input.attr('class'));
  7877.         if ($input.attr('id')) {
  7878.             $date.attr('id', $input.attr('id'));
  7879.         }
  7880.  
  7881.         // this is needed to handle input[type=reset] buttons that end up focusing the field
  7882.         $date.closest('form').on('reset', function () {
  7883.             setTimeout(function () {
  7884.                 $date.data('dateinput').hide();
  7885.             }, 10);
  7886.             setTimeout(function () {
  7887.                 $date.data('dateinput').hide();
  7888.             }, 100);
  7889.         });
  7890.  
  7891.         if (!XenForo.DatePicker.$root) {
  7892.             XenForo.DatePicker.$root = $('#calroot').appendTo(document.body);
  7893.  
  7894.             $('#calprev').html(XenForo.isRTL() ? '&rarr;' : '&larr;').prop('unselectable', true);
  7895.             $('#calnext').html(XenForo.isRTL() ? '&larr;' : '&rarr;').prop('unselectable', true);
  7896.         }
  7897.     };
  7898.  
  7899.     XenForo.DatePicker.$root = null;
  7900.  
  7901.     // *********************************************************************
  7902.     var localStorageSmiliesKey = 'smilies_cache';
  7903.  
  7904.  
  7905.     XenForo.AutoComplete = function ($element) {
  7906.         this.__construct($element);
  7907.     };
  7908.     XenForo.AutoComplete.prototype =
  7909.     {
  7910.         __construct: function ($input) {
  7911.             this.$input = $input;
  7912.  
  7913.             this.url = $input.data('acurl') || XenForo.AutoComplete.getDefaultUrl();
  7914.             this.extraFields = $input.data('acextrafields');
  7915.             this.smiliesAutoCompleteCount = 5;
  7916.             var options = {
  7917.                 multiple: $input.hasClass('AcSingle') ? false : ',', // mutiple value joiner
  7918.                 minLength: 2, // min word length before lookup
  7919.                 queryKey: 'q',
  7920.                 extraParams: { searchOnlyWithAtSign: $input.hasClass('AtSign') },
  7921.                 jsonContainer: 'results',
  7922.                 autoSubmit: XenForo.isPositive($input.data('autosubmit'))
  7923.             };
  7924.             if ($input.data('acoptions')) {
  7925.                 options = $.extend(options, $input.data('acoptions'));
  7926.             }
  7927.  
  7928.             if (options.autoSubmit) {
  7929.                 options.multiple = false;
  7930.             }
  7931.  
  7932.             this.multiple = options.multiple;
  7933.             this.minLength = options.minLength;
  7934.             this.queryKey = options.queryKey;
  7935.             this.extraParams = options.extraParams;
  7936.             this.jsonContainer = options.jsonContainer;
  7937.             this.autoSubmit = options.autoSubmit;
  7938.             this.enabled = true
  7939.             var self = this
  7940.             this.loadVal = '';
  7941.             this.lastAcLookup = []
  7942.             this.results = new XenForo.AutoCompleteResults({
  7943.                 onInsert: $.context(this, 'addValue')
  7944.             });
  7945.             this.acSmiliesResults = new XenForo.AutoSmiliesCompleteResults({
  7946.                 onInsert: $.context(this, 'insertAutoComplete')
  7947.             });
  7948.  
  7949.             $input.attr('autocomplete', 'off')
  7950.                 .keydown($.context(this, 'keystroke'))
  7951.                 .keypress($.context(this, 'operaKeyPress'))
  7952.                 .blur($.context(this, 'blur'));
  7953.  
  7954.             $input.on('paste', function () {
  7955.                 setTimeout(function () {
  7956.                     $input.trigger('keydown');
  7957.                 }, 0);
  7958.             });
  7959.  
  7960.             $input.closest('form').submit($.context(this, 'hideResults'));
  7961.  
  7962.             var smilies_loaded = false;
  7963.  
  7964.             if (supports_html5_storage()) {
  7965.                 var temp = localStorage.getItem(localStorageSmiliesKey);
  7966.                 if (temp) {
  7967.                     temp = JSON.parse(temp)
  7968.                     if (temp.cache === XenForo.smiliesCacheKey) {
  7969.                         this.smilies_complete = temp
  7970.                         smilies_loaded = true
  7971.                     }
  7972.                 }
  7973.  
  7974.                 if (!smilies_loaded) {
  7975.                     XenForo.ajax(
  7976.                         XenForo._editorSmiliesUrl,
  7977.                         {},
  7978.                         function (ajaxData) {
  7979.                             if (XenForo.hasResponseError(ajaxData)) {
  7980.                                 return;
  7981.                             }
  7982.                             var data = {}
  7983.                             data.smilies = ajaxData.smilies
  7984.                             data.cache = XenForo.smiliesCacheKey
  7985.                             if (temp) {
  7986.                                 data.recently = temp.recently
  7987.                             } else {
  7988.                                 data.recently = []
  7989.                             }
  7990.                             localStorage.setItem(localStorageSmiliesKey, JSON.stringify(data))
  7991.                             self.smilies_complete = data
  7992.                         }).complete(function () {
  7993.                             this.smilies_complete = self.smilies_complete
  7994.                         }.bind(this));
  7995.                 }
  7996.             }
  7997.  
  7998.             if (this.$input.hasClass('MessageCtrl')) {
  7999.                 $(".ImNotification--QuickReplyForm").on('submit', function (e) {
  8000.                     self.hideAutoComplete();
  8001.                 })
  8002.             } else {
  8003.                 $("input[type=submit]").on('click', function () {
  8004.                     self.hideAutoComplete();
  8005.                 })
  8006.             }
  8007.         },
  8008.  
  8009.         setEnabled(enabled) {
  8010.             if (this.loadTimer) clearTimeout(this.loadTimer)
  8011.             this.loadTimer = null
  8012.             this.hideResults()
  8013.             this.enabled = enabled
  8014.         },
  8015.  
  8016.         keystroke: function (e) {
  8017.             if (!this.enabled) return
  8018.             var code = e.keyCode || e.charCode, prevent = true;
  8019.  
  8020.             switch (code) {
  8021.                 case 40:
  8022.                     if (this.results.isVisible()) {
  8023.                         this.results.selectResult(1);
  8024.                         break; // down
  8025.                     }
  8026.                 case 39:
  8027.                     if (this.acSmiliesResults.isVisible()) {
  8028.                         this.acSmiliesResults.selectResult(1);
  8029.                         break; // right
  8030.                     }
  8031.                 case 38:
  8032.                     if (this.results.isVisible()) {
  8033.                         this.results.selectResult(-1);
  8034.                         break; // up
  8035.                     }
  8036.                 case 37:
  8037.                     if (this.acSmiliesResults.isVisible()) {
  8038.                         this.acSmiliesResults.selectResult(-1);
  8039.                         break; // left
  8040.                     }
  8041.                 case 27:
  8042.                     if (this.results.isVisible()) {
  8043.                         this.results.hideResults();
  8044.                     } else {
  8045.                         if (this.acSmiliesResults.isVisible()) {
  8046.                             this.acSmiliesResults.hideResults()
  8047.                         } else {
  8048.                             prevent = false;
  8049.                         }
  8050.                     }
  8051.                     break; // esc
  8052.                 case 13: // enter
  8053.                     if (this.results.isVisible()) {
  8054.                         this.results.insertSelectedResult();
  8055.                     } else {
  8056.                         if (this.acSmiliesResults.isVisible() && this.acSmiliesResults.selectedResult !== -1) {
  8057.                             this.acSmiliesResults.insertSelectedResult()
  8058.                         } else {
  8059.                             prevent = false;
  8060.                         }
  8061.                     }
  8062.                     this.hideAutoComplete();
  8063.                     break;
  8064.  
  8065.                 default:
  8066.                     prevent = false;
  8067.                     if (e.ctrlKey && code === 86 || code === 17) {
  8068.                         return
  8069.                     }
  8070.                     if (this.loadTimer) {
  8071.                         clearTimeout(this.loadTimer);
  8072.                     }
  8073.                     this.loadTimer = setTimeout($.context(this, 'load'), 200);
  8074.                     this.loadTimerSmilies = setTimeout($.context(this, 'smilies'), 1);
  8075.                     if (code != 229) {
  8076.                         this.results.hideResults();
  8077.                     }
  8078.             }
  8079.             if (prevent) {
  8080.                 e.preventDefault();
  8081.             }
  8082.             this.preventKey = prevent;
  8083.         },
  8084.  
  8085.         operaKeyPress: function (e) {
  8086.             if ($.browser.opera && this.preventKey) {
  8087.                 e.preventDefault();
  8088.             }
  8089.         },
  8090.  
  8091.         blur: function (e) {
  8092.             clearTimeout(this.loadTimer);
  8093.  
  8094.             // timeout ensures that clicks still register
  8095.             setTimeout($.context(this, 'hideResults'), 250);
  8096.  
  8097.             if (this.xhr) {
  8098.                 this.xhr.abort();
  8099.                 this.xhr = false;
  8100.             }
  8101.         },
  8102.  
  8103.         smilies: function () {
  8104.             if (!this.$input.hasClass('AutoSmiliesComplete')) {
  8105.                 return
  8106.             }
  8107.  
  8108.             if (!(XenForo.SmiliesAutoComplete && !XenForo.SmiliesAutoComplete.enable)) {
  8109.                 var smiliesCompleteText = this.findCurrentSmiliesAutoCompleteOption();
  8110.                 var smiliesFullCompleteText = this.findFullSmiliesAutoCompleteOption();
  8111.             } else {
  8112.                 var smiliesCompleteText = []
  8113.                 var smiliesFullCompleteText = []
  8114.             }
  8115.  
  8116.             if (smiliesCompleteText.length) {
  8117.                 return this.triggerSmiliesAutoComplete(smiliesCompleteText);
  8118.             } else {
  8119.                 if (smiliesFullCompleteText.length) {
  8120.                     return this.triggerSmiliesAutoComplete(smiliesFullCompleteText, true);
  8121.                 }
  8122.             }
  8123.             return this.hideAutoComplete();
  8124.  
  8125.         },
  8126.  
  8127.         hideAutoComplete: function () {
  8128.             this.acSmiliesResults.hideResults();
  8129.         },
  8130.  
  8131.         load: function () {
  8132.             var lastLoad = this.loadVal,
  8133.                 params = this.extraParams;
  8134.  
  8135.             if (this.loadTimer) {
  8136.                 clearTimeout(this.loadTimer);
  8137.             }
  8138.  
  8139.             this.loadVal = this.getPartialValue();
  8140.  
  8141.             if (this.loadVal === '') {
  8142.                 this.hideResults();
  8143.                 return;
  8144.             }
  8145.  
  8146.             if (params.searchOnlyWithAtSign) {
  8147.                 if (this.loadVal.indexOf('@') !== 0) {
  8148.                     return;
  8149.                 }
  8150.  
  8151.                 this.loadVal = this.loadVal.substr(1);
  8152.             }
  8153.  
  8154.             if (this.loadVal == lastLoad) {
  8155.                 return;
  8156.             }
  8157.  
  8158.             if (this.loadVal.length < this.minLength) {
  8159.                 return;
  8160.             }
  8161.  
  8162.             if (this.loadVal.indexOf(",") !== -1) {
  8163.                 return;
  8164.             }
  8165.  
  8166.             params[this.queryKey] = this.loadVal;
  8167.  
  8168.             if (this.extraFields != '') {
  8169.                 $(this.extraFields).each(function () {
  8170.                     params[this.name] = $(this).val();
  8171.                 });
  8172.             }
  8173.  
  8174.             if (this.xhr) {
  8175.                 this.xhr.abort();
  8176.             }
  8177.  
  8178.             this.xhr = XenForo.ajax(
  8179.                 this.url,
  8180.                 params,
  8181.                 $.context(this, 'showResults'),
  8182.                 { global: false, error: false }
  8183.             );
  8184.         },
  8185.  
  8186.         hideResults: function () {
  8187.             this.results.hideResults();
  8188.         },
  8189.  
  8190.         showResults: function (results) {
  8191.             if (this.xhr) {
  8192.                 this.xhr = false;
  8193.             }
  8194.  
  8195.             if (this.jsonContainer && results) {
  8196.                 results = results[this.jsonContainer];
  8197.             }
  8198.  
  8199.             this.results.showResults(this.getPartialValue(), results, this.$input);
  8200.         },
  8201.  
  8202.         addValue: function (value) {
  8203.             if (this.extraParams.searchOnlyWithAtSign) {
  8204.                 value = '@' + value;
  8205.             }
  8206.  
  8207.             if (!this.multiple) {
  8208.                 this.$input.val(value + ' ');
  8209.             } else {
  8210.                 var values = this.getFullValues();
  8211.                 if (value != '') {
  8212.                     if (values.length) {
  8213.                         value = ' ' + value;
  8214.                     }
  8215.                     values.push(value + this.multiple + ' ');
  8216.                 }
  8217.                 this.$input.val(values.join(this.multiple));
  8218.             }
  8219.             this.$input.trigger("AutoComplete", { inserted: value, current: this.$input.val() });
  8220.  
  8221.             if (this.autoSubmit) {
  8222.                 this.$input.closest('form').submit();
  8223.             } else {
  8224.                 this.$input.focus();
  8225.             }
  8226.         },
  8227.  
  8228.         findFullSmiliesAutoCompleteOption: function () {
  8229.             var selected = window.getSelection()
  8230.             var origin = [selected.anchorNode, selected.anchorOffset];
  8231.             var focus = [selected.focusNode, selected.focusOffset]
  8232.  
  8233.             this.loadVal = this.getPartialValue();
  8234.             testText = this.loadVal
  8235.             var lastAt = testText.lastIndexOf(':');
  8236.             if (lastAt < testText.lastIndexOf('@')) {
  8237.                 return false
  8238.             }
  8239.  
  8240.             var list = []
  8241.  
  8242.             // Search by full name
  8243.             list.push(...this.smilies_complete.smilies.filter(function (e) {
  8244.                 return e.name.toUpperCase() === testText.toUpperCase() // By name
  8245.                     || e.value.toUpperCase() === testText.toUpperCase() // By value
  8246.                     || e.aliases.filter(function (alias) {
  8247.                         return alias.toUpperCase() === testText.toUpperCase() // By alias
  8248.                     }).length
  8249.             }
  8250.             ))
  8251.  
  8252.             list = list.filter(function (e) { return e.value !== name.value })
  8253.             list.splice(this.smiliesAutoCompleteCount)
  8254.  
  8255.             return list
  8256.         },
  8257.  
  8258.         findCurrentSmiliesAutoCompleteOption: function () {
  8259.             var selected = window.getSelection()
  8260.             var origin = [selected.anchorNode, selected.anchorOffset];
  8261.             var focus = [selected.focusNode, selected.focusOffset]
  8262.  
  8263.             this.loadVal = this.getPartialValue();
  8264.             testText = this.loadVal
  8265.             var lastAt = testText.lastIndexOf(':');
  8266.             if (lastAt < testText.lastIndexOf('@')) {
  8267.                 return false
  8268.             }
  8269.  
  8270.             var list = []
  8271.  
  8272.             if (lastAt !== -1 && (lastAt === 0 || ((testText[lastAt - 1].trim() === "" || testText[lastAt + 1]) && !(/[a-zA-Z0-9]/).test(testText[lastAt - 1]))) && list.length < this.smiliesAutoCompleteCount) {
  8273.                 var afterAt = testText.substr(lastAt + 1);
  8274.                 afterAt = afterAt.replace(new RegExp(String.fromCharCode(160), 'g'), ' ')
  8275.                 if (afterAt.startsWith(' ')) {
  8276.                     return false;
  8277.                 }
  8278.                 if (!afterAt.trim() && this.smilies_complete.recently.length) {
  8279.                     // Return recently
  8280.                     list = this.smilies_complete.recently;
  8281.                 }
  8282.  
  8283.                 list.push(...this.smilies_complete.smilies.filter(function (e) {
  8284.                     return e.name.replaceAll(":", "").substr(0, afterAt.length).toUpperCase() === afterAt.toUpperCase() // By name
  8285.                         || e.value.replaceAll(":", "").substr(0, afterAt.length).toUpperCase() === afterAt.toUpperCase() // By value
  8286.                         || e.aliases.filter(function (alias) {
  8287.                             return alias.replaceAll(":", "").substr(0, afterAt.length).toUpperCase() === afterAt.toUpperCase() // By alias
  8288.                         }).length
  8289.                 }
  8290.                 ))
  8291.             }
  8292.  
  8293.             list = list.filter(function (e) { return e.value !== name.value })
  8294.             list.splice(this.smiliesAutoCompleteCount)
  8295.  
  8296.             return list
  8297.         },
  8298.  
  8299.         insertAutoComplete: function (name) {
  8300.             this.$input.focus();
  8301.  
  8302.             var selected = window.getSelection()
  8303.             var origin = [selected.anchorNode, selected.anchorOffset];
  8304.             var focus = [selected.focusNode, selected.focusOffset]
  8305.  
  8306.             if (!focus || !origin || focus[0] != origin[0] || focus[1] != origin[1] || typeof this.smilies_complete === "undefined") {
  8307.                 return false;
  8308.             }
  8309.  
  8310.             var $focus = $(focus[0]),
  8311.                 testText = focus[0].nodeType == 3 ? $focus.text().substring(0, focus[1]) : $($focus.contents().get(focus[1] - 1)).val();
  8312.             if (!testText.trim()) {
  8313.                 testText = $($focus.contents().get(focus[1])).val()
  8314.             }
  8315.  
  8316.             var lastAt = testText.toLowerCase().lastIndexOf(name[0].toLowerCase());
  8317.  
  8318.             if (lastAt === -1) {
  8319.                 lastAt = 0
  8320.             }
  8321.             var name = this.lastAcLookup[parseInt(name[1])]
  8322.             var value = name.value
  8323.             var input = this.$input.val()
  8324.  
  8325.             var length = input.length
  8326.             input = input.substring(0, lastAt)
  8327.             input = input.substring(length - (lastAt + testText.length))
  8328.             input += value
  8329.             this.$input.val(input)
  8330.             this.$input.trigger("AutoComplete", { inserted: value, current: this.$input.val() });
  8331.  
  8332.             if (this.autoSubmit) {
  8333.                 this.$input.closest('form').submit();
  8334.             } else {
  8335.                 this.$input.focus();
  8336.             }
  8337.             this.lastAcLookup = name;
  8338.  
  8339.             // Save to recently
  8340.             var recently = this.smilies_complete.recently.filter(function (e) { return e.value !== name.value })
  8341.             recently.unshift(name)
  8342.             recently.splice(this.smiliesAutoCompleteCount)
  8343.             this.smilies_complete.recently = recently
  8344.             if (supports_html5_storage()) {
  8345.                 localStorage.setItem(localStorageSmiliesKey, JSON.stringify(this.smilies_complete))
  8346.             }
  8347.  
  8348.             this.hideAutoComplete();
  8349.             this.$input.focus();
  8350.         },
  8351.  
  8352.         triggerSmiliesAutoComplete: function (name, def) {
  8353.             if (this.lastAcLookup.length && this.lastAcLookup == name) {
  8354.                 return;
  8355.             }
  8356.  
  8357.             if (this.$input.prop('disabled')) {
  8358.                 return
  8359.             }
  8360.  
  8361.             this.hideAutoComplete();
  8362.             this.lastAcLookup = name;
  8363.             this.showAutoCompleteResults({ results: name }, def)
  8364.         },
  8365.  
  8366.         showAutoCompleteResults: function (ajaxData, def) {
  8367.             this.acXhr = false;
  8368.  
  8369.             if (JSON.stringify(this.lastAcLookup) !== JSON.stringify(this.findCurrentSmiliesAutoCompleteOption()) && JSON.stringify(this.lastAcLookup) !== JSON.stringify(this.findFullSmiliesAutoCompleteOption())) {
  8370.                 return;
  8371.             }
  8372.  
  8373.             this.acSmiliesResults.showResults(
  8374.                 this.lastAcLookup,
  8375.                 ajaxData.results,
  8376.                 this.$input
  8377.             );
  8378.             if (def) {
  8379.                 this.acSmiliesResults.selectResult(-1)
  8380.             }
  8381.         },
  8382.  
  8383.         getFullValues: function () {
  8384.             var val = this.$input.val();
  8385.  
  8386.             if (val == '') {
  8387.                 return [];
  8388.             }
  8389.  
  8390.             if (!this.multiple) {
  8391.                 return [val];
  8392.             } else {
  8393.                 splitPos = val.lastIndexOf(this.multiple);
  8394.                 if (splitPos == -1) {
  8395.                     return [];
  8396.                 } else {
  8397.                     val = val.substr(0, splitPos);
  8398.                     return val.split(this.multiple);
  8399.                 }
  8400.             }
  8401.         },
  8402.  
  8403.         getPartialValue: function () {
  8404.             var val = this.$input.val(),
  8405.                 splitPos;
  8406.  
  8407.             if (!this.multiple) {
  8408.                 return $.trim(val);
  8409.             } else {
  8410.                 splitPos = val.lastIndexOf(this.multiple);
  8411.                 let value
  8412.                 if (splitPos == -1) {
  8413.                     value = $.trim(val);
  8414.                 } else {
  8415.                     value = $.trim(val.substr(splitPos + this.multiple.length));
  8416.                 }
  8417.                 return value.replace(/^@/, '')
  8418.             }
  8419.         }
  8420.     };
  8421.     XenForo.AutoComplete.getDefaultUrl = function () {
  8422.         if (XenForo.AutoComplete.defaultUrl === null) {
  8423.             if ($('html').hasClass('Admin')) {
  8424.                 XenForo.AutoComplete.defaultUrl = 'admin.php?users/search-name&_xfResponseType=json';
  8425.             } else {
  8426.                 XenForo.AutoComplete.defaultUrl = 'members/find&_xfResponseType=json';
  8427.             }
  8428.         }
  8429.         ;
  8430.  
  8431.         return XenForo.AutoComplete.defaultUrl;
  8432.     };
  8433.     XenForo.AutoComplete.defaultUrl = null;
  8434.  
  8435.     // *********************************************************************
  8436.  
  8437.     XenForo.UserTagger = function ($element) {
  8438.         this.__construct($element);
  8439.     };
  8440.     XenForo.UserTagger.prototype =
  8441.     {
  8442.         __construct: function ($textarea) {
  8443.             this.$textarea = $textarea;
  8444.             this.url = $textarea.data('acurl') || XenForo.AutoComplete.getDefaultUrl();
  8445.             this.acResults = new XenForo.AutoCompleteResults({
  8446.                 onInsert: $.context(this, 'insertAutoComplete')
  8447.             });
  8448.  
  8449.             var self = this,
  8450.                 hideTimer,
  8451.                 hideCallback = function () {
  8452.                     if (hideTimer) {
  8453.                         return;
  8454.                     }
  8455.  
  8456.                     hideTimer = setTimeout(function () {
  8457.                         self.acResults.hideResults();
  8458.                         hideTimer = null;
  8459.                     }, 200);
  8460.                 };
  8461.  
  8462.             $(document).on('scroll', hideCallback);
  8463.  
  8464.             $textarea.on('click blur', hideCallback);
  8465.             $textarea.on('keydown', function (e) {
  8466.                 var prevent = true,
  8467.                     acResults = self.acResults;
  8468.  
  8469.                 if (!acResults.isVisible()) {
  8470.                     return;
  8471.                 }
  8472.  
  8473.                 switch (e.keyCode) {
  8474.                     case 40:
  8475.                         acResults.selectResult(1);
  8476.                         break; // down
  8477.                     case 38:
  8478.                         acResults.selectResult(-1);
  8479.                         break; // up
  8480.                     case 27:
  8481.                         acResults.hideResults();
  8482.                         break; // esc
  8483.                     case 13:
  8484.                         acResults.insertSelectedResult();
  8485.                         break; // enter
  8486.  
  8487.                     default:
  8488.                         prevent = false;
  8489.                 }
  8490.  
  8491.                 if (prevent) {
  8492.                     e.stopPropagation();
  8493.                     e.stopImmediatePropagation();
  8494.                     e.preventDefault();
  8495.                 }
  8496.             });
  8497.             $textarea.on('keyup', function (e) {
  8498.                 var autoCompleteText = self.findCurrentAutoCompleteOption();
  8499.                 if (autoCompleteText) {
  8500.                     self.triggerAutoComplete(autoCompleteText);
  8501.                 } else {
  8502.                     self.hideAutoComplete();
  8503.                 }
  8504.             });
  8505.         },
  8506.  
  8507.         findCurrentAutoCompleteOption: function () {
  8508.             var $textarea = this.$textarea;
  8509.  
  8510.             $textarea.focus();
  8511.             var sel = $textarea.getSelection(),
  8512.                 testText,
  8513.                 lastAt;
  8514.  
  8515.             if (!sel || sel.end <= 1) {
  8516.                 return false;
  8517.             }
  8518.  
  8519.             testText = $textarea.val().substring(0, sel.end);
  8520.             lastAt = testText.lastIndexOf('@');
  8521.  
  8522.             if (lastAt != -1 && (lastAt == 0 || testText.substr(lastAt - 1, 1).match(/(\s|[\](,]|--)/))) {
  8523.                 var afterAt = testText.substr(lastAt + 1);
  8524.                 if (!afterAt.match(/\s/) || afterAt.length <= 10) {
  8525.                     return afterAt;
  8526.                 }
  8527.             }
  8528.  
  8529.             return false;
  8530.         },
  8531.  
  8532.         insertAutoComplete: function (name) {
  8533.             var $textarea = this.$textarea;
  8534.  
  8535.             $textarea.focus();
  8536.             var sel = $textarea.getSelection(),
  8537.                 testText;
  8538.  
  8539.             if (!sel || sel.end <= 1) {
  8540.                 return false;
  8541.             }
  8542.  
  8543.             testText = $textarea.val().substring(0, sel.end);
  8544.  
  8545.             var lastAt = testText.lastIndexOf('@');
  8546.             if (lastAt != -1) {
  8547.                 $textarea.setSelection(lastAt, sel.end);
  8548.                 $textarea.replaceSelectedText('@' + name + ' ', 'collapseToEnd');
  8549.                 this.lastAcLookup = name + ' ';
  8550.             }
  8551.         },
  8552.  
  8553.         triggerAutoComplete: function (name) {
  8554.             if (this.lastAcLookup && this.lastAcLookup == name) {
  8555.                 return;
  8556.             }
  8557.  
  8558.             this.hideAutoComplete();
  8559.             this.lastAcLookup = name;
  8560.             if (name.length > 2 && name.substr(0, 1) != '[') {
  8561.                 this.acLoadTimer = setTimeout($.context(this, 'autoCompleteLookup'), 200);
  8562.             }
  8563.         },
  8564.  
  8565.         autoCompleteLookup: function () {
  8566.             if (this.acXhr) {
  8567.                 this.acXhr.abort();
  8568.             }
  8569.  
  8570.             if (this.lastAcLookup != this.findCurrentAutoCompleteOption()) {
  8571.                 return;
  8572.             }
  8573.  
  8574.             this.acXhr = XenForo.ajax(
  8575.                 this.url,
  8576.                 { q: this.lastAcLookup },
  8577.                 $.context(this, 'showAutoCompleteResults'),
  8578.                 { global: false, error: false }
  8579.             );
  8580.         },
  8581.  
  8582.         showAutoCompleteResults: function (ajaxData) {
  8583.             this.acXhr = false;
  8584.             this.acResults.showResults(
  8585.                 this.lastAcLookup,
  8586.                 ajaxData.results,
  8587.                 this.$textarea
  8588.             );
  8589.         },
  8590.  
  8591.         hideAutoComplete: function () {
  8592.             this.acResults.hideResults();
  8593.  
  8594.             if (this.acLoadTimer) {
  8595.                 clearTimeout(this.acLoadTimer);
  8596.                 this.acLoadTimer = false;
  8597.             }
  8598.         }
  8599.     };
  8600.  
  8601.     // *********************************************************************
  8602.  
  8603.     XenForo.AutoCompleteResults = function (options) {
  8604.         this.__construct(options);
  8605.     };
  8606.     XenForo.AutoCompleteResults.prototype =
  8607.     {
  8608.         __construct: function (options) {
  8609.             this.options = $.extend({
  8610.                 onInsert: false
  8611.             }, options);
  8612.  
  8613.             this.selectedResult = 0;
  8614.             this.$results = false;
  8615.             this.resultsVisible = false;
  8616.             this.resizeBound = false;
  8617.         },
  8618.  
  8619.         isVisible: function () {
  8620.             return this.resultsVisible;
  8621.         },
  8622.  
  8623.         hideResults: function () {
  8624.             this.resultsVisible = false;
  8625.  
  8626.             if (this.$results) {
  8627.                 this.$results.hide();
  8628.             }
  8629.         },
  8630.  
  8631.         showResults: function (val, results, $targetOver, cssPosition) {
  8632.             var maxZIndex = 0,
  8633.                 i,
  8634.                 filterRegex,
  8635.                 result,
  8636.                 $li;
  8637.  
  8638.             if (!results) {
  8639.                 this.hideResults();
  8640.                 return;
  8641.             }
  8642.  
  8643.             this.resultsVisible = false;
  8644.  
  8645.             if (!this.$results) {
  8646.                 this.$results = $('<ul />')
  8647.                     .css({ position: 'absolute', display: 'none' })
  8648.                     .addClass('autoCompleteList')
  8649.                     .appendTo(document.body);
  8650.  
  8651.                 $targetOver.parents().each(function (i, el) {
  8652.                     var $el = $(el),
  8653.                         zIndex = parseInt($el.css('z-index'), 10);
  8654.  
  8655.                     if (zIndex > maxZIndex) {
  8656.                         maxZIndex = zIndex;
  8657.                     }
  8658.                 });
  8659.  
  8660.                 this.$results.css('z-index', maxZIndex + 1000);
  8661.                 this.$results.data('XenForo.AutoCompleteResults', this);
  8662.             } else {
  8663.                 this.$results.hide().empty();
  8664.             }
  8665.  
  8666.             //filterRegex = new RegExp('(' + XenForo.regexQuote(val) + ')', 'i');
  8667.  
  8668.             for (i in results) {
  8669.                 if (!results.hasOwnProperty(i)) {
  8670.                     continue;
  8671.                 }
  8672.  
  8673.                 result = results[i];
  8674.                 var $html = $(result.username)
  8675.                 $html.find('.scamNotice').remove()
  8676.                 var $avatar = $html.clone();
  8677.                 $avatar.prepend($(`<img class="smallCompleteAvatar" src="${result.avatar}" style="float: left;margin-top: 2px;margin-right: 5px;border-radius: 17px"/>`))
  8678.                 var $lastSpan = $html.find('span:last')
  8679.                 $lastSpan.text('@' + $lastSpan.text())
  8680.  
  8681.                 $li = $('<li />')
  8682.                     .css('cursor', 'pointer')
  8683.                     .attr('unselectable', 'on')
  8684.                     .data('autocomplete', i)
  8685.                     .data('autocompleteHtml', $html.prop('outerHTML'))
  8686.                     .data('autocompleteAvatar', $avatar.prop('outerHTML'))
  8687.                     .click($.context(this, 'resultClick'))
  8688.                     .mouseenter($.context(this, 'resultMouseEnter'))
  8689.                     .on('mousedown touchstart', function (e) {
  8690.                         e.stopPropagation() // prevent focusing, fix for froala popups
  8691.                     })
  8692.  
  8693.                 if (typeof result == 'string') {
  8694.                     $li.html(XenForo.htmlspecialchars(result));//.replace(filterRegex, '<strong>$1</strong>'));
  8695.                 } else {
  8696.                     // unescaping the avatar here as we expect the username to be escaped, so this means
  8697.                     // that we can/should expect all values to be escaped
  8698.                     $li.html(result['username'])
  8699.                         .prepend(
  8700.                             $('<img class="autoCompleteAvatar" />')
  8701.                                 .attr('src', XenForo.htmlEntityDecode(result['avatar']))
  8702.                         );
  8703.                     /*$li.html(result['username'].replace(filterRegex, '<strong>$1</strong>'))
  8704.                                                      .prepend(
  8705.                                                              $('<img class="autoCompleteAvatar" />')
  8706.                                                                      .attr('src', XenForo.htmlEntityDecode(result['avatar']))
  8707.                                                      );*/
  8708.  
  8709.                 }
  8710.  
  8711.                 $li.appendTo(this.$results);
  8712.             }
  8713.  
  8714.  
  8715.             if (!this.$results.find('li').length) {
  8716.                 return;
  8717.             }
  8718.  
  8719.             this.selectResult(0, true);
  8720.  
  8721.             if (!this.resizeBound) {
  8722.                 $(window).bind('resize', $.context(this, 'hideResults'));
  8723.             }
  8724.  
  8725.             if (!cssPosition) {
  8726.                 var offset = $targetOver.offset();
  8727.  
  8728.                 cssPosition = {
  8729.                     top: offset.top + $targetOver.outerHeight(),
  8730.                     left: offset.left
  8731.                 };
  8732.  
  8733.                 if (XenForo.isRTL()) {
  8734.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  8735.                     cssPosition.left = 'auto';
  8736.                 }
  8737.             }
  8738.  
  8739.             this.$results.css(cssPosition).show();
  8740.             this.$results.wrapInner('<div class="scrollbar scrollbar-macosx scrollbar-dynamic"></div>');
  8741.             this.$results.children().scrollbar();
  8742.             this.resultsVisible = true;
  8743.  
  8744.             $(window).on('scroll', function (e) {
  8745.                 var offset = $targetOver.offset();
  8746.                 cssPosition = {
  8747.                     top: offset.top + $targetOver.outerHeight(),
  8748.                     left: offset.left
  8749.                 };
  8750.  
  8751.                 if (XenForo.isRTL()) {
  8752.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  8753.                     cssPosition.left = 'auto';
  8754.                 }
  8755.                 this.$results.css(cssPosition)
  8756.                 var rect = this.$results.get(0).getBoundingClientRect();
  8757.                 if (!(rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
  8758.                     if (!cssPosition.focus) {
  8759.                         cssPosition.focus = 40;
  8760.                     }
  8761.                     cssPosition.top -= this.$results.get(0).offsetHeight;
  8762.                     cssPosition.top -= cssPosition.focus;
  8763.                     if (cssPosition.top < 0 && XenForo.isTouchBrowser()) return;
  8764.                     this.$results.css(cssPosition);
  8765.                 }
  8766.             }.bind(this));
  8767.  
  8768.             var rect = this.$results.get(0).getBoundingClientRect();
  8769.             if (!(rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
  8770.                 if (!cssPosition.focus) {
  8771.                     cssPosition.focus = 40;
  8772.                 }
  8773.                 cssPosition.top -= this.$results.get(0).offsetHeight;
  8774.                 cssPosition.top -= cssPosition.focus;
  8775.                 if (cssPosition.top < 0 && XenForo.isTouchBrowser()) return;
  8776.                 this.$results.css(cssPosition);
  8777.             }
  8778.         },
  8779.  
  8780.         resultClick: function (e) {
  8781.             e.stopPropagation();
  8782.  
  8783.             this.insertResult($(e.currentTarget).data('autocomplete'), $(e.currentTarget).data('autocompleteHtml'), $(e.currentTarget).data('autocompleteAvatar'));
  8784.             this.hideResults();
  8785.         },
  8786.  
  8787.         resultMouseEnter: function (e) {
  8788.             this.selectResult($(e.currentTarget).index(), true);
  8789.         },
  8790.  
  8791.         selectResult: function (shift, absolute) {
  8792.             var sel, children;
  8793.  
  8794.             if (!this.$results) {
  8795.                 return;
  8796.             }
  8797.  
  8798.             if (absolute) {
  8799.                 this.selectedResult = shift;
  8800.             } else {
  8801.                 this.selectedResult += shift;
  8802.             }
  8803.  
  8804.             sel = this.selectedResult;
  8805.             children = this.$results.find('li');
  8806.             children.each(function (i) {
  8807.                 if (i == sel) {
  8808.                     $(this).addClass('selected');
  8809.                 } else {
  8810.                     $(this).removeClass('selected');
  8811.                 }
  8812.             });
  8813.  
  8814.             if (sel < 0 || sel >= children.length) {
  8815.                 this.selectedResult = -1;
  8816.             }
  8817.         },
  8818.  
  8819.         insertSelectedResult: function () {
  8820.             var res, ret = false;
  8821.  
  8822.             if (!this.resultsVisible) {
  8823.                 return false;
  8824.             }
  8825.  
  8826.             if (this.selectedResult >= 0) {
  8827.                 res = this.$results.find('li').get(this.selectedResult);
  8828.                 if (res) {
  8829.                     this.insertResult($(res).data('autocomplete'), $(res).data('autocompleteHtml'), $(res).data('autocompleteAvatar'));
  8830.                     ret = true;
  8831.                 }
  8832.             }
  8833.  
  8834.             this.hideResults();
  8835.  
  8836.             return ret;
  8837.         },
  8838.  
  8839.         insertResult: function (value, htmlValue, avatarValue) {
  8840.             if (this.options.onInsert) {
  8841.                 this.options.onInsert(value, htmlValue, avatarValue);
  8842.             }
  8843.         }
  8844.     };
  8845.  
  8846.     XenForo.AutoSmiliesCompleteResults = function (options) { this.__construct(options); };
  8847.     XenForo.AutoSmiliesCompleteResults.prototype =
  8848.     {
  8849.         __construct: function (options) {
  8850.             this.options = $.extend({
  8851.                 onInsert: false
  8852.             }, options);
  8853.  
  8854.             this.selectedResult = 0;
  8855.             this.$results = false;
  8856.             this.resultsVisible = false;
  8857.             this.resizeBound = false;
  8858.         },
  8859.  
  8860.         isVisible: function () {
  8861.             return this.resultsVisible;
  8862.         },
  8863.  
  8864.         hideResults: function () {
  8865.             this.resultsVisible = false;
  8866.  
  8867.             if (this.$results) {
  8868.                 this.$results.hide();
  8869.             }
  8870.         },
  8871.  
  8872.         showResults: function (val, results, $targetOver, cssPosition) {
  8873.             var maxZIndex = 0,
  8874.                 i,
  8875.                 filterRegex,
  8876.                 result,
  8877.                 $li;
  8878.  
  8879.             if (!results) {
  8880.                 this.hideResults();
  8881.                 return;
  8882.             }
  8883.  
  8884.             this.resultsVisible = false;
  8885.  
  8886.             if (!this.$results) {
  8887.                 this.$results = $('<ul />')
  8888.                     .css({
  8889.                         position: 'absolute', display: 'none',
  8890.                         'background': 'rgb(45, 45, 45)', 'font-size': '13px', 'border-radius': '3px'
  8891.                     })
  8892.                     .addClass('autoCompleteListSmilies')
  8893.                     .appendTo(document.body);
  8894.  
  8895.                 $targetOver.parents().each(function (i, el) {
  8896.                     var $el = $(el),
  8897.                         zIndex = parseInt($el.css('z-index'), 10);
  8898.  
  8899.                     if (zIndex > maxZIndex) {
  8900.                         maxZIndex = zIndex;
  8901.                     }
  8902.                 });
  8903.  
  8904.                 this.$results.css('z-index', maxZIndex + 1000);
  8905.                 this.$results.data('XenForo.AutoSmiliesCompleteResults', this);
  8906.             }
  8907.             else {
  8908.                 this.$results.hide().empty();
  8909.             }
  8910.  
  8911.             filterRegex = new RegExp('(' + XenForo.regexQuote(val) + ')', 'i');
  8912.  
  8913.             for (var i = 0; i < results.length; i++) {
  8914.                 if (!results.hasOwnProperty(i)) {
  8915.                     continue;
  8916.                 }
  8917.  
  8918.                 result = results[i];
  8919.  
  8920.                 $li = $('<li />')
  8921.                     .css('cursor', 'pointer')
  8922.                     .css('float', 'left')
  8923.                     .css('box-sizing', 'border-box')
  8924.                     .css('line-height', '15px')
  8925.                     .css("display", "flex")
  8926.                     .css("justify-content", 'center')
  8927.                     .css('width', '50px')
  8928.                     .css('height', '50px')
  8929.                     .attr('unselectable', 'on')
  8930.                     .data('autocomplete', i)
  8931.                     .click($.context(this, 'resultClick'))
  8932.                     .mouseenter($.context(this, 'resultMouseEnter'));
  8933.  
  8934.                 $listElement = $('<li />', { 'class': 'Smilie' }).css('display', 'flex').css('align-items', 'center')
  8935.                 $listElement.append($('<img />', { 'src': result.image, title: result.name, alt: result.value, 'data-smilie': 'yes' }).css('max-width', '46px'))
  8936.                 $li.html($listElement.get(0).outerHTML)
  8937.                 $li.appendTo(this.$results);
  8938.             }
  8939.  
  8940.             if (!this.$results.children().length) {
  8941.                 return;
  8942.             }
  8943.  
  8944.             this.selectResult(0, true);
  8945.  
  8946.             if (!this.resizeBound) {
  8947.                 $(window).bind('resize', $.context(this, 'hideResults'));
  8948.             }
  8949.  
  8950.             if (!cssPosition) {
  8951.                 var offset = $targetOver.offset();
  8952.  
  8953.                 cssPosition = {
  8954.                     top: offset.top + $targetOver.outerHeight(),
  8955.                     left: offset.left
  8956.                 };
  8957.  
  8958.                 if (XenForo.isRTL()) {
  8959.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  8960.                     cssPosition.left = 'auto';
  8961.                 }
  8962.             }
  8963.             this.$results.css(cssPosition);
  8964.             this.$results.show()
  8965.             if (!isElementInViewport(this.$results)) {
  8966.                 if (!cssPosition.focus) {
  8967.                     cssPosition.focus = 40
  8968.                 }
  8969.                 cssPosition.top -= this.$results.get(0).offsetHeight
  8970.                 cssPosition.top -= cssPosition.focus
  8971.                 this.$results.css(cssPosition);
  8972.             }
  8973.             $(window).on('scroll', function (e) {
  8974.                 var offset = $targetOver.offset();
  8975.                 cssPosition = {
  8976.                     top: offset.top + $targetOver.outerHeight(),
  8977.                     left: offset.left
  8978.                 };
  8979.  
  8980.                 if (XenForo.isRTL()) {
  8981.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  8982.                     cssPosition.left = 'auto';
  8983.                 }
  8984.                 this.$results.css(cssPosition)
  8985.                 var rect = this.$results.get(0).getBoundingClientRect();
  8986.                 if (!(rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
  8987.                     if (!cssPosition.focus) {
  8988.                         cssPosition.focus = 40
  8989.                     }
  8990.                     cssPosition.top -= this.$results.get(0).offsetHeight
  8991.                     cssPosition.top -= cssPosition.focus
  8992.                     this.$results.css(cssPosition);
  8993.                 }
  8994.             }.bind(this));
  8995.             this.resultsVisible = true;
  8996.         },
  8997.  
  8998.         resultClick: function (e) {
  8999.             e.stopPropagation();
  9000.  
  9001.             this.insertResult($(e.currentTarget).data('autocomplete'));
  9002.             this.hideResults();
  9003.         },
  9004.  
  9005.         resultMouseEnter: function (e) {
  9006.             this.selectResult($(e.currentTarget).index(), true);
  9007.         },
  9008.  
  9009.         selectResult: function (shift, absolute) {
  9010.             var sel, children;
  9011.  
  9012.             if (!this.$results) {
  9013.                 return;
  9014.             }
  9015.  
  9016.             if (absolute) {
  9017.                 this.selectedResult = shift;
  9018.             }
  9019.             else {
  9020.                 this.selectedResult += shift;
  9021.             }
  9022.  
  9023.             sel = this.selectedResult;
  9024.             children = this.$results.children();
  9025.  
  9026.             if (sel >= children.length) {
  9027.                 this.selectedResult = 0;
  9028.                 sel = 0;
  9029.             }
  9030.  
  9031.             if (sel < -1) {
  9032.                 return this.hideResults()
  9033.             }
  9034.  
  9035.             children.each(function (i) {
  9036.                 if (i == sel) {
  9037.                     $(this).css('background', 'rgb(54, 54, 54)')
  9038.                     $(this).addClass('selected');
  9039.                 }
  9040.                 else {
  9041.                     $(this).css('background', 'rgb(45, 45, 45)')
  9042.                     $(this).removeClass('selected');
  9043.                 }
  9044.             });
  9045.  
  9046.         },
  9047.  
  9048.         insertSelectedResult: function () {
  9049.             var res, ret = false;
  9050.  
  9051.             if (!this.resultsVisible) {
  9052.                 return false;
  9053.             }
  9054.  
  9055.             if (this.selectedResult >= 0) {
  9056.                 res = this.$results.children().get(this.selectedResult);
  9057.                 if (res) {
  9058.                     this.insertResult($(res).data('autocomplete'));
  9059.                     ret = true;
  9060.                 }
  9061.             }
  9062.  
  9063.             this.hideResults();
  9064.  
  9065.             return ret;
  9066.         },
  9067.  
  9068.         insertResult: function (value) {
  9069.             if (this.options.onInsert) {
  9070.                 this.options.onInsert(":" + value);
  9071.             }
  9072.         }
  9073.     };
  9074.  
  9075.     XenForo.AutoTemplateCompleteResults = function (options) { this.__construct(options); };
  9076.     XenForo.AutoTemplateCompleteResults.prototype =
  9077.     {
  9078.         __construct: function (options) {
  9079.             this.options = $.extend({
  9080.                 onInsert: false
  9081.             }, options);
  9082.  
  9083.             this.selectedResult = 0;
  9084.             this.$results = false;
  9085.             this.resultsVisible = false;
  9086.             this.resizeBound = false;
  9087.         },
  9088.  
  9089.         isVisible: function () {
  9090.             return this.resultsVisible;
  9091.         },
  9092.  
  9093.         hideResults: function () {
  9094.             this.resultsVisible = false;
  9095.  
  9096.             if (this.$results) {
  9097.                 this.$results.hide();
  9098.             }
  9099.         },
  9100.  
  9101.         showResults: function (val, results, $targetOver, cssPosition) {
  9102.             var maxZIndex = 0,
  9103.                 i,
  9104.                 filterRegex,
  9105.                 result,
  9106.                 $li;
  9107.  
  9108.             if (!results) {
  9109.                 this.hideResults();
  9110.                 return;
  9111.             }
  9112.  
  9113.             this.resultsVisible = false;
  9114.  
  9115.             if (!this.$results) {
  9116.                 this.$results = $('<ul />')
  9117.                     .css({ position: 'absolute', display: 'none' })
  9118.                     .addClass('autoCompleteList')
  9119.                     .appendTo(document.body);
  9120.  
  9121.                 $targetOver.parents().each(function (i, el) {
  9122.                     var $el = $(el),
  9123.                         zIndex = parseInt($el.css('z-index'), 10);
  9124.  
  9125.                     if (zIndex > maxZIndex) {
  9126.                         maxZIndex = zIndex;
  9127.                     }
  9128.                 });
  9129.  
  9130.                 this.$results.css('z-index', maxZIndex + 1000);
  9131.             }
  9132.             else {
  9133.                 this.$results.hide().empty();
  9134.             }
  9135.  
  9136.             filterRegex = new RegExp('(' + XenForo.regexQuote(val) + ')', 'i');
  9137.  
  9138.             for (var i = 0; i < results.length; i++) {
  9139.                 if (!results.hasOwnProperty(i)) {
  9140.                     continue;
  9141.                 }
  9142.  
  9143.                 result = results[i];
  9144.  
  9145.                 $li = $('<li />')
  9146.                     .css('cursor', 'pointer')
  9147.                     .attr('unselectable', 'on')
  9148.                     .data('autocomplete', i)
  9149.                     .click($.context(this, 'resultClick'))
  9150.                     .mouseenter($.context(this, 'resultMouseEnter'));
  9151.  
  9152.                 $listElement = $('<div />', { 'class': 'InsertTemplate' })
  9153.                 $listElement.append($('<div />', { 'class': 'title bold', text: result['title'] }))
  9154.                 $listElement.append($('<div />', { 'class': 'content', text: result['content'] }))
  9155.                 $listElement.append($('<div />', { 'class': 'ContentHtml dnone', text: result['text'] }))
  9156.                 $li.html($listElement.get(0).outerHTML)
  9157.                 $li.appendTo(this.$results);
  9158.             }
  9159.  
  9160.             if (!this.$results.children().length) {
  9161.                 return;
  9162.             }
  9163.  
  9164.             this.selectResult(0, true);
  9165.  
  9166.             if (!this.resizeBound) {
  9167.                 $(window).bind('resize', $.context(this, 'hideResults'));
  9168.             }
  9169.  
  9170.             if (!cssPosition) {
  9171.                 var offset = $targetOver.offset();
  9172.  
  9173.                 cssPosition = {
  9174.                     top: offset.top + $targetOver.outerHeight(),
  9175.                     left: offset.left
  9176.                 };
  9177.  
  9178.                 if (XenForo.isRTL()) {
  9179.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  9180.                     cssPosition.left = 'auto';
  9181.                 }
  9182.             }
  9183.             this.$results.css(cssPosition);
  9184.             this.$results.show()
  9185.             if (!isElementInViewport(this.$results)) {
  9186.                 if (!cssPosition.focus) {
  9187.                     cssPosition.focus = 40
  9188.                 }
  9189.                 cssPosition.top -= this.$results.get(0).offsetHeight
  9190.                 cssPosition.top -= cssPosition.focus
  9191.                 this.$results.css(cssPosition);
  9192.             }
  9193.             $(window).on('scroll', function (e) {
  9194.                 var offset = $targetOver.offset();
  9195.                 cssPosition = {
  9196.                     top: offset.top + $targetOver.outerHeight(),
  9197.                     left: offset.left
  9198.                 };
  9199.  
  9200.                 if (XenForo.isRTL()) {
  9201.                     cssPosition.right = $('html').width() - offset.left - $targetOver.outerWidth();
  9202.                     cssPosition.left = 'auto';
  9203.                 }
  9204.                 this.$results.css(cssPosition)
  9205.                 var rect = this.$results.get(0).getBoundingClientRect();
  9206.                 if (!(rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth))) {
  9207.                     if (!cssPosition.focus) {
  9208.                         cssPosition.focus = 40
  9209.                     }
  9210.                     cssPosition.top -= this.$results.get(0).offsetHeight
  9211.                     cssPosition.top -= cssPosition.focus
  9212.                     this.$results.css(cssPosition);
  9213.                 }
  9214.             }.bind(this));
  9215.             this.resultsVisible = true;
  9216.         },
  9217.  
  9218.         resultClick: function (e) {
  9219.             e.stopPropagation();
  9220.  
  9221.             this.insertResult($(e.currentTarget).data('autocomplete'));
  9222.             this.hideResults();
  9223.         },
  9224.  
  9225.         resultMouseEnter: function (e) {
  9226.             this.selectResult($(e.currentTarget).index(), true);
  9227.         },
  9228.  
  9229.         selectResult: function (shift, absolute) {
  9230.             var sel, children;
  9231.  
  9232.             if (!this.$results) {
  9233.                 return;
  9234.             }
  9235.  
  9236.             if (absolute) {
  9237.                 this.selectedResult = shift;
  9238.             }
  9239.             else {
  9240.                 this.selectedResult += shift;
  9241.             }
  9242.  
  9243.             sel = this.selectedResult;
  9244.             children = this.$results.children();
  9245.  
  9246.             if (sel < 0 || sel >= children.length) {
  9247.                 this.selectedResult = 0;
  9248.                 sel = 0;
  9249.             }
  9250.  
  9251.             children.each(function (i) {
  9252.                 if (i == sel) {
  9253.                     $(this).addClass('selected');
  9254.                 }
  9255.                 else {
  9256.                     $(this).removeClass('selected');
  9257.                 }
  9258.             });
  9259.         },
  9260.  
  9261.         insertSelectedResult: function () {
  9262.             var res, ret = false;
  9263.  
  9264.             if (!this.resultsVisible) {
  9265.                 return false;
  9266.             }
  9267.  
  9268.             if (this.selectedResult >= 0) {
  9269.                 res = this.$results.children().get(this.selectedResult);
  9270.                 if (res) {
  9271.                     this.insertResult($(res).data('autocomplete'));
  9272.                     ret = true;
  9273.                 }
  9274.             }
  9275.  
  9276.             this.hideResults();
  9277.  
  9278.             return ret;
  9279.         },
  9280.  
  9281.         insertResult: function (value) {
  9282.             if (this.options.onInsert) {
  9283.                 this.options.onInsert(value);
  9284.             }
  9285.         }
  9286.     };
  9287.  
  9288.     // *********************************************************************
  9289.  
  9290.     XenForo.AutoSelect = function ($input) {
  9291.         $input.bind('focus', function (e) {
  9292.             setTimeout(function () {
  9293.                 $input.select();
  9294.             }, 50);
  9295.         });
  9296.     };
  9297.  
  9298.     // *********************************************************************
  9299.  
  9300.     /**
  9301.      * Status Editor
  9302.      *
  9303.      * @param jQuery $textarea.StatusEditor
  9304.      */
  9305.     XenForo.StatusEditor = function ($input) {
  9306.         this.__construct($input);
  9307.     };
  9308.     XenForo.StatusEditor.prototype =
  9309.     {
  9310.         __construct: function ($input) {
  9311.             this.$input = $input
  9312.                 .keyup($.context(this, 'update'))
  9313.                 .keydown($.context(this, 'preventNewline'));
  9314.  
  9315.             this.$counter = $(this.$input.data('statuseditorcounter'));
  9316.             if (!this.$counter.length) {
  9317.                 this.$counter = $('<span />').insertAfter(this.$input);
  9318.             }
  9319.             this.$counter
  9320.                 .addClass('statusEditorCounter')
  9321.                 .text('0');
  9322.  
  9323.             this.$form = this.$input.closest('form').bind(
  9324.                 {
  9325.                     AutoValidationComplete: $.context(this, 'saveStatus')
  9326.                 });
  9327.  
  9328.             this.charLimit = 140; // Twitter max characters
  9329.             this.charCount = 0; // number of chars currently in use
  9330.  
  9331.             this.update();
  9332.         },
  9333.  
  9334.         /**
  9335.          * Handles key events on the status editor, updates the 'characters remaining' output.
  9336.          *
  9337.          * @param Event e
  9338.          */
  9339.         update: function (e) {
  9340.             var statusText = this.$input.val();
  9341.  
  9342.             if (this.$input.attr('placeholder') && this.$input.attr('placeholder') == statusText) {
  9343.                 this.setCounterValue(this.charLimit, statusText.length);
  9344.             } else {
  9345.                 this.setCounterValue(this.charLimit - statusText.length, statusText.length);
  9346.             }
  9347.         },
  9348.  
  9349.         /**
  9350.          * Sets the value of the character countdown, and appropriate classes for that value.
  9351.          *
  9352.          * @param integer Characters remaining
  9353.          * @param integer Current length of status text
  9354.          */
  9355.         setCounterValue: function (remaining, length) {
  9356.             if (remaining < 0) {
  9357.                 this.$counter.addClass('error');
  9358.                 this.$counter.removeClass('warning');
  9359.             } else if (remaining <= this.charLimit - 130) {
  9360.                 this.$counter.removeClass('error');
  9361.                 this.$counter.addClass('warning');
  9362.             } else {
  9363.                 this.$counter.removeClass('error');
  9364.                 this.$counter.removeClass('warning');
  9365.             }
  9366.  
  9367.             this.$counter.text(remaining);
  9368.             this.charCount = length || this.$input.val().length;
  9369.         },
  9370.  
  9371.         /**
  9372.          * Don't allow newline characters in the status message.
  9373.          *
  9374.          * Submit the form if [Enter] or [Return] is hit.
  9375.          *
  9376.          * @param Event e
  9377.          */
  9378.         preventNewline: function (e) {
  9379.             if (e.which == 13) // return / enter
  9380.             {
  9381.                 e.preventDefault();
  9382.  
  9383.                 $(this.$input.get(0).form).submit();
  9384.  
  9385.                 return false;
  9386.             }
  9387.         },
  9388.  
  9389.         /**
  9390.          * Updates the status field after saving
  9391.          *
  9392.          * @param event e
  9393.          */
  9394.         saveStatus: function (e) {
  9395.             this.$input.val('');
  9396.             this.update(e);
  9397.  
  9398.             if (e.ajaxData && e.ajaxData.status !== undefined) {
  9399.                 $('.CurrentStatus').text(e.ajaxData.status);
  9400.             }
  9401.         }
  9402.     };
  9403.  
  9404.     // *********************************************************************
  9405.  
  9406.     window.addEventListener("scroll", function () {
  9407.         window.lastScrollTime = new Date().getTime()
  9408.     });
  9409.     function is_scrolling() {
  9410.         return window.lastScrollTime && new Date().getTime() < window.lastScrollTime + 500
  9411.     }
  9412.  
  9413.     XenForo._ShowPreviewTimeout = null;
  9414.     XenForo._ActivePreviewTooltip = null;
  9415.  
  9416.     XenForo.PreviewTooltip = function ($el, timeout, appendTo) {
  9417.         var previewUrl;
  9418.  
  9419.         if (!parseInt(XenForo._enableOverlays)) {
  9420.             return;
  9421.         }
  9422.  
  9423.         if (!(previewUrl = $el.data('previewurl'))) {
  9424.             console.warn('Preview tooltip has no preview: %o', $el);
  9425.             return;
  9426.         }
  9427.  
  9428.         $el.find('[title]').andSelf().attr('title', '');
  9429.         var loaded = false;
  9430.  
  9431.         tippy($el.get(), {
  9432.             touch: false,
  9433.             interactive: false,
  9434.             arrow: true,
  9435.             theme: timeout ? 'popup' : 'popup PreviewTooltip',
  9436.             animation: 'shift-toward',
  9437.             distance: 5,
  9438.             appendTo: appendTo || document.body,
  9439.             delay: [timeout || 300, 0],
  9440.             maxWidth: 400,
  9441.             placement: 'top-start',
  9442.             flipOnUpdate: true,
  9443.             content: '',
  9444.             popperOptions: {
  9445.                 modifiers: {
  9446.                     computeStyle: {
  9447.                         gpuAcceleration: false
  9448.                     }
  9449.                 }
  9450.             },
  9451.             onShow(instance) {
  9452.  
  9453.                 if (is_scrolling()) {
  9454.                     clearTimeout(XenForo._ShowPreviewTimeout);
  9455.                     XenForo._ShowPreviewTimeout = setTimeout(function () {
  9456.                         console.log('check scroll', is_scrolling());
  9457.                         if (!is_scrolling()) {
  9458.                             console.log('trigger hover', $el[0]._tippy);
  9459.                             $el[0]._tippy.show();
  9460.                         }
  9461.                     }, 700);
  9462.  
  9463.                     return false;
  9464.                 }
  9465.  
  9466.                 if (XenForo._ActivePreviewTooltip && XenForo._ActivePreviewTooltip !== instance) {
  9467.                     XenForo._ActivePreviewTooltip.hide();
  9468.                 }
  9469.  
  9470.                 if (!loaded) {
  9471.                     XenForo.ajax(previewUrl, {}, function (ajaxData) {
  9472.                         var $content = $('#PreviewTooltip').clone();
  9473.                         $content.find('.previewContent').html('');
  9474.                         //$content.hide();
  9475.                         $(ajaxData.templateHtml).xfInsert('appendTo', $content.find('.previewContent'), 'fadeIn', 50, function () {
  9476.                             instance.setContent($content.html());
  9477.                             loaded = true;
  9478.                             if ($el.is(':hover')) {
  9479.                                 instance.show();
  9480.                                 XenForo._ActivePreviewTooltip = instance;
  9481.                                 return true;
  9482.                             }
  9483.  
  9484.                         });
  9485.                     });
  9486.  
  9487.                     return false;
  9488.                 }
  9489.  
  9490.                 return true;
  9491.             },
  9492.         })
  9493.     };
  9494.  
  9495.     // *********************************************************************
  9496.  
  9497.     /**
  9498.      * Allows an entire block to act as a link in the navigation popups
  9499.      *
  9500.      * @param jQuery li.PopupItemLink
  9501.      */
  9502.     XenForo.PopupItemLink = function ($listItem) {
  9503.         var href = $listItem.find('.PopupItemLink').first().attr('href');
  9504.  
  9505.         if (href) {
  9506.             $listItem
  9507.                 .addClass('PopupItemLinkActive')
  9508.                 .on('click auxclick contextmenu', function (e) {
  9509.                     if ($(e.target).closest('a').length || $(e.target).closest('input').length || window.getSelection().toString() !== "" || $(e.target).closest('.marketIndexItem--otherInfo').length || $(e.target).closest('.marketItemView--loginData').length) {
  9510.                         return;
  9511.                     }
  9512.  
  9513.                     switch (e.which) {
  9514.                         case 1: XenForo.redirect(href); break
  9515.                         case 2: window.open(href)
  9516.                     }
  9517.  
  9518.                 });
  9519.         }
  9520.     };
  9521.  
  9522.     // *********************************************************************
  9523.  
  9524.     /**
  9525.      * Allows a link or input to load content via AJAX and insert it into the DOM.
  9526.      * The control element to which this is applied must have href or data-href attributes
  9527.      * and a data-target attribute describing a jQuery selector for the element relative to which
  9528.      * the content will be inserted.
  9529.      *
  9530.      * You may optionally provide a data-method attribute to override the default insertion method
  9531.      * of 'appendTo'.
  9532.      *
  9533.      * By default, the control will be unlinked and have its click event unbound after a single use.
  9534.      * Specify data-unlink="false" to prevent this default behaviour.
  9535.      *
  9536.      * Upon successful return of AJAX data, the control element will fire a 'ContentLoaded' event,
  9537.      * including ajaxData and textStatus data properties.
  9538.      */
  9539.     XenForo.Loader = function ($link) {
  9540.         var clickHandler = function (e) {
  9541.             var href = $link.attr('href') || $link.data('href'),
  9542.                 target = $link.data('target');
  9543.  
  9544.             if (href && $(target).length) {
  9545.                 if ($link.closest('a').length) {
  9546.                     e.stopPropagation();
  9547.                 }
  9548.  
  9549.                 e.preventDefault();
  9550.  
  9551.                 if ($link.data('tooltip')) {
  9552.                     $link.data('tooltip').hide();
  9553.                 }
  9554.  
  9555.                 XenForo.ajax(href, {}, function (ajaxData, textStatus) {
  9556.                     if (XenForo.hasResponseError(ajaxData)) {
  9557.                         return false;
  9558.                     }
  9559.  
  9560.                     var insertEvent = new $.Event('ContentLoaded');
  9561.                     insertEvent.ajaxData = ajaxData;
  9562.                     insertEvent.textStatus = textStatus;
  9563.  
  9564.                     $link.trigger(insertEvent);
  9565.  
  9566.                     if (!insertEvent.isDefaultPrevented()) {
  9567.                         if (ajaxData.templateHtml) {
  9568.                             new XenForo.ExtLoader(ajaxData, function () {
  9569.                                 var method = $link.data('method');
  9570.  
  9571.                                 if (typeof $.fn[method] != 'function') {
  9572.                                     method = 'appendTo';
  9573.                                 }
  9574.  
  9575.                                 if (method == 'replaceAll') {
  9576.                                     $(ajaxData.templateHtml).xfInsert(method, target, 'show', 0);
  9577.                                 } else {
  9578.                                     $(ajaxData.templateHtml).xfInsert(method, target);
  9579.                                 }
  9580.  
  9581.                                 if ($link.data('unlink') !== false) {
  9582.                                     $link.removeAttr('href').removeData('href').unbind('click', clickHandler);
  9583.                                 }
  9584.                             });
  9585.                         }
  9586.                     }
  9587.                 });
  9588.             }
  9589.         };
  9590.  
  9591.         $link.bind('click', clickHandler);
  9592.     };
  9593.  
  9594.     // *********************************************************************
  9595.  
  9596.     /**
  9597.      * Allows a control to create a clone of an existing field, like 'add new response' for polls
  9598.      *
  9599.      * @param jQuery $button.FieldAdder[data-source=#selectorOfCloneSource]
  9600.      */
  9601.     XenForo.FieldAdder = function ($button) {
  9602.         $($button.data('source')).filter('.PollNonJsInput').remove();
  9603.  
  9604.         var maxFields = $button.data('maxfields');
  9605.  
  9606.         var checkRemoveButton = function (instant) {
  9607.             if (maxFields && $($button.data('source')).length >= maxFields) {
  9608.                 if (instant) {
  9609.                     $button.css('display', 'none');
  9610.                 } else {
  9611.                     $button.xfHide();
  9612.                 }
  9613.             } else {
  9614.                 $button.css('display', '').xfShow();
  9615.             }
  9616.         };
  9617.  
  9618.         // https://zelenka.guru/threads/4615364/
  9619.         var $source = $($button.data('source'))
  9620.         var $template = $source.last().clone()
  9621.         var $insertTarget = $('<div>').hide().insertAfter($source.last())
  9622.  
  9623.         $button.click(function (e) {
  9624.             if ((!maxFields || ($($button.data('source')).length < maxFields))) {
  9625.                 var $clone = $template.clone();
  9626.                 $clone.find('span.button').on('click', function () {
  9627.                     checkRemoveButton()
  9628.                 })
  9629.                 $clone.find('input:not([type="button"], [type="submit"])').val('').prop('disabled', true);
  9630.                 $clone.find('.spinBoxButton').remove();
  9631.                 $button.trigger({
  9632.                     type: 'FieldAdderClone',
  9633.                     clone: $clone
  9634.                 });
  9635.                 $clone.xfInsert('insertBefore', $insertTarget, false, false, function () {
  9636.                     checkRemoveButton();
  9637.                     var $inputs = $clone.find('input');
  9638.                     $inputs.prop('disabled', false);
  9639.                     $inputs.first().focus().select();
  9640.                 });
  9641.             } else {
  9642.                 checkRemoveButton();
  9643.             }
  9644.         });
  9645.  
  9646.         checkRemoveButton(true);
  9647.     };
  9648.  
  9649.     // *********************************************************************
  9650.  
  9651.     /**
  9652.      * Quick way to toggle the read status of an item
  9653.      *
  9654.      * @param jQuery a.ReadToggle
  9655.      */
  9656.     XenForo.ReadToggle = function ($link) {
  9657.         $link.click(function (e) {
  9658.             e.preventDefault();
  9659.  
  9660.             var xhr = null,
  9661.                 $items = null,
  9662.                 counterId = $link.data('counter');
  9663.  
  9664.             if (xhr == null) {
  9665.                 $items = $link.closest('.discussionListItem').andSelf().toggleClass('unread');
  9666.  
  9667.                 xhr = XenForo.ajax($link.attr('href'), { _xfConfirm: 1 }, function (ajaxData, textStatus) {
  9668.                     xhr = null;
  9669.  
  9670.                     if (XenForo.hasResponseError(ajaxData)) {
  9671.                         return false;
  9672.                     }
  9673.  
  9674.                     if (typeof ajaxData.unread != 'undefined') {
  9675.                         $items[(ajaxData.unread ? 'addClass' : 'removeClass')]('unread');
  9676.                     }
  9677.  
  9678.                     if (counterId && typeof ajaxData.counterFormatted != 'undefined') {
  9679.                         var $counter = $(counterId),
  9680.                             $total = $counter.find('span.Total');
  9681.  
  9682.                         if ($total.length) {
  9683.                             $total.text(ajaxData.counterFormatted);
  9684.                         } else {
  9685.                             $counter.text(ajaxData.counterFormatted);
  9686.                         }
  9687.                     }
  9688.  
  9689.                     if (typeof ajaxData.actionPhrase != 'undefined') {
  9690.                         if ($link.text() != '') {
  9691.                             $link.html(ajaxData.actionPhrase);
  9692.                         }
  9693.                         if ($link.attr('title')) {
  9694.                             $link.attr('title', ajaxData.actionPhrase);
  9695.                         }
  9696.                     }
  9697.  
  9698.                     XenForo.alert(ajaxData._redirectMessage, '', 1000);
  9699.                 });
  9700.             }
  9701.         });
  9702.     };
  9703.  
  9704.     // *********************************************************************
  9705.  
  9706.     XenForo.Notices = function ($notices) {
  9707.         var useCookie = XenForo.visitor.user_id ? false : true,
  9708.             cookieName = 'notice_dismiss',
  9709.             useScroller = $notices.hasClass('PanelScroller');
  9710.  
  9711.         var getCookieIds = function () {
  9712.             var cookieValue = $.getCookie(cookieName),
  9713.                 cookieDismissed = cookieValue ? cookieValue.split(',') : [],
  9714.                 values = [],
  9715.                 id;
  9716.  
  9717.             for (var i = 0; i < cookieDismissed.length; i++) {
  9718.                 id = parseInt(cookieDismissed[i], 10);
  9719.                 if (id) {
  9720.                     values.push(id);
  9721.                 }
  9722.             }
  9723.  
  9724.             return values;
  9725.         };
  9726.         var dismissed = getCookieIds();
  9727.  
  9728.         if (useCookie) {
  9729.             $notices.find('.Notice').each(function () {
  9730.                 var $notice = $(this),
  9731.                     id = parseInt($notice.data('notice'), 10);
  9732.  
  9733.                 if (id && $.inArray(id, dismissed) != -1) {
  9734.                     $notice.remove();
  9735.                     $('#n' + id).remove();
  9736.                 }
  9737.             });
  9738.         }
  9739.  
  9740.         var setNoticeVisibility = function ($notices, useScroller) {
  9741.             if (useScroller) {
  9742.                 $notices.find('.Notice').each(function () {
  9743.                     var $notice = $(this),
  9744.                         display = $(this).css('display'),
  9745.                         id = parseInt($notice.data('notice'), 10);
  9746.  
  9747.                     if (display == 'none') {
  9748.                         $notice.remove();
  9749.                         $('#n' + id).remove();
  9750.                     }
  9751.                 });
  9752.  
  9753.                 var $navLinks = $notices.find('.Nav a');
  9754.                 if (!$navLinks.filter('.current').length) {
  9755.                     $navLinks.first().addClass('current');
  9756.                 }
  9757.             }
  9758.  
  9759.             if (!$notices.find('.Notice').length) {
  9760.                 $notices.remove();
  9761.                 return;
  9762.             }
  9763.         };
  9764.  
  9765.         setNoticeVisibility($notices, useScroller);
  9766.  
  9767.         $notices.show();
  9768.  
  9769.         var PanelScroller;
  9770.  
  9771.         if (useScroller) {
  9772.             PanelScroller = XenForo.PanelScroller($notices.find('.PanelContainer'), {
  9773.                 scrollable: {
  9774.                     speed: $notices.dataOrDefault('speed', 400) * XenForo._animationSpeedMultiplier,
  9775.                     vertical: XenForo.isPositive($notices.data('vertical')),
  9776.                     keyboard: false,
  9777.                     touch: false,
  9778.                     prev: '.NoticePrev',
  9779.                     next: '.NoticeNext'
  9780.                 },
  9781.                 autoscroll: { interval: $notices.dataOrDefault('interval', 2000) }
  9782.             });
  9783.  
  9784.             if (PanelScroller && PanelScroller.getItems().length > 1) {
  9785.                 $(document).bind({
  9786.                     XenForoWindowBlur: function (e) {
  9787.                         PanelScroller.stop();
  9788.                     },
  9789.                     XenForoWindowFocus: function (e) {
  9790.                         PanelScroller.play();
  9791.                     }
  9792.                 });
  9793.             }
  9794.         }
  9795.  
  9796.         if ($notices.hasClass('FloatingContainer')) {
  9797.             var $floatingNotices = $notices.find('.Notice');
  9798.  
  9799.             $floatingNotices.each(function () {
  9800.                 var $self = $(this),
  9801.                     displayDuration = $self.data('display-duration'),
  9802.                     delayDuration = $self.data('delay-duration'),
  9803.                     autoDismiss = XenForo.isPositive($self.data('auto-dismiss'));
  9804.  
  9805.                 if (delayDuration) {
  9806.                     setTimeout(function () {
  9807.                         $self.xfFadeDown(XenForo.speed.normal, function () {
  9808.                             if (displayDuration) {
  9809.                                 setTimeout(function () {
  9810.                                     $self.xfFadeUp(XenForo.speed.normal);
  9811.  
  9812.                                     if (autoDismiss) {
  9813.                                         $self.find('a.DismissCtrl').trigger('click');
  9814.                                     }
  9815.                                 }, displayDuration);
  9816.                             }
  9817.                         });
  9818.                     }, delayDuration);
  9819.                 } else {
  9820.                     $self.css('display', 'block');
  9821.                     if (displayDuration) {
  9822.                         setTimeout(function () {
  9823.                             $self.xfFadeUp(XenForo.speed.normal);
  9824.  
  9825.                             if (autoDismiss) {
  9826.                                 $self.find('a.DismissCtrl').trigger('click');
  9827.                             }
  9828.                         }, displayDuration);
  9829.                     }
  9830.                 }
  9831.  
  9832.  
  9833.             });
  9834.         }
  9835.  
  9836.         var $cookieNotice = $notices.find('.Notice[data-notice="-1"]');
  9837.         if ($cookieNotice.length) {
  9838.             var height = $cookieNotice.height();
  9839.             $(document).find('footer').css('margin-bottom', height);
  9840.         }
  9841.  
  9842.  
  9843.         $notices.delegate('a.DismissCtrl, a.CustomDismissCtrl', 'click', function (e) {
  9844.             e.preventDefault();
  9845.  
  9846.             var $ctrl = $(this),
  9847.                 $notice = $ctrl.closest('.Notice'),
  9848.                 $noticeParent = $notice.closest('.Notices');
  9849.  
  9850.             if ($ctrl.data('tooltip')) {
  9851.                 $ctrl.data('tooltip').hide();
  9852.             }
  9853.  
  9854.             if (PanelScroller) {
  9855.                 PanelScroller.removeItem($notice);
  9856.  
  9857.                 if (!PanelScroller.getItems().length) {
  9858.                     $notices.xfFadeUp();
  9859.                 }
  9860.             } else {
  9861.                 $notice.xfFadeUp(XenForo.speed.fast, function () {
  9862.                     if ($notice.data('notice') == '-1') {
  9863.                         $(document).find('footer').css('margin-bottom', '');
  9864.                     }
  9865.                     $notice.remove();
  9866.                     if (!$noticeParent.find('.Notice').length) {
  9867.                         $notices.xfFadeUp();
  9868.                     }
  9869.                 });
  9870.             }
  9871.  
  9872.             if (useCookie) {
  9873.                 var noticeId = parseInt($notice.data('notice'), 10),
  9874.                     dismissed = getCookieIds();
  9875.  
  9876.                 if (noticeId && $.inArray(noticeId, dismissed) == -1) {
  9877.                     dismissed.push(noticeId);
  9878.                     dismissed.sort(function (a, b) {
  9879.                         return (a - b);
  9880.                     });
  9881.  
  9882.                     $.setCookie(cookieName, dismissed.join(','));
  9883.                 }
  9884.             } else if (!$ctrl.data('xhr')) {
  9885.                 $ctrl.data('xhr', XenForo.ajax($ctrl.attr('href'), { _xfConfirm: 1 }, function (ajaxData, textStatus) {
  9886.                     $ctrl.removeData('xhr');
  9887.  
  9888.                     if (XenForo.hasResponseError(ajaxData)) {
  9889.                         return false;
  9890.                     }
  9891.  
  9892.                     //XenForo.alert(ajaxData._redirectMessage, '', 2000);
  9893.                 }));
  9894.             }
  9895.         });
  9896.  
  9897.         $(window).on('resize', function (e) {
  9898.             setNoticeVisibility($notices, useScroller);
  9899.         });
  9900.     };
  9901.  
  9902.     // *********************************************************************
  9903.  
  9904.     XenForo.PanelScroller = function ($container, options) {
  9905.         var $items = $container.find('.Panels > *');
  9906.  
  9907.         // don't initialize if we have just a single panel
  9908.         if ($items.length < 2) {
  9909.             $container.find('.Panels').css('position', 'static');
  9910.             return false;
  9911.         }
  9912.  
  9913.         $items.find('script').remove(); // script should already have been run and document.writes break stuff
  9914.  
  9915.         function resizeItems() {
  9916.             var maxHeight = 0;
  9917.  
  9918.             $container.find('.Panels > *').css({ width: $container.innerWidth(), height: 'auto' }).each(function () {
  9919.                 maxHeight = Math.max($(this).outerHeight(), maxHeight);
  9920.  
  9921.             }).andSelf().css('height', maxHeight);
  9922.  
  9923.             var api = $container.data('scrollable');
  9924.             if (api) {
  9925.                 api.seekTo(api.getIndex(), 0);
  9926.             }
  9927.         };
  9928.  
  9929.         options = $.extend(true,
  9930.             {
  9931.                 scrollable:
  9932.                 {
  9933.                     circular: true,
  9934.                     items: '.Panels'
  9935.                 },
  9936.                 navigator:
  9937.                 {
  9938.                     navi: '.Nav',
  9939.                     naviItem: 'a',
  9940.                     activeClass: 'current'
  9941.                 },
  9942.                 autoscroll:
  9943.                 {
  9944.                     interval: 3000
  9945.                 }
  9946.  
  9947.             }, options);
  9948.  
  9949.         $container.css('overflow', 'hidden');
  9950.  
  9951.         if (!options.scrollable.vertical) {
  9952.             $container.css('height', 'auto')
  9953.                 .find('.Panels').css('width', '20000em')
  9954.                 .find('.panel').css('float', (XenForo.isRTL() ? 'right' : 'left'));
  9955.         }
  9956.  
  9957.         $(window).bind('load resize', resizeItems);
  9958.         $('.mainContent').bind('XenForoResize', resizeItems);
  9959.  
  9960.         resizeItems();
  9961.  
  9962.         $container.scrollable(options.scrollable).navigator(options.navigator);
  9963.  
  9964.         if ($items.length > 1) {
  9965.             $container.autoscroll(options.autoscroll);
  9966.         }
  9967.  
  9968.         $container.find('input.Disabler').on('click', function () {
  9969.             setInterval(resizeItems, XenForo.speed.fast);
  9970.         });
  9971.  
  9972.         return $container.data('scrollable');
  9973.     };
  9974.  
  9975.     // *********************************************************************
  9976.  
  9977.     XenForo.DisplayIgnoredContent = function (e) {
  9978.         var i, j, styleSheet, rules, rule;
  9979.  
  9980.         const isProfilePage = $('.profilePage').length;
  9981.         e.preventDefault();
  9982.  
  9983.         let $hideContent = $('a.DisplayIgnoredContent')
  9984.         $hideContent.hide();
  9985.  
  9986.         // remove the styling that hides quotes etc.
  9987.         $('#ignoredUserCss').empty().remove();
  9988.  
  9989.         if (document.styleSheets) {
  9990.             for (i = 0; i < document.styleSheets.length; i++) {
  9991.                 styleSheet = document.styleSheets[i];
  9992.                 try {
  9993.                     rules = (styleSheet.cssRules ? styleSheet.cssRules : styleSheet.rules);
  9994.                 } catch (e) {
  9995.                     rules = false;
  9996.                 }
  9997.                 if (!rules) {
  9998.                     continue;
  9999.                 }
  10000.                 for (j = 0; j < rules.length; j++) {
  10001.                     rule = rules[j];
  10002.  
  10003.                     if (rule.selectorText && rule.selectorText.toLowerCase() === '.ignored') {
  10004.                         if (styleSheet.deleteRule) {
  10005.                             styleSheet.deleteRule(j);
  10006.                         } else {
  10007.                             styleSheet.removeRule(j);
  10008.                         }
  10009.                     }
  10010.                 }
  10011.             }
  10012.         }
  10013.  
  10014.         $('.ignored').removeClass('ignored');
  10015.         if (isProfilePage && !$hideContent.closest('.pageNavLinkGroup').find('.PageNav').length) {
  10016.             $clone = $hideContent.closest('.pageNavLinkGroup').clone();
  10017.             $clone.find('.DisplayIgnoredContent').remove()
  10018.             if (!$clone.find('.linkGroup').children().length) $('.pageNavLinkGroup:visible').remove();
  10019.         }
  10020.     };
  10021.  
  10022.     if ($('html').hasClass('Public')) {
  10023.         $(function () {
  10024.             $('body').delegate('a.DisplayIgnoredContent', 'click', XenForo.DisplayIgnoredContent);
  10025.  
  10026.             if (window.location.hash) {
  10027.                 var $jump = $(window.location.hash.replace(/[^\w_#-]/g, ''));
  10028.                 if ($jump.hasClass('ignored')) {
  10029.                     $jump.removeClass('ignored');
  10030.                     $jump.get(0).scrollIntoView(true);
  10031.                 }
  10032.             }
  10033.         });
  10034.     }
  10035.  
  10036.     // *********************************************************************
  10037.  
  10038.     XenForo.SpoilerBBCode = function ($spoiler) {
  10039.         $spoiler.click(function (e) {
  10040.             $spoiler.siblings(':first').css('fontSize', '25pt');
  10041.             return false;
  10042.         });
  10043.  
  10044.         /*$spoiler.click(function(e)
  10045.                  {
  10046.                          $spoiler.html($spoiler.data('spoiler')).removeClass('Spoiler').addClass('bbCodeSpoiler');
  10047.                  });*/
  10048.     };
  10049.  
  10050.     // *********************************************************************
  10051.  
  10052.     /**
  10053.      * Produces centered, square crops of thumbnails identified by data-thumb-selector within the $container.
  10054.      * Requires that CSS of this kind is in place:
  10055.      * .SquareThumb
  10056.      * {
  10057.      *         position: relative; display: block; overflow: hidden;
  10058.      *         width: {$thumbHeight}px; height: {$thumbHeight}px;
  10059.      * }
  10060.      * .SquareThumb img
  10061.      * {
  10062.      *         position: relative; display: block;
  10063.      * }
  10064.      *
  10065.      * @param jQuery $container
  10066.      */
  10067.     XenForo.SquareThumbs = function ($container) {
  10068.         var thumbHeight = $container.data('thumb-height') || 44,
  10069.             thumbSelector = $container.data('thumb-selector') || 'a.SquareThumb';
  10070.  
  10071.         console.info('XenForo.SquareThumbs: %o', $container);
  10072.  
  10073.         var $imgs = $container.find(thumbSelector).addClass('SquareThumb').children('img');
  10074.  
  10075.         var thumbProcessor = function () {
  10076.             var $thumb = $(this),
  10077.                 w = $thumb.width(),
  10078.                 h = $thumb.height();
  10079.  
  10080.             if (!w || !h) {
  10081.                 return;
  10082.             }
  10083.  
  10084.             if (h > w) {
  10085.                 $thumb.css('width', thumbHeight);
  10086.                 $thumb.css('top', ($thumb.height() - thumbHeight) / 2 * -1);
  10087.             } else {
  10088.                 $thumb.css('height', thumbHeight);
  10089.                 $thumb.css('left', ($thumb.width() - thumbHeight) / 2 * -1);
  10090.             }
  10091.         };
  10092.  
  10093.         $imgs.load(thumbProcessor);
  10094.         $imgs.each(thumbProcessor);
  10095.     };
  10096.  
  10097.     // *********************************************************************
  10098.  
  10099.     // Register overlay-loading controls
  10100.     // TODO: when we have a global click handler, change this to use rel="Overlay" instead of class="OverlayTrigger"
  10101.     XenForo.register(
  10102.         'a.OverlayTrigger, input.OverlayTrigger, button.OverlayTrigger, label.OverlayTrigger, a.username, a.avatar',
  10103.         'XenForo.OverlayTrigger'
  10104.     );
  10105.  
  10106.     XenForo.register('.ToggleTrigger', 'XenForo.ToggleTrigger');
  10107.  
  10108.  
  10109.     // Register tooltip elements for desktop browsers
  10110.     XenForo.register('.Tooltip', 'XenForo.Tooltip');
  10111.  
  10112.     //XenForo.register('a.StatusTooltip', 'XenForo.StatusTooltip');
  10113.     XenForo.register('.PreviewTooltip', 'XenForo.PreviewTooltip');
  10114.  
  10115.     XenForo.register('a.LbTrigger', 'XenForo.LightBoxTrigger');
  10116.  
  10117.     // Register click-proxy controls
  10118.     XenForo.register('.ClickProxy', 'XenForo.ClickProxy');
  10119.  
  10120.     // Register popup menu controls
  10121.     XenForo.register('.Popup', 'XenForo.PopupMenu', 'XenForoActivatePopups');
  10122.  
  10123.     // Register scrolly pagenav elements
  10124.     XenForo.register('.PageNav', 'XenForo.PageNav');
  10125.  
  10126.     // Register tabs
  10127.     XenForo.register('.Tabs', 'XenForo.Tabs');
  10128.  
  10129.     // Register square thumb cropper
  10130.     XenForo.register('.SquareThumbs', 'XenForo.SquareThumbs');
  10131.  
  10132.     // Handle all xenForms
  10133.     XenForo.register('form.xenForm, .MultiSubmitFix', 'XenForo.MultiSubmitFix');
  10134.  
  10135.     // Register check-all controls
  10136.     XenForo.register('input.CheckAll, a.CheckAll, label.CheckAll', 'XenForo.CheckAll');
  10137.  
  10138.     // Register auto-checker controls
  10139.     XenForo.register('input.AutoChecker', 'XenForo.AutoChecker');
  10140.  
  10141.     // Register toggle buttons
  10142.     XenForo.register('label.ToggleButton', 'XenForo.ToggleButton');
  10143.  
  10144.     // Register auto inline uploader controls
  10145.     XenForo.register('form.AutoInlineUploader', 'XenForo.AutoInlineUploader');
  10146.  
  10147.     // Register form auto validators
  10148.     XenForo.register('form.AutoValidator', 'XenForo.AutoValidator');
  10149.  
  10150.     // Register auto time zone selector
  10151.     XenForo.register('select.AutoTimeZone', 'XenForo.AutoTimeZone');
  10152.  
  10153.     // Register generic content loader
  10154.     XenForo.register('a.Loader, input.Loader', 'XenForo.Loader');
  10155.  
  10156.     var supportsStep = 'step' in document.createElement('input');
  10157.  
  10158.     // Register form controls
  10159.     XenForo.register('input, textarea', function (i) {
  10160.         var $this = $(this);
  10161.  
  10162.         switch ($this.attr('type')) {
  10163.             case 'hidden':
  10164.             case 'submit':
  10165.                 return;
  10166.             case 'checkbox':
  10167.             case 'radio':
  10168.                 // Register auto submitters
  10169.                 if ($this.hasClass('SubmitOnChange')) {
  10170.                     XenForo.create('XenForo.SubmitOnChange', this);
  10171.                 }
  10172.                 return;
  10173.         }
  10174.  
  10175.         // Spinbox / input[type=number]
  10176.         if ($this.attr('type') == 'number' && supportsStep) {
  10177.             // use the XenForo implementation instead, as browser implementations seem to be universally horrible
  10178.             this.type = 'text';
  10179.             $this.addClass('SpinBox number');
  10180.         }
  10181.         if ($this.hasClass('SpinBox')) {
  10182.             XenForo.create('XenForo.SpinBox', this);
  10183.         }
  10184.  
  10185.         // Prompt / placeholder
  10186.         if ($this.hasClass('Prompt')) {
  10187.             console.error('input.Prompt[title] is now deprecated. Please replace any instances with input[placeholder] and remove the Prompt class.');
  10188.             $this.attr({ placeholder: $this.attr('title'), title: '' });
  10189.         }
  10190.         if ($this.attr('placeholder')) {
  10191.             XenForo.create('XenForo.Prompt', this);
  10192.         }
  10193.  
  10194.         // LiveTitle
  10195.         if ($this.data('livetitletemplate')) {
  10196.             XenForo.create('XenForo.LiveTitle', this);
  10197.         }
  10198.  
  10199.         // DatePicker
  10200.         if ($this.is(':date')) {
  10201.             XenForo.create('XenForo.DatePicker', this);
  10202.         }
  10203.  
  10204.         // AutoComplete
  10205.         if ($this.hasClass('AutoComplete')) {
  10206.             XenForo.create('XenForo.AutoComplete', this);
  10207.         }
  10208.  
  10209.         // UserTagger
  10210.         if ($this.hasClass('UserTagger')) {
  10211.             XenForo.create('XenForo.UserTagger', this);
  10212.         }
  10213.  
  10214.         // AutoSelect
  10215.         if ($this.hasClass('AutoSelect')) {
  10216.             XenForo.create('XenForo.AutoSelect', this);
  10217.         }
  10218.  
  10219.         // AutoValidator
  10220.         if (XenForo.isAutoValidatorField(this)) {
  10221.             XenForo.create('XenForo.AutoValidatorControl', this);
  10222.         }
  10223.  
  10224.         if ($this.is('textarea.StatusEditor')) {
  10225.             XenForo.create('XenForo.StatusEditor', this);
  10226.         }
  10227.  
  10228.         // Register Elastic textareas
  10229.         if ($this.is('textarea.Elastic')) {
  10230.             XenForo.create('XenForo.TextareaElastic', this);
  10231.         }
  10232.     });
  10233.  
  10234.     // Register form previewer
  10235.     XenForo.register('form.Preview', 'XenForo.PreviewForm');
  10236.  
  10237.     // Register field adder
  10238.     XenForo.register('a.FieldAdder, input.FieldAdder', 'XenForo.FieldAdder');
  10239.  
  10240.     // Read status toggler
  10241.     XenForo.register('a.ReadToggle', 'XenForo.ReadToggle');
  10242.  
  10243.     XenForo.StarContent = function ($link) {
  10244.         var callback = function (ajaxData) {
  10245.             if (!XenForo.hasResponseError(ajaxData)) {
  10246.                 if ($link.hasClass('mainc')) {
  10247.                     $link.removeClass('mainc');
  10248.                     $link.attr('title', XenForo.phrases.addFave)
  10249.                 } else {
  10250.                     $link.addClass('mainc');
  10251.                     $link.attr('title', XenForo.phrases.removeFave)
  10252.                 }
  10253.             }
  10254.         };
  10255.  
  10256.         $link.on('click', function (e) {
  10257.             e.preventDefault();
  10258.             XenForo.ajax($link.attr('href'), {}, callback);
  10259.         });
  10260.     };
  10261.  
  10262.  
  10263.     XenForo.MarketBalanceSwitcher = function ($button) {
  10264.         this.__construct($button);
  10265.     };
  10266.  
  10267.     XenForo.MarketBalanceSwitcher.prototype =
  10268.     {
  10269.         __construct: function ($button) {
  10270.             this.$button = $button;
  10271.             this.showButtonFromCache();
  10272.             this.$button.on('click', $.context(this, 'switch'));
  10273.         },
  10274.  
  10275.         switch: function (e) {
  10276.             e.preventDefault();
  10277.             e.stopPropagation();
  10278.             XenForo.showMarketBalanceEnabled = !XenForo.showMarketBalanceEnabled;
  10279.  
  10280.             if (this.$button.hasClass('Enabled')) {
  10281.                 localStorage.removeItem('market_show_balance_enabled');
  10282.             } else {
  10283.                 localStorage.setItem('market_show_balance_enabled', '1');
  10284.             }
  10285.  
  10286.             this.$button.toggleClass('Enabled');
  10287.             $('#NavigationAccountUsername').toggleClass('hidden');
  10288.             $('#NavigationAccountBalance').toggleClass('hidden');
  10289.         },
  10290.  
  10291.         showButtonFromCache: function () {
  10292.             XenForo.showMarketBalanceEnabled = localStorage.hasOwnProperty('market_show_balance_enabled');
  10293.             if (XenForo.showMarketBalanceEnabled) {
  10294.                 this.$button.addClass('Enabled');
  10295.                 $('#NavigationAccountUsername').addClass('hidden');
  10296.                 $('#NavigationAccountBalance').removeClass('hidden');
  10297.             }
  10298.         }
  10299.     };
  10300.  
  10301.     XenForo.Modal = function ($content, options) {
  10302.         this.__construct($content, options)
  10303.     }
  10304.  
  10305.     XenForo.Modal.prototype = {
  10306.         __construct: function ($content, options) {
  10307.             this.opened = false
  10308.             this.$modal = $('<div class="modal fade" />').append($content)
  10309.             if (!options) options = {}
  10310.  
  10311.             if (options.top && options.top === 'center') {
  10312.                 $content.css('align-self', 'center')
  10313.             } else if (options.top) {
  10314.                 $content.css('top', options.top)
  10315.             }
  10316.  
  10317.             if (options.close)
  10318.                 this.$closeElems = this.$modal.find(options.close).on('click', $.proxy(this, 'hide'))
  10319.  
  10320.             this.$trigger = options.trigger || $($content)
  10321.  
  10322.             var self = this
  10323.             this.$modal.modal({
  10324.                 show: false,
  10325.                 zIndex: options.zIndex,
  10326.                 severalModals: options.severalModals,
  10327.                 preventClose: this.$trigger.data('unclosable') === true,
  10328.                 unloadAlert: this.$trigger.data('alertonclose') === true
  10329.             }).bind({
  10330.                 'show.bs.modal': function () {
  10331.                     self.opened = true
  10332.                     self.$trigger.trigger('onBeforeLoad')
  10333.                     options.onBeforeLoad && options.onBeforeLoad()
  10334.                 },
  10335.                 'shown.bs.modal': function () {
  10336.                     self.$trigger.trigger('onLoad')
  10337.                     options.onLoad && options.onLoad()
  10338.                 },
  10339.                 'hide.bs.modal': function () {
  10340.                     self.opened = false
  10341.                     self.$trigger.trigger('onBeforeClose')
  10342.                     options.onBeforeClose && options.onBeforeClose()
  10343.                 },
  10344.                 'hidden.bs.modal': function () {
  10345.                     self.$trigger.trigger('onClose')
  10346.                     options.onClose && options.onClose()
  10347.                 }
  10348.             })
  10349.  
  10350.             var overlayData = { // для обратной совместимости
  10351.                 load: function () { self.show(); return overlayData },
  10352.                 close: function () { self.hide(); return overlayData },
  10353.                 destroy: function () { self.destroy(); return overlayData },
  10354.                 getOverlay: function () { return $content },
  10355.                 getConf: function () { return options },
  10356.                 getTrigger: function () { return self.$trigger },
  10357.                 getClosers: function () { return self.$closeElems ? this.$closeElems : $() },
  10358.                 isOpened: function () { return self.opened }
  10359.             }
  10360.  
  10361.             this.$trigger.data('overlay', overlayData)
  10362.         },
  10363.  
  10364.         show: function () { this.$modal.modal('show'); },
  10365.         load: function () { this.show() },
  10366.         destroy: function () { this.$modal.remove() },
  10367.         hide: function () { this.$modal.modal('hide') }
  10368.     }
  10369.  
  10370.     XenForo.TimedMessage = function ($elem, timeout, callback) {
  10371.         this.__construct($elem, timeout, callback)
  10372.     }
  10373.  
  10374.     XenForo.TimedMessage.prototype = {
  10375.         __construct($elem, timeout, callback) {
  10376.             this.closed = false
  10377.             this.$elem = $elem instanceof $ ? $elem : $($elem)
  10378.             this.callback = callback
  10379.  
  10380.             $('body').append(this.$elem)
  10381.             this.$elem.css({
  10382.                 top: 0,
  10383.                 left: 0,
  10384.                 position: 'fixed',
  10385.                 display: 'block',
  10386.                 zIndex: 15000,
  10387.             }).on('click', $.proxy(this, 'close'))
  10388.             setTimeout($.proxy(this, 'close'), timeout)
  10389.         },
  10390.  
  10391.         close() {
  10392.             if (this.closed) return
  10393.             this.closed = true
  10394.             this.callback && this.callback()
  10395.  
  10396.             this.$elem.animate({ opacity: 0 }, 500, function () {
  10397.                 $(this).remove()
  10398.             })
  10399.         }
  10400.     }
  10401.  
  10402.     /**
  10403.      * Public-only registrations
  10404.      */
  10405.     if ($('html').hasClass('Public')) {
  10406.  
  10407.         XenForo.register('.StarContent', 'XenForo.StarContent');
  10408.  
  10409.         // Register the login bar handle
  10410.         //XenForo.register('#loginBar', 'XenForo.LoginBar');
  10411.  
  10412.         // Register the header search box
  10413.         XenForo.register('.QuickSearch', 'XenForo.QuickSearch');
  10414.  
  10415.         // Register attribution links
  10416.         XenForo.register('a.AttributionLink', 'XenForo.AttributionLink');
  10417.  
  10418.         // CAPTCHAS
  10419.         XenForo.register('#ReCaptcha', 'XenForo.ReCaptcha');
  10420.         XenForo.register('.NoCaptcha', 'XenForo.NoCaptcha');
  10421.         XenForo.register('#SolveMediaCaptcha', 'XenForo.SolveMediaCaptcha');
  10422.         XenForo.register('#KeyCaptcha', 'XenForo.KeyCaptcha');
  10423.         XenForo.register('#Captcha', 'XenForo.Captcha');
  10424.  
  10425.         // Resize large BB code images
  10426.         XenForo.register('img.bbCodeImage', 'XenForo.BbCodeImage');
  10427.  
  10428.         // Handle like/unlike links
  10429.         XenForo.register('a.LikeLink', 'XenForo.LikeLink');
  10430.  
  10431.         // Register node description tooltips
  10432.         if (!XenForo.isTouchBrowser()) {
  10433.             //XenForo.register('h3.nodeTitle a', 'XenForo.NodeDescriptionTooltip');
  10434.         }
  10435.  
  10436.         // Register visitor menu
  10437.         //XenForo.register('#AccountMenu', 'XenForo.AccountMenu');
  10438.         XenForo.register('#MarketBalanceSwitcher', 'XenForo.MarketBalanceSwitcher');
  10439.  
  10440.         // Register follow / unfollow links
  10441.         XenForo.register('a.FollowLink', 'XenForo.FollowLink');
  10442.  
  10443.         XenForo.register('li.PopupItemLink', 'XenForo.PopupItemLink');
  10444.  
  10445.         // Register notices
  10446.         XenForo.register('.Notices', 'XenForo.Notices');
  10447.  
  10448.         // Spoiler BB Code
  10449.         XenForo.register('button.Spoiler', 'XenForo.SpoilerBBCode');
  10450.     }
  10451.  
  10452.     // Register control disablers last so they disable anything added by other behaviours
  10453.     XenForo.register('input:checkbox.Disabler, input:radio.Disabler', 'XenForo.Disabler');
  10454.  
  10455.     // *********************************************************************
  10456.     var isScrolled = false;
  10457.     $(window).on('load', function () {
  10458.         if (isScrolled || !window.location.hash) {
  10459.             return;
  10460.         }
  10461.  
  10462.         var hash = window.location.hash.replace(/[^a-zA-Z0-9_-]/g, ''),
  10463.             $match = hash ? $('#' + hash) : $();
  10464.  
  10465.         if ($match.length) {
  10466.             $match.get(0).scrollIntoView(true);
  10467.         }
  10468.     });
  10469.  
  10470.     /**
  10471.      * Use jQuery to initialize the system
  10472.      */
  10473.     $(function () {
  10474.         XenForo.init();
  10475.  
  10476.         if (window.location.hash) {
  10477.             // do this after the document is ready as triggering it too early
  10478.             // causes the initial hash to trigger a scroll
  10479.             $(window).one('scroll', function (e) {
  10480.                 isScrolled = true;
  10481.             });
  10482.         }
  10483.  
  10484.         var scrollTrigger = 100; // px
  10485.         var backToTop = function () {
  10486.             var scrollTop = $(window).scrollTop();
  10487.             if (scrollTop > scrollTrigger) {
  10488.                 $('.cd-top').addClass('cd-is-visible');
  10489.             } else {
  10490.                 $('.cd-top').removeClass('cd-is-visible');
  10491.             }
  10492.         };
  10493.         $(window).on('load scroll', function () {
  10494.             backToTop();
  10495.         });
  10496.         $('.cd-top').on('click', function (e) {
  10497.             e.preventDefault();
  10498.             $('html,body').animate({
  10499.                 scrollTop: 0
  10500.             }, 350);
  10501.         });
  10502.  
  10503.         $('a[href^="#"]').on('click', function (e) {
  10504.             if (!$(this).hasClass('cd-top') && $(this.hash).offset()) {
  10505.                 e.preventDefault();
  10506.                 $('html,body').animate({ scrollTop: ($(this.hash).offset().top - 44) }, 500);
  10507.             }
  10508.         });
  10509.  
  10510.     })
  10511. }
  10512.     (jQuery, this, document);
  10513.  
  10514. +function ($) {
  10515.     'use strict';
  10516.  
  10517.     // MODAL CLASS DEFINITION
  10518.     // ======================
  10519.  
  10520.     var Modal = function (element, options) {
  10521.         this.options = options
  10522.         this.$body = $(document.body)
  10523.         this.$html = $('html')
  10524.         this.$element = $(element)
  10525.         this.$dialog = this.$element.find('.modal-dialog')
  10526.         this.$backdrop = null
  10527.         this.isShown = null
  10528.         this.originalBodyPad = null
  10529.         this.scrollbarWidth = 0
  10530.         this.ignoreBackdropClick = false
  10531.         this.fixedContent = ''; //'.navbar-fixed-top, .navbar-fixed-bottom, #navigation'
  10532.         this.zIndex = options.zIndex || 0
  10533.         this.zIndexOffset = 10001
  10534.         if (this.options.remote) {
  10535.             this.$element
  10536.                 .find('.modal-content')
  10537.                 .load(this.options.remote, $.proxy(function () {
  10538.                     this.$element.trigger('loaded.bs.modal')
  10539.                 }, this))
  10540.         }
  10541.  
  10542.         this.$element
  10543.             .css('z-index', this.zIndexOffset + 1 + this.zIndex * 2)
  10544.             .attr('data-z-index', this.zIndex)
  10545.     }
  10546.  
  10547.     Modal.VERSION = '3.4.1'
  10548.  
  10549.     Modal.TRANSITION_DURATION = 200
  10550.     Modal.BACKDROP_TRANSITION_DURATION = 150
  10551.  
  10552.     Modal.DEFAULTS = {
  10553.         backdrop: true,
  10554.         keyboard: true,
  10555.         show: true
  10556.     }
  10557.  
  10558.     Modal.prototype.toggle = function (_relatedTarget) {
  10559.         return this.isShown ? this.hide() : this.show(_relatedTarget)
  10560.     }
  10561.  
  10562.     Modal.prototype.show = function (_relatedTarget) {
  10563.         var $modal = $('.modal.in[data-z-index=' + this.zIndex + ']')
  10564.         if (this.options.severalModals) {
  10565.             this.showModal(_relatedTarget);
  10566.             var $fade = $('div.modal-backdrop.fade.in').last();
  10567.             $fade.css('z-index', parseInt($fade.css('z-index')) + 5);
  10568.             this.$element
  10569.                 .css('z-index', parseInt($fade.css('z-index')) + 6)
  10570.                 .attr('data-z-index', parseInt($fade.css('z-index')) + 6)
  10571.             return;
  10572.         }
  10573.         if ($modal.length) {
  10574.             $modal.data('bs.modal').hide(null, function () {
  10575.                 this.showModal(_relatedTarget)
  10576.             }.bind(this))
  10577.         } else this.showModal(_relatedTarget)
  10578.     }
  10579.  
  10580.     Modal.prototype.showModal = function (_relatedTarget) {
  10581.         var that = this
  10582.  
  10583.         if (this.isShown) return
  10584.  
  10585.         this.isShown = true
  10586.  
  10587.         this.checkScrollbar()
  10588.         this.setScrollbar()
  10589.         this.$html.addClass('modal-open')
  10590.  
  10591.         if (this.options.unloadAlert) {
  10592.             window.onbeforeunload = function () {
  10593.                 return true;
  10594.             };
  10595.         }
  10596.  
  10597.         if (!this.options.preventClose) this.escape()
  10598.         this.resize()
  10599.  
  10600.         //this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
  10601.         this.$dialog.on('mousedown.dismiss.bs.modal', function () {
  10602.             that.$element.one('mouseup.dismiss.bs.modal', function (e) {
  10603.                 if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
  10604.             })
  10605.         })
  10606.  
  10607.         if (!this.$element.parent().length) {
  10608.             this.$element.appendTo(that.$body) // don't move modals dom position
  10609.         }
  10610.  
  10611.         var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
  10612.         this.$element.trigger(e)
  10613.  
  10614.         this.backdrop(function () {
  10615.             var transition = that.$element.hasClass('fade')
  10616.  
  10617.             that.$element
  10618.                 .show()
  10619.                 .scrollTop(0)
  10620.  
  10621.             that.adjustDialog()
  10622.  
  10623.             if (transition) {
  10624.                 that.$element[0].offsetWidth // force reflow
  10625.             }
  10626.  
  10627.             that.$element.addClass('in')
  10628.  
  10629.             that.enforceFocus()
  10630.  
  10631.             var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
  10632.             that.$element.find('.overlayScroll').addClass('scrollbar-macosx scrollbar-dynamic').scrollbar()
  10633.             that.$element.attr('tabindex', '-1').css('outline', 'none')
  10634.             transition ?
  10635.                 setTimeout(function () {
  10636.                     that.$element.trigger('focus').trigger(e)
  10637.                 }, Modal.TRANSITION_DURATION) :
  10638.                 that.$element.trigger('focus').trigger(e)
  10639.         })
  10640.     }
  10641.  
  10642.     Modal.prototype.hide = function (e, cb) {
  10643.         if (e) e.preventDefault()
  10644.  
  10645.         e = $.Event('hide.bs.modal')
  10646.  
  10647.         this.$element.trigger(e)
  10648.  
  10649.         if (!this.isShown || e.isDefaultPrevented()) return
  10650.  
  10651.         this.isShown = false
  10652.  
  10653.         this.escape()
  10654.         this.resize()
  10655.  
  10656.         window.onbeforeunload = null;
  10657.  
  10658.         $(document).off('focusin.bs.modal')
  10659.  
  10660.         this.$element
  10661.             .removeClass('in')
  10662.             .off('click.dismiss.bs.modal')
  10663.             .off('mouseup.dismiss.bs.modal')
  10664.  
  10665.         this.$dialog.off('mousedown.dismiss.bs.modal')
  10666.  
  10667.         setTimeout(function () {
  10668.             this.hideModal(cb)
  10669.         }.bind(this), Modal.TRANSITION_DURATION)
  10670.     }
  10671.  
  10672.     Modal.prototype.enforceFocus = function () {
  10673.         $(document)
  10674.             .off('focusin.bs.modal') // guard against infinite focus loop
  10675.             .on('focusin.bs.modal', $.proxy(function (e) {
  10676.                 if (document !== e.target &&
  10677.                     this.$element[0] !== e.target &&
  10678.                     !this.$element.has(e.target).length) {
  10679.                     this.$element.trigger('focus')
  10680.                 }
  10681.             }, this))
  10682.     }
  10683.  
  10684.     Modal.prototype.escape = function () {
  10685.         if (this.isShown && this.options.keyboard) {
  10686.             this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
  10687.                 e.which == 27 && this.hide()
  10688.             }, this))
  10689.         } else if (!this.isShown) {
  10690.             this.$element.off('keydown.dismiss.bs.modal')
  10691.         }
  10692.     }
  10693.  
  10694.     Modal.prototype.resize = function () {
  10695.         if (this.isShown) {
  10696.             $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
  10697.         } else {
  10698.             $(window).off('resize.bs.modal')
  10699.         }
  10700.     }
  10701.  
  10702.     Modal.prototype.hideModal = function (cb) {
  10703.         var that = this
  10704.         this.$element.hide()
  10705.         this.backdrop(function () {
  10706.             if (!$('.modal.in:visible').length)
  10707.                 that.$html.removeClass('modal-open')
  10708.             that.resetAdjustments()
  10709.             that.resetScrollbar()
  10710.             that.$element.trigger('hidden.bs.modal')
  10711.             let $modal = $('.modal.in:visible');
  10712.             if ($modal.length) {
  10713.                 $modal.attr('tabindex', '-1')
  10714.                 $modal.trigger('focus')
  10715.             }
  10716.             $('.modal.in:hidden').removeClass('in')
  10717.             if (cb) cb()
  10718.         })
  10719.     }
  10720.  
  10721.     Modal.prototype.removeBackdrop = function () {
  10722.         this.$backdrop && this.$backdrop.remove()
  10723.         this.$backdrop = null
  10724.     }
  10725.  
  10726.     Modal.prototype.backdrop = function (callback) {
  10727.         var that = this
  10728.         var animate = this.$element.hasClass('fade') ? 'fade' : ''
  10729.  
  10730.         if (this.isShown && this.options.backdrop) {
  10731.             var doAnimate = animate
  10732.  
  10733.             this.$backdrop = $(document.createElement('div'))
  10734.                 .addClass('modal-backdrop ' + animate)
  10735.                 .appendTo(this.$body)
  10736.                 .css('z-index', this.zIndexOffset + this.zIndex * 2)
  10737.  
  10738.             if (!this.options.preventClose) {
  10739.                 this.$element.on('mousedown.dismiss.bs.modal touchstart.dismiss.bs.modal', $.proxy(function (e) {
  10740.                     if (this.ignoreBackdropClick) {
  10741.                         this.ignoreBackdropClick = false
  10742.                         return
  10743.                     }
  10744.                     if (e.target !== e.currentTarget) return
  10745.  
  10746.  
  10747.  
  10748.                     if (this.$element[0].scrollHeight > document.documentElement.clientHeight && e.clientX > this.$backdrop.width() - this.scrollbarWidth)
  10749.                         return
  10750.                     this.options.backdrop == 'static'
  10751.                         ? this.$element[0].focus()
  10752.                         : this.hide()
  10753.                 }, this))
  10754.             }
  10755.             if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
  10756.  
  10757.             this.$backdrop.addClass('in')
  10758.  
  10759.             if (!callback) return
  10760.             if (!doAnimate) return callback()
  10761.  
  10762.             setTimeout(function () {
  10763.                 callback()
  10764.             }.bind(this), Modal.BACKDROP_TRANSITION_DURATION)
  10765.  
  10766.         } else if (!this.isShown && this.$backdrop) {
  10767.             this.$backdrop.removeClass('in')
  10768.  
  10769.             var callbackRemove = function () {
  10770.                 that.removeBackdrop()
  10771.                 callback && callback()
  10772.             }
  10773.  
  10774.             if (this.$element.hasClass('fade'))
  10775.                 setTimeout(function () {
  10776.                     callbackRemove()
  10777.                 }.bind(this), Modal.BACKDROP_TRANSITION_DURATION)
  10778.  
  10779.         } else if (callback) {
  10780.             callback()
  10781.         }
  10782.     }
  10783.  
  10784.     // these following methods are used to handle overflowing modals
  10785.  
  10786.     Modal.prototype.handleUpdate = function () {
  10787.         this.adjustDialog()
  10788.     }
  10789.  
  10790.     Modal.prototype.adjustDialog = function () {
  10791.         var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
  10792.  
  10793.         this.$element.css({
  10794.             paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
  10795.             paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
  10796.         })
  10797.     }
  10798.  
  10799.     Modal.prototype.resetAdjustments = function () {
  10800.         this.$element.css({
  10801.             paddingLeft: '',
  10802.             paddingRight: ''
  10803.         })
  10804.     }
  10805.  
  10806.     Modal.prototype.checkScrollbar = function () {
  10807.         var fullWindowWidth = window.innerWidth
  10808.         if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
  10809.             var documentElementRect = document.documentElement.getBoundingClientRect()
  10810.             fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
  10811.         }
  10812.         this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
  10813.         this.scrollbarWidth = this.measureScrollbar()
  10814.     }
  10815.  
  10816.     Modal.prototype.setScrollbar = function () {
  10817.         if ($('.modal.in:visible').length) return;
  10818.  
  10819.         var bodyPad = parseInt((this.$html.css('padding-right') || 0), 10)
  10820.         this.originalBodyPad = document.body.style.paddingRight || ''
  10821.         var scrollbarWidth = this.scrollbarWidth
  10822.         if (this.bodyIsOverflowing) {
  10823.             this.$html.css('padding-right', bodyPad + scrollbarWidth)
  10824.             $(this.fixedContent).each(function (index, element) {
  10825.                 var actualPadding = element.style.paddingRight
  10826.                 var calculatedPadding = $(element).css('padding-right')
  10827.                 $(element)
  10828.                     .data('padding-right', actualPadding)
  10829.                 //.css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')
  10830.             })
  10831.             if ($('#ChatboxStartIcon').length)
  10832.                 $('#ChatboxStartIcon')
  10833.                     .css('transition', 'none')
  10834.                     .css('right', (15 + scrollbarWidth) + 'px')
  10835.             if ($('#completeChatbox').length)
  10836.                 $('#completeChatbox')
  10837.                     .data('right', $('#completeChatbox').css('right'))
  10838.                     .css('right', (parseInt($('#completeChatbox').css('right')) + scrollbarWidth) + 'px')
  10839.         }
  10840.     }
  10841.  
  10842.     Modal.prototype.resetScrollbar = function () {
  10843.         if ($('.modal.in:visible').length) return;
  10844.  
  10845.         this.$html.css('padding-right', this.originalBodyPad)
  10846.         $(this.fixedContent).each(function (index, element) {
  10847.             var padding = $(element).data('padding-right')
  10848.             $(element).removeData('padding-right')
  10849.             element.style.paddingRight = padding ? padding : ''
  10850.         })
  10851.         if ($('#ChatboxStartIcon').length) {
  10852.             $('#ChatboxStartIcon').css('right', '15px')
  10853.             setTimeout(function () {
  10854.                 $('#ChatboxStartIcon').css('transition', '.2s')
  10855.             }, 250)
  10856.         }
  10857.         if ($('#completeChatbox').length)
  10858.             $('#completeChatbox').css('right', $('#completeChatbox').data('right'))
  10859.     }
  10860.  
  10861.     Modal.prototype.measureScrollbar = function () {
  10862.         return window.innerWidth - document.documentElement.getBoundingClientRect().width
  10863.     }
  10864.  
  10865.  
  10866.     // MODAL PLUGIN DEFINITION
  10867.     // =======================
  10868.  
  10869.     function Plugin(option, _relatedTarget) {
  10870.         return this.each(function () {
  10871.             var $this = $(this)
  10872.             var data = $this.data('bs.modal')
  10873.  
  10874.             var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
  10875.             if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
  10876.             if (typeof option == 'string') data[option](_relatedTarget)
  10877.             else if (options.show) data.show(_relatedTarget)
  10878.         })
  10879.     }
  10880.  
  10881.     var old = $.fn.modal
  10882.  
  10883.     $.fn.modal = Plugin
  10884.     $.fn.modal.Constructor = Modal
  10885.  
  10886.  
  10887.     // MODAL NO CONFLICT
  10888.     // =================
  10889.  
  10890.     $.fn.modal.noConflict = function () {
  10891.         $.fn.modal = old
  10892.         return this
  10893.     }
  10894.  
  10895.  
  10896.     // MODAL DATA-API
  10897.     // ==============
  10898.  
  10899.     $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
  10900.         var $this = $(this)
  10901.         var href = $this.attr('href')
  10902.         var target = $this.attr('data-target') ||
  10903.             (href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
  10904.  
  10905.         var $target = $(document).find(target)
  10906.         var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
  10907.  
  10908.         if ($this.is('a')) e.preventDefault()
  10909.  
  10910.         $target.one('show.bs.modal', function (showEvent) {
  10911.             if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
  10912.             $target.one('hidden.bs.modal', function () {
  10913.                 $this.is(':visible') && $this.trigger('focus')
  10914.             })
  10915.         })
  10916.         Plugin.call($target, option, this)
  10917.     })
  10918.  
  10919.     XenForo.SmiliesPicker = function ($element) {
  10920.         this.__construct($element);
  10921.     };
  10922.  
  10923.     XenForo.SmiliesPicker.prototype =
  10924.     {
  10925.         __construct: function ($element) {
  10926.             console.log("[Smilies] Loading smilies for", $element)
  10927.             this.$element = $element;
  10928.             if (this.$element.get(0)._tippy !== undefined) {
  10929.                 this.$element.get(0)._tippy.destroy()
  10930.             }
  10931.             this.$element.on('mousedown', function (e) {
  10932.                 e.preventDefault()
  10933.             })
  10934.             this.$element.off('click')
  10935.             this.$smilies = undefined;
  10936.             this.localStorageKey = XenForo.smiliesCacheKey;
  10937.             this.smiliesLoaded = false;
  10938.             this.recentlyUsedLimit = 14;
  10939.             let ownerDocument = document.implementation.createHTMLDocument('virtual'); // Used to edit img tags without loading them
  10940.             if (supports_html5_storage()) {
  10941.                 let temp = localStorage.getItem(this.localStorageKey);
  10942.                 if (temp && temp.startsWith("<")) {
  10943.                     this.cleanSmiliesCache();
  10944.                     this.smiliesBox = $(temp, ownerDocument);
  10945.                     this.smiliesBox.find('img').map(function () {
  10946.                         $(this).attr("data-src", $(this).attr("src"));
  10947.                         $(this).removeAttr('src');
  10948.                         $(this).css({ 'display': 'none' });
  10949.                     })
  10950.                     this.createSmiliesBox();
  10951.                     this.smiliesLoaded = true;
  10952.                 }
  10953.             }
  10954.  
  10955.             if (!this.smiliesLoaded) {
  10956.                 XenForo.ajax(
  10957.                     XenForo._editorSmiliesUrl,
  10958.                     {},
  10959.                     function (ajaxData) {
  10960.                         if (XenForo.hasResponseError(ajaxData)) {
  10961.                             return;
  10962.                         }
  10963.  
  10964.                         if (ajaxData.templateHtml) {
  10965.                             new XenForo.ExtLoader(ajaxData, function () {
  10966.                                 let recentlyUsedSmilies = this.getRecentlyUsedSmiliesFromCache();
  10967.                                 this.cleanSmiliesCache();
  10968.                                 this.smiliesBox = $(ajaxData.templateHtml, ownerDocument);
  10969.                                 this.smiliesBox.find('img').map(function () {
  10970.                                     $(this).attr("data-src", $(this).attr("src"));
  10971.                                     $(this).removeAttr('src');
  10972.                                     $(this).css({ 'display': 'none' });
  10973.                                 })
  10974.  
  10975.                                 if (recentlyUsedSmilies) {
  10976.                                     this.smiliesBox.find('.RecentlyUsedSmilies').html(recentlyUsedSmilies).removeClass('hidden');
  10977.                                 }
  10978.                                 if (supports_html5_storage()) {
  10979.                                     try {
  10980.                                         localStorage.setItem(this.localStorageKey, this.smiliesBox[0].outerHTML);
  10981.                                     } catch (e) {
  10982.                                         this.clearLocalStorage(256);
  10983.                                         localStorage.setItem(this.localStorageKey, this.smiliesBox[0].outerHTML);
  10984.                                     }
  10985.                                 }
  10986.                                 this.createSmiliesBox()
  10987.                             }.bind(this));
  10988.                         }
  10989.                     }.bind(this)
  10990.                 );
  10991.             }
  10992.         },
  10993.  
  10994.         cleanSmiliesCache: function () {
  10995.             let values = Object.keys(localStorage)
  10996.                 .filter((key) => key !== this.localStorageKey && !isNaN(+new Date(parseInt(key) * 1000)) && localStorage.getItem(key).indexOf('<li class="Smilie') !== -1);
  10997.  
  10998.             values.forEach(function (item, i, arr) {
  10999.                 localStorage.removeItem(item);
  11000.             });
  11001.         },
  11002.  
  11003.         locateSmiliesElement: function () {
  11004.             if (this.$element.closest('.input').length) {
  11005.                 return this.$element.closest('.input').find('#smiliesBox');
  11006.             } else if (this.$element.closest('.commentSubmit').length) {
  11007.                 if (!this.$element.closest('.commentSubmit').find('.redactor_smilies').length) {
  11008.                     this.$element.closest('.commentSubmit').prepend($('<div class="redactor_smilies" />'));
  11009.                 }
  11010.                 return this.$element.closest('.commentSubmit').find('.redactor_smilies');
  11011.             } else if (this.$element.closest('.redactor_box').length) {
  11012.                 if (!this.$element.closest('.redactor_box').parent().find('.redactor_smilies').length) {
  11013.                     this.$element.closest('.redactor_box').parent().prepend($('<div class="redactor_smilies" />'));
  11014.                 }
  11015.                 return this.$element.closest('.redactor_box').parent().find('.redactor_smilies');
  11016.             } else if (this.$element.closest('form').length) {
  11017.                 if (!this.$element.closest('form').find('.redactor_smilies').length) {
  11018.                     this.$element.closest('form').prepend($('<div class="redactor_smilies" />'));
  11019.                 }
  11020.                 return this.$element.closest('form').find('.redactor_smilies');
  11021.             }
  11022.         },
  11023.  
  11024.         locateInputElement: function () {
  11025.             if (this.$element.parent().find('input[type=text]').length) {
  11026.                 return this.$element.parent().find('input[type=text]');
  11027.             } else if (this.$element.closest('.commentSubmit').length) {
  11028.                 return this.$element.closest('.commentSubmit');
  11029.             } else if (this.$element.closest('.redactor_box').length) {
  11030.                 return this.$element.closest('.redactor_box').parent();
  11031.             } else if (this.$element.closest('form').length) {
  11032.                 return this.$element.closest('form');
  11033.             }
  11034.         },
  11035.  
  11036.         setContent: function (content) {
  11037.             if (XenForo.isTouchBrowser()) {
  11038.                 this.$smilies.html(content.children()).xfActivate()
  11039.             } else {
  11040.                 this.$element.get(0)._tippy.setContent(content)
  11041.             }
  11042.             this.bind();
  11043.         },
  11044.  
  11045.         getRecentlyUsedSmiliesFromCache: function () {
  11046.             let values = Object.keys(localStorage)
  11047.                 .filter((key) => key !== this.localStorageKey && !isNaN(+new Date(parseInt(key) * 1000)) && localStorage.getItem(key).indexOf('<li class="Smilie') !== -1);
  11048.  
  11049.             let returnValue = null;
  11050.             values.forEach(function (item, i, arr) {
  11051.                 returnValue = $(localStorage.getItem(item)).find('.RecentlyUsedSmilies').html();
  11052.             });
  11053.  
  11054.             return returnValue;
  11055.         },
  11056.  
  11057.         addSmilieToRecentlyUsed: function ($smilie) {
  11058.             let selector = '.' + $smilie.attr('class').replace(' ', '.');
  11059.             let $cachedSmilie = this.$smilies.find('.RecentlyUsedSmilies').find(selector);
  11060.             if ($cachedSmilie.length) {
  11061.                 $cachedSmilie.remove();
  11062.             }
  11063.             let $clone = $smilie.clone();
  11064.  
  11065.             $clone.xfInsert('prependTo', this.$smilies.find('.RecentlyUsedSmilies').find('ul'), 'show', 0, function () {
  11066.                 $clone.css('display', '');
  11067.                 this.$smilies.find('.RecentlyUsedSmilies').removeClass('hidden');
  11068.                 this.$smilies.find('.RecentlyUsedSmilies .Smilie').off('click').slice(this.recentlyUsedLimit).remove();
  11069.                 if (supports_html5_storage()) {
  11070.                     this.$save = this.$smilies.clone()
  11071.                     this.$save.find('img').map(function () {
  11072.                         $(this).attr("data-src", $(this).attr("src"));
  11073.                         $(this).removeAttr('src');
  11074.                         $(this).css({ 'display': 'none' });
  11075.                     })
  11076.                     try {
  11077.                         localStorage.setItem(this.localStorageKey, this.$save.html());
  11078.                     } catch (e) {
  11079.                         this.clearLocalStorage(256);
  11080.                         localStorage.setItem(this.localStorageKey, this.$save.html());
  11081.                     }
  11082.                 }
  11083.             }.bind(this));
  11084.         },
  11085.  
  11086.         appendToInput: function ($smilie) {
  11087.             let $input = this.locateInputElement()
  11088.             if ($input.is("input")) {
  11089.                 $input.val($input.val() + $smilie.find('img').attr('alt')).focus();
  11090.             } else {
  11091.                 let $ed = XenForo.getEditorInForm($input);
  11092.                 $ed.execCommand('inserthtml', $smilie.html().trim());
  11093.                 $ed.focus();
  11094.             }
  11095.         },
  11096.  
  11097.         createSmiliesBox: function () {
  11098.             if (XenForo.isTouchBrowser()) {
  11099.                 this.loadMobile();
  11100.             } else {
  11101.                 this.loadDesktop();
  11102.             }
  11103.         },
  11104.  
  11105.         updateSmiliesKits: function ($el) {
  11106.             $el.addClass('updated-smilies')
  11107.             const $nav = $('<div class="smilies-nav"><div class="smilies-nav-container"></div></div>')
  11108.             $nav.find('.smilies-nav-container')
  11109.                 .append($el.find('.navigation').children().clone())
  11110.             $el.find('.navigation').css('display', 'none');
  11111.             $el.append($nav)
  11112.         },
  11113.  
  11114.         loadDesktop: function () {
  11115.             this.smiliesBox.find('img').map(function () {
  11116.                 $(this).css({ 'display': 'none' });
  11117.             })
  11118.  
  11119.             let content = this.smiliesBox[0].outerHTML
  11120.             let d = document.createElement('div');
  11121.             let contentSelector = 'chatboxSmilies';
  11122.             $(d).attr('id', contentSelector).addClass('simpleRedactor--smiliesBox').hide();
  11123.             $(d).html(content).show();
  11124.             this.updateSmiliesKits($(d))
  11125.             this.$smilies = $(d);
  11126.             let isFirstShown = true;
  11127.             let $content = $('<div>').append(this.$smilies.clone());
  11128.  
  11129.             this._tippy = tippy(this.$element.get(0), {
  11130.                 content: $content.html(),
  11131.                 theme: 'popup',
  11132.                 interactive: true,
  11133.                 arrow: true,
  11134.                 animation: 'shift-toward',
  11135.                 hideOnClick: false,
  11136.                 zIndex: this.$element.closest('.xenOverlay').length || this.$element.closest('.StackAlerts').length ? 11111 : 9999,
  11137.                 onHidden: function () {
  11138.                     this.$smilies.find('ul.smilieContainer').get(0).scrollTop = 0;
  11139.                 }.bind(this),
  11140.                 onShown: function (element) {
  11141.                     this.$smilies = $(element.popper).find('#chatboxSmilies');
  11142.                     // If element position is over real page size
  11143.                     var picker = $('.tippy-popper')[0].getBoundingClientRect()
  11144.                     if (window.innerWidth < (picker.x + picker.width)) {
  11145.                         var amount = picker.x + picker.width - window.innerWidth + 20
  11146.                         $('.tippy-popper').css('margin-left', '-' + amount + 'px').css('margin-right', amount + 'px')
  11147.                     } else {
  11148.                         if (window.innerWidth > (picker.x + picker.width + parseInt($('.tippy-popper').css('margin-right'), 10))) {
  11149.                             $('.tippy-popper').css('margin-left', '0px').css('margin-right', '0px')
  11150.                         }
  11151.                     }
  11152.  
  11153.                     if (!isFirstShown) return;
  11154.                     // Lazy loading
  11155.                     $content.find('img').map(function () {
  11156.                         $(this).attr('src', $(this).data("src"));
  11157.                         $(this).css({ 'display': '' });
  11158.                     })
  11159.                     this.$element.get(0)._tippy.setContent($content.html());
  11160.                     this.$smilies = $(element.popper).find('#chatboxSmilies');
  11161.                     $('.smilies-nav-container')
  11162.                         .addClass('scrollbar-macosx scrollbar-dynamic').scrollbar()
  11163.                         .siblings('.scroll-x').find('.scroll-bar').css('left', 0);
  11164.                     this.bind()
  11165.                     isFirstShown = false;
  11166.                 }.bind(this)
  11167.             });
  11168.  
  11169.             let $input = this.locateInputElement()
  11170.             if (!$input.is("input")) {
  11171.                 $input = XenForo.getEditorInForm($input).$editor;
  11172.                 if (!$input) {
  11173.                     return
  11174.                 }
  11175.             }
  11176.             $input.on('keydown', function (e) {
  11177.                 if (e.keyCode === 9 && !!this._tippy) {
  11178.                     e.preventDefault();
  11179.                     if (this._tippy.state.isShown) {
  11180.                         this._tippy.hide()
  11181.                     } else {
  11182.                         this._tippy.show()
  11183.                     }
  11184.                 }
  11185.             }.bind(this))
  11186.         },
  11187.  
  11188.         slideSmilies: function () {
  11189.             this.$smilies.slideToggle();
  11190.  
  11191.             const scrollElement = $('body,html')
  11192.             const scrollTop = $(".smilieContainer").offset().top - 200
  11193.             if (Math.abs(scrollElement.scrollTop() - scrollTop) > 100) {
  11194.                 scrollElement.animate({ scrollTop }, { duration: 450, queue: false });
  11195.             }
  11196.         },
  11197.  
  11198.         loadMobile: function () {
  11199.             this.$element.off('click').click(function (e) {
  11200.                 e.preventDefault();
  11201.                 e.stopImmediatePropagation();
  11202.                 this.$smilies = this.locateSmiliesElement();
  11203.  
  11204.                 if (!this.$smilies.length) {
  11205.                     return;
  11206.                 }
  11207.  
  11208.                 if (this.$smilies.children().length) {
  11209.                     this.slideSmilies();
  11210.                     return;
  11211.                 }
  11212.  
  11213.                 this.smiliesBox.find('img').map(function () {
  11214.                     $(this).attr('src', $(this).data("src"));
  11215.                     $(this).css({ 'display': '' });
  11216.                 })
  11217.  
  11218.                 this.$smilies.html(this.smiliesBox[0].outerHTML)
  11219.                 this.$smilies.hide();
  11220.                 this.bind();
  11221.                 this.slideSmilies();
  11222.             }.bind(this));
  11223.         },
  11224.  
  11225.         bind: function () {
  11226.             this.$smilies.xfActivate();
  11227.             this.$smilies.off('click').on('click', '.Smilie', function (e) {
  11228.                 let target = e.target.closest('li') || e.target;
  11229.                 this.appendToInput($(target));
  11230.  
  11231.                 if (!$(target).closest('.RecentlyUsedSmilies').length) {
  11232.                     this.addSmilieToRecentlyUsed($(target));
  11233.                 }
  11234.  
  11235.                 const acResults = $('.autoCompleteListSmilies:visible')
  11236.                 if (acResults && acResults.length) {
  11237.                     acResults.data('XenForo.AutoSmiliesCompleteResults').hideResults()
  11238.                 }
  11239.                 return true;
  11240.             }.bind(this));
  11241.             const kitsPositions = []
  11242.             this.$smilies.find('.NavigationSmilie').each(function () {
  11243.                 const elem = $($(this).data("id").replace("Id", ""))
  11244.                 const parent = $("ul.smilieContainer")
  11245.                 kitsPositions.push([elem.attr('id'), elem.offset().top - parent.offset().top])
  11246.                 const self = $(this)
  11247.                 self.off('click').on('click', function (e) {
  11248.                     $('.smilies-nav-container li.active-kit').removeClass('active-kit')
  11249.                     self.addClass('active-kit')
  11250.                     parent.scrollTop(parent.scrollTop() + (elem.offset().top - parent.offset().top));
  11251.                 });
  11252.             });
  11253.  
  11254.             if (!XenForo.isTouchBrowser()) {
  11255.                 const navContainer = $(".smilies-nav-container")[1]
  11256.                 navContainer.addEventListener("wheel", e => {
  11257.                     e.preventDefault();
  11258.                     navContainer.scrollLeft += (Math.abs(e.deltaY) > Math.abs(e.deltaX) ? e.deltaY : e.deltaX) * 0.5;
  11259.                 });
  11260.  
  11261.                 let prev_active_kit = 'smilieCategory--0'
  11262.                 this.$smilies.find('.smilieContainer').off('scroll').on('scroll', function (e) {
  11263.                     const scroll_pos = $(this).scrollTop()
  11264.                     kitsPositions.some((item, index) => {
  11265.                         if (scroll_pos < item[1]) {
  11266.                             const active_kit = kitsPositions[index === 0 ? 0 : index - 1][0]
  11267.                             if (active_kit !== prev_active_kit) {
  11268.                                 prev_active_kit = active_kit
  11269.  
  11270.                                 const parent = $('.smilies-nav-container')
  11271.                                 const elem = $('.smilies-nav-container li[data-id="#' + active_kit.replace('--', 'Id--') + '"]')
  11272.                                 $('.smilies-nav-container li.active-kit').removeClass('active-kit')
  11273.                                 elem.addClass('active-kit')
  11274.                                 parent.animate({
  11275.                                     scrollLeft: parent.scrollLeft() + (elem.offset().left - parent.offset().left)
  11276.                                 }, { duration: 200, queue: false });
  11277.                             }
  11278.                             return true
  11279.                         }
  11280.                     })
  11281.                 })
  11282.             }
  11283.         },
  11284.  
  11285.         clearLocalStorage: function (counter) {
  11286.             if (this.getFreeLocalStorageSize(counter) <= 256) { // kB
  11287.                 let values = Object.keys(localStorage)
  11288.                     .filter((key) => key.startsWith('market_search_bar'));
  11289.                 for (let i = 0; i < values.length; i++) {
  11290.                     localStorage.removeItem(values[i])
  11291.                     if (this.getFreeLocalStorageSize(counter) >= 512) {
  11292.                         return
  11293.                     }
  11294.                 }
  11295.             }
  11296.         },
  11297.  
  11298.         getFreeLocalStorageSize: function (counter) {
  11299.             localStorage.removeItem('XenForo');
  11300.             if (localStorage && !localStorage.getItem('XenForo')) {
  11301.                 let i = 0;
  11302.                 try {
  11303.                     for (i = counter; i <= 10000; i += counter) {
  11304.                         localStorage.setItem('XenForo', new Array((i * 1024) + 1).join('a'));
  11305.                     }
  11306.                 } catch (e) {
  11307.                     localStorage.removeItem('XenForo');
  11308.                     return i - counter;
  11309.                 }
  11310.             }
  11311.         },
  11312.     };
  11313.  
  11314.     XenForo.DisableButton = function ($el) {
  11315.         var $spinner = $('<i class="fa fa-spinner fa-spin"></i>').hide().appendTo($el)
  11316.         var loading = false
  11317.         $el.on('click', function (e) {
  11318.             if (loading) e.preventDefault()
  11319.         })
  11320.         $el.on('BeforeOverlayTrigger', function (e) {
  11321.             if (loading) e.preventDefault()
  11322.             loading = true
  11323.             $spinner.show()
  11324.             $el.addClass('disabled')
  11325.         })
  11326.         $(document).on('OverlayOpening', function () {
  11327.             if (!loading) return;
  11328.             loading = false
  11329.             $spinner.hide()
  11330.             $el.removeClass('disabled')
  11331.         })
  11332.     }
  11333.     XenForo.register('.DisableButton', 'XenForo.DisableButton')
  11334.  
  11335.     XenForo.register('.smiliePicker', 'XenForo.SmiliesPicker');
  11336.     XenForo.register('.SmiliesTooltip', 'XenForo.SmiliesPicker');
  11337.     XenForo.register('.redactor_btn_smilies', 'XenForo.SmiliesPicker');
  11338.     XenForo.register('.redactor_btn_container_smilies', 'XenForo.SmiliesPicker');
  11339.     XenForo.register('span.ImNotification--Smiliepicker', 'XenForo.SmiliesPicker');
  11340.  
  11341.     // *********************************************************************
  11342.  
  11343.     XenForo.ReportForm = function ($element) {
  11344.         $element.find('input[type="radio"]').labelauty();
  11345.         $element.find('.reportReasons > label').on("click", function (e) {
  11346.             e.stopPropagation();
  11347.             e.stopImmediatePropagation();
  11348.             e.preventDefault();
  11349.             $element.find('._insertHereReason').val($(this).find('.labelauty-checked').text());
  11350.             $element.find('input[name="is_common_reason"]').val("1");
  11351.             $element.trigger('submit');
  11352.         });
  11353.     };
  11354.  
  11355.     XenForo.register('._reportForm', 'XenForo.ReportForm');
  11356.  
  11357.     // *********************************************************************
  11358.  
  11359.     XenForo.SuggestThreads = function ($selector) {
  11360.         XenForo.scriptLoader.loadCss(['discussion_list'], "css.php?css=__sentinel__&style=9&_v=" + XenForo._jsVersion, function () {
  11361.             this.__construct($selector);
  11362.         }.bind(this), function () { });
  11363.     };
  11364.  
  11365.     XenForo.SuggestThreads.prototype = {
  11366.         __construct: function ($select) {
  11367.             if (!$select.hasClass('SuggestThreads')) return;
  11368.             this.$input = $select;
  11369.             this.$element = $select.closest('.titleUnit');
  11370.  
  11371.             let $fieldset = $select.closest('fieldset');
  11372.             $fieldset.find('dl').each((_, element) => {
  11373.                 if (!$(element).hasClass('fullWidth'))
  11374.                     $fieldset.before($(element));
  11375.             })
  11376.             $fieldset.parent().xfActivate();
  11377.  
  11378.             let timer;
  11379.             $('<div class="discussionList" style="padding: 0px !important;"><div class="latestThreads _insertLoadedContent"/></div>').appendTo(this.$element);
  11380.             this.$input.on('keyup', function () {
  11381.                 clearTimeout(timer);
  11382.                 timer = setTimeout(() => { this.hitEdit() }, 400);
  11383.             }.bind(this))
  11384.         },
  11385.  
  11386.         hitEdit: function () {
  11387.             let value = this.$input.val().trim();
  11388.             let counter = 0;
  11389.             if (!value) return $('._insertLoadedContent').css('padding-top', '0px').html('');
  11390.             XenForo.ajax(window.location.href.substring(0, window.location.href.lastIndexOf('/') + 1), { order: 'last_post_date', direction: 'desc', period: '', state: 'active', 'title': value }, function (ajaxData) {
  11391.                 var $data = $(ajaxData.templateHtml).find('._insertLoadedContent');
  11392.                 $('._insertLoadedContent').css('padding-top', '0px').html('');
  11393.                 if (!$data.length) return;
  11394.                 $('._insertLoadedContent').replaceWith($data).xfActivate();
  11395.                 $(`<p class="textHeading">${XenForo.phrases.check_similar_threads}</p>`).prependTo($('._insertLoadedContent'));
  11396.                 $('._insertLoadedContent').css('padding-top', '20px').find('.discussionListItem').each(function () {
  11397.                     counter++;
  11398.                     if (counter > 5) $(this).remove();
  11399.                 })
  11400.             })
  11401.         }
  11402.     }
  11403.  
  11404.     XenForo.register('#ctrl_title_thread_create', 'XenForo.SuggestThreads');
  11405.  
  11406.     // *********************************************************************
  11407.  
  11408.     XenForo.PasswordInput = function ($element) {
  11409.         this.$element = $element;
  11410.         if (!this.$element.is(':visible')) return;
  11411.         this.$parent = $element.wrap('<div class="inputRelative"/>').parent();
  11412.         this.$button = $('<i class="inputRelativeIcon fas fa-eye"/>').appendTo(this.$parent);
  11413.         this.$button.on('click', function () {
  11414.             if (this.$element.attr('type') === 'password') {
  11415.                 this.$button.removeClass('fa-eye').addClass('fa-eye-slash');
  11416.                 this.$element.attr('type', 'text');
  11417.             } else {
  11418.                 this.$button.removeClass('fa-eye-slash').addClass('fa-eye');
  11419.                 this.$element.attr('type', 'password');
  11420.             }
  11421.         }.bind(this))
  11422.     };
  11423.  
  11424.     XenForo.register('input[type=password]', 'XenForo.PasswordInput');
  11425.  
  11426.     // *********************************************************************
  11427.  
  11428.     XenForo.EnhancedAutoComplete = function ($selector) {
  11429.         $selector.prop("disabled", true);
  11430.         XenForo.scriptLoader.loadCss(['select2'], "css.php?css=__sentinel__&style=9&_v=" + XenForo._jsVersion, function () {
  11431.             this.__construct($selector);
  11432.         }.bind(this), function () { });
  11433.     };
  11434.  
  11435.     XenForo.EnhancedAutoComplete.prototype = {
  11436.         __construct: function ($element) {
  11437.             this.$element = $element;
  11438.             this.$element.prop("disabled", false);
  11439.             this.uniqueName = "enhancedAutoComplete" + Math.floor(Math.random() * new Date());
  11440.             this.$parent = this.$element.wrap(`<div class="${this.uniqueName}"/>`).parent();
  11441.             this.$select = $(`<select multiple="multiple" class="textCtrl" data-placeholder="${this.$element.attr('placeholder') || ''}" aria-hidden="true"></select>`).appendTo(this.$parent);
  11442.             this.height = this.$element.height();
  11443.             this.$select.select2({
  11444.                 escapeMarkup: function (val) {
  11445.                     return DOMPurify.sanitize(val);
  11446.                 },
  11447.                 width: this.$element.width() + 20 + "px",
  11448.                 maximumSelectionLength: this.$element.hasClass('AcSingle') ? 1 : 0,
  11449.                 dropdownParent: $('<div/>'),
  11450.                 sorter: function (sort) {
  11451.                     this.bind()
  11452.                     return sort;
  11453.                 }.bind(this)
  11454.             });
  11455.             $(`<style>.${this.uniqueName} li.select2-selection__choice {
  11456.                  height:${this.height - 10}px !important;
  11457.                  line-height:${this.height - 10}px !important;
  11458.              }
  11459.              .${this.uniqueName} img.smallCompleteAvatar {
  11460.                  width: ${this.height - 14}px;
  11461.                  height: ${this.height - 14}px;
  11462.              }
  11463.              </style>`).appendTo(this.$parent)
  11464.             this.$element.css('display', 'none');
  11465.             this.$element.attr("class").split(/\s+/).map((val) => {
  11466.                 this.$parent.find('.select2-selection__rendered').addClass(val)
  11467.                 if (val !== "textCtrl") this.$parent.find('.select2').addClass(val)
  11468.             })
  11469.             this.$parent.find('.select2-selection__rendered').attr('style', 'height: inherit !important;')
  11470.             this.acResults = new XenForo.AutoCompleteResults({
  11471.                 onInsert: $.context(this, 'insertAutoComplete')
  11472.             });
  11473.             this.$parent.find('.select2-search__field').addClass('AutoComplete').xfActivate();
  11474.             if (this.$element.val()) this.initializeOld(this.$element.val().split(","));
  11475.             this.bind();
  11476.             this.$select.on('change', function () {
  11477.                 let elements = [];
  11478.                 let data = this.$select.select2('data');
  11479.                 for (const el in data) {
  11480.                     if (data[el].selected) elements.push(data[el].id);
  11481.                 }
  11482.                 this.$element.val(elements.join(","));
  11483.                 this.$element.trigger('change');
  11484.                 setTimeout(() => { this.bind() })
  11485.             }.bind(this));
  11486.             if (this.$parent.find('.select2-search__field').css('width') && !this.$parent.find('.select2-search__field').attr('width')) {
  11487.                 this.$parent.find('.select2-search__field').attr('width', 'set');
  11488.                 let padding = parseInt(window.getComputedStyle(this.$parent.find('.select2-selection__rendered').get(0), null).getPropertyValue('padding-left'));
  11489.                 if (this.$parent.find('.select2-search__field').width() - padding > 0) {
  11490.                     this.$parent.find('.select2-search__field').css('width', this.$parent.find('.select2-search__field').width() - padding + 'px');
  11491.                 }
  11492.             }
  11493.             if (this.$element.val() && this.$element.val().split(",").length === 1) {
  11494.                 XenForo.ajax(XenForo.AutoComplete.getDefaultUrl(), { q: this.$element.val() }, function (ajaxData) {
  11495.                     if (ajaxData.results !== []) {
  11496.                         this.$element.data('result', ajaxData);
  11497.                         this.initializeOld(this.$element.val().split(","));
  11498.                     }
  11499.                 }.bind(this))
  11500.             }
  11501.         },
  11502.  
  11503.         findMatch: function (match) {
  11504.             let resultKeys = Object.keys((this.$element.data('result') || {}).results || {});
  11505.             for (let i = 0; i < resultKeys.length; i++) {
  11506.                 if (resultKeys[i].toLowerCase() === match.toLowerCase()) {
  11507.                     return resultKeys[i]
  11508.                 }
  11509.             }
  11510.             return false;
  11511.         },
  11512.  
  11513.         formatAvatar: function (result) {
  11514.             let $html = $(result.username)
  11515.             $html.find('.scamNotice').remove()
  11516.             let $avatar = $html.clone();
  11517.             $avatar.prepend($(`<img class="smallCompleteAvatar" src="${result.avatar}" style="float: left;margin-top: 2px;margin-right: 5px;border-radius: 17px"/>`))
  11518.             return $avatar.prop('outerHTML');
  11519.         },
  11520.  
  11521.         initializeOld: function (value) {
  11522.             let elements = [];
  11523.             for (let i = 0; i < value.length; i++) {
  11524.                 value[i] = XenForo.htmlspecialchars(value[i])
  11525.                 elements.push(value[i]);
  11526.                 let match = this.findMatch(value[i]);
  11527.                 let newOption = new Option(match ? this.formatAvatar(this.$element.data('result').results[match]) : value[i], value[i], false, false);
  11528.                 let hit;
  11529.                 this.$select.find('option').each(function () {
  11530.                     if ($(this).attr('value') === value[i]) {
  11531.                         if (match) $(this).remove()
  11532.                         else hit = true
  11533.                     }
  11534.                 })
  11535.                 if (!hit)
  11536.                     this.$select.append(newOption).trigger('change');
  11537.             }
  11538.             this.$select.val(elements);
  11539.             this.$select.trigger('change');
  11540.             this.bind();
  11541.         },
  11542.  
  11543.         insertAutoComplete: function (name, html, avatar) {
  11544.             let elements = [];
  11545.             let data = this.$select.select2('data');
  11546.             for (let el = 0; el < data.length; el++) {
  11547.                 if (data[el].selected) elements.push(data[el].id);
  11548.             }
  11549.             elements.push(name);
  11550.             if (!this.$select.find('option').filter((_, element) => { return $(element).attr('value') === avatar || $(element).attr('value') === name }).length) {
  11551.                 let newOption = new Option(avatar, name, false, false);
  11552.                 this.$select.append(newOption).trigger('change');
  11553.             }
  11554.             this.$select.val(elements);
  11555.             this.$select.trigger('change');
  11556.             this.$element.val(elements.join(","))
  11557.             this.lastAcLookup = '';
  11558.             this.$parent.find('.select2-search__field').val('');
  11559.             setTimeout(() => { this.bind() })
  11560.         },
  11561.  
  11562.         bind: function () {
  11563.             this.$parent.find('.select2-search__field').off('focus keydown keyup').on('focus keydown', function (e) {
  11564.                 if (e.type === "focus" && this.$parent.find('.select2-search__field').css('width') && !this.$parent.find('.select2-search__field').attr('width')) {
  11565.                     this.$parent.find('.select2-search__field').attr('width', 'set');
  11566.                     let padding = parseInt(window.getComputedStyle(this.$parent.find('.select2-selection__rendered').get(0), null).getPropertyValue('padding-left'));
  11567.                     if (this.$parent.find('.select2-search__field').width() - padding > 0) {
  11568.                         this.$parent.find('.select2-search__field').css('width', this.$parent.find('.select2-search__field').width() - padding + 'px');
  11569.                     }
  11570.                 }
  11571.                 var prevent = true,
  11572.                     acResults = this.acResults;
  11573.  
  11574.                 if (e.keyCode === 8 && this.$select.select2('data').length && !this.$parent.find('.select2-search__field').val()) {
  11575.                     e.stopPropagation();
  11576.                     e.stopImmediatePropagation();
  11577.                     e.preventDefault();
  11578.                     // Remove last element and put username to input
  11579.                     let elements = [];
  11580.                     let data = this.$select.select2('data');
  11581.                     for (let el = 0; el < data.length; el++) {
  11582.                         if (data[el].selected) elements.push(data[el].id);
  11583.                     }
  11584.                     this.$parent.find('.select2-search__field').val(elements.pop());
  11585.                     this.$select.val(elements);
  11586.                     this.$select.trigger('change');
  11587.                     this.$element.val(elements.join(","))
  11588.                     this.lastAcLookup = '';
  11589.                     setTimeout(() => { this.bind() })
  11590.                 }
  11591.  
  11592.                 if (!acResults.isVisible()) {
  11593.                     return;
  11594.                 }
  11595.  
  11596.                 switch (e.keyCode) {
  11597.                     case 8:
  11598.                         this.lastAcLookup = '';
  11599.                         prevent = false; // backspace
  11600.                     case 40:
  11601.                         acResults.selectResult(1);
  11602.                         break; // down
  11603.                     case 38:
  11604.                         acResults.selectResult(-1);
  11605.                         break; // up
  11606.                     case 27:
  11607.                         acResults.hideResults();
  11608.                         break; // esc
  11609.                     case 13:
  11610.                         if (!e.shiftKey && acResults.selectedResult !== -1) {
  11611.                             // Shift isn't pressed
  11612.                             acResults.insertSelectedResult();
  11613.                             break;
  11614.                         }
  11615.                         // Shift is pressed, allow user to break line
  11616.                         this.lastAcLookup = '';
  11617.                         acResults.hideResults(); // enter
  11618.                     default:
  11619.                         prevent = false;
  11620.                 }
  11621.  
  11622.                 if (prevent) {
  11623.                     e.stopPropagation();
  11624.                     e.stopImmediatePropagation();
  11625.                     e.preventDefault();
  11626.                 }
  11627.             }.bind(this)).on('keyup', function (e) {
  11628.                 if ([13, 17, 27, 38, 40].indexOf(e.keyCode) !== -1 || e.ctrlKey && e.keyCode === 86) {
  11629.                     return
  11630.                 }
  11631.                 if (this.$parent.find('.select2-search__field').val()) this.triggerAutoComplete(this.$parent.find('.select2-search__field').val().replaceAll("@", "").trim());
  11632.             }.bind(this)).on('blur', function () {
  11633.                 if (this.$parent.find('.select2-search__field').css('width') && !this.$parent.find('.select2-search__field').attr('width')) {
  11634.                     this.$parent.find('.select2-search__field').attr('width', 'set');
  11635.                     let padding = parseInt(window.getComputedStyle(this.$parent.find('.select2-selection__rendered').get(0), null).getPropertyValue('padding-left'));
  11636.                     if (this.$parent.find('.select2-search__field').width() - padding > 0) {
  11637.                         this.$parent.find('.select2-search__field').css('width', this.$parent.find('.select2-search__field').width() - padding + 'px');
  11638.                     }
  11639.                 }
  11640.  
  11641.                 setTimeout($.context(this, 'hideAutoComplete'), 300)
  11642.             }.bind(this)).on('paste', function () {
  11643.                 setTimeout(() => {
  11644.                     this.lastAcLookup = '';
  11645.                     this.triggerAutoComplete(this.$parent.find('.select2-search__field').val().replaceAll("@", "").trim(), true)
  11646.                 });
  11647.             }.bind(this))
  11648.         },
  11649.  
  11650.         triggerAutoComplete: function (name, instant) {
  11651.             if (this.$element.hasClass('AcSingle') && this.$select.select2('data').length) {
  11652.                 return;
  11653.             }
  11654.  
  11655.             this.hideAutoComplete();
  11656.             this.lastAcLookup = name;
  11657.             if (name.length > 2 && name.substr(0, 1) !== '[') {
  11658.                 this.acLoadTimer = setTimeout($.context(this, 'autoCompleteLookup'), instant ? 0 : 200);
  11659.             }
  11660.         },
  11661.  
  11662.         autoCompleteLookup: function () {
  11663.             if (this.acXhr) {
  11664.                 this.acXhr.abort();
  11665.             }
  11666.  
  11667.             this.acXhr = XenForo.ajax(
  11668.                 this.$element.attr('data-acurl') || XenForo.AutoComplete.getDefaultUrl(),
  11669.                 { q: this.lastAcLookup },
  11670.                 $.context(this, 'showAutoCompleteResults'),
  11671.                 { global: false, error: false }
  11672.             );
  11673.         },
  11674.  
  11675.         hideAutoComplete: function () {
  11676.             this.acResults.hideResults();
  11677.             if (this.acLoadTimer) {
  11678.                 clearTimeout(this.acLoadTimer);
  11679.                 this.acLoadTimer = false;
  11680.             }
  11681.         },
  11682.  
  11683.         showAutoCompleteResults: function (ajaxData, def) {
  11684.             this.acXhr = false;
  11685.  
  11686.             if (this.lastAcLookup !== this.$parent.find('.select2-search__field').val().replaceAll("@", "").trim()) {
  11687.                 return;
  11688.             }
  11689.  
  11690.             this.acResults.showResults(
  11691.                 this.lastAcLookup,
  11692.                 ajaxData.results,
  11693.                 this.$parent.find('.select2-search__field')
  11694.             );
  11695.         },
  11696.     }
  11697.  
  11698.     XenForo.register('.ChosenAutoComplete', 'XenForo.EnhancedAutoComplete');
  11699.  
  11700.     // *********************************************************************
  11701.  
  11702.     XenForo.Cachify = function () {
  11703.         if (!navigator.serviceWorker) return;
  11704.         navigator.serviceWorker.register('/js/lolzteam/sw.js?_v=' + XenForo._jsVersion, { scope: '/' }).then(function (worker) {
  11705.             if (worker && worker.active)
  11706.                 worker.active.postMessage({
  11707.                     method: 'initCache',
  11708.                     path: '/cache?_xfResponseType=json&_xfToken=' + XenForo._csrfToken
  11709.                 })
  11710.         }).catch(function (error) {
  11711.             console.error('Error deploying service worker:', error);
  11712.         });
  11713.     };
  11714.  
  11715.     XenForo.register('html.Cachify', 'XenForo.Cachify');
  11716.  
  11717.     // *********************************************************************
  11718.  
  11719.     XenForo.AlertsClear = function ($link) {
  11720.         var callback = function (ajaxData, textStatus) {
  11721.             if (ajaxData.error) {
  11722.                 XenForo.hasResponseError(ajaxData, textStatus);
  11723.             } else {
  11724.                 $('.alerts.Popup').data('XenForo.PopupMenu').reload();
  11725.             }
  11726.         };
  11727.  
  11728.         $link.click(function (e) {
  11729.             e.preventDefault();
  11730.             const alertType = $('.alertsTabs').find('.active').data('id');
  11731.             XenForo.ajax($link.attr('href'), { type: alertType }, callback);
  11732.         });
  11733.     };
  11734.  
  11735.     XenForo.register('a.AlertsClear', 'XenForo.AlertsClear');
  11736.  
  11737.     // *********************************************************************
  11738.  
  11739.     XenForo.ClickableTabs = function ($element) {
  11740.         $element.find('li').on('click', function (e) {
  11741.             if (e.target.tagName !== "LI" || !$(this).find('a').length) return;
  11742.             e.preventDefault();
  11743.             e.stopPropagation();
  11744.             e.stopImmediatePropagation();
  11745.  
  11746.             $(this).find('a')[0].click();
  11747.         })
  11748.     };
  11749.  
  11750.     XenForo.register('.tabs', 'XenForo.ClickableTabs');
  11751.  
  11752.     XenForo.AlertReader = function ($element) {
  11753.         $element.parent().on('click mouseover', function () {
  11754.             localStorage.setItem('read_notifies', + new Date());
  11755.         });
  11756.  
  11757.         $(window).on('storage', function (e) {
  11758.             if (e.originalEvent.key === 'read_notifies') {
  11759.                 localStorage.removeItem('read_notifies');
  11760.                 const badge = $("#AlertsMenu_Counter");
  11761.                 if (!badge.hasClass('Zero')) {
  11762.                     badge.addClass('Zero');
  11763.                 }
  11764.             }
  11765.         })
  11766.     };
  11767.  
  11768.     /**
  11769.      * РЅРµ юзайте больше обычный tippy, заебали пастить настройки
  11770.      * обдрочитесь же заменять РєРѕРіРґР° обновлять будете
  11771.      * ваще РїРѕ хорошему надо РІСЃРµ отрефакторить РЅР° эту функу
  11772.      */
  11773.     XenForo.tippy = function (target, options = {}, preset = 'tooltip') {
  11774.         if (typeof preset === 'string') preset = preset.split(' ')
  11775.  
  11776.         const presets = {
  11777.             default: {
  11778.                 zIndex: $(target).closest('.xenOverlay').length ? 11111 : 9000, // overlay fix
  11779.                 boundary: 'viewport',
  11780.                 distance: 5,
  11781.                 arrow: true,
  11782.                 animation: 'shift-toward',
  11783.                 trigger: XenForo.isTouchBrowser() ? 'click' : 'mouseenter focus',
  11784.                 appendTo: $(target).closest('.modal, body')[0] // https://zelenka.guru/threads/4730409/
  11785.             },
  11786.             tooltip: {
  11787.                 hideOnClick: true,
  11788.             },
  11789.             popup: {
  11790.                 theme: 'popup',
  11791.                 placement: 'top',
  11792.                 interactive: true,
  11793.                 hideOnClick: XenForo.isTouchBrowser()
  11794.             }
  11795.         }
  11796.  
  11797.         return tippy(target, $.extend(
  11798.             presets.default,
  11799.             ...preset.map(p => presets[p]),
  11800.             options
  11801.         ))
  11802.     }
  11803.  
  11804.     if (supports_html5_storage()) {
  11805.         XenForo.register('a[href="account/alerts"]', 'XenForo.AlertReader');
  11806.     }
  11807.  
  11808.     XenForo.ContactDetailsForm = function ($element) {
  11809.         const secretAnswer = $element.find("#secretAnswer");
  11810.  
  11811.         $element.find('input[type="email"]').on('input', () => {
  11812.             if (!secretAnswer.is(':visible')) {
  11813.                 secretAnswer.show();
  11814.             }
  11815.         });
  11816.     }
  11817.  
  11818.     XenForo.register('form.ContactDetailsForm', 'XenForo.ContactDetailsForm');
  11819. }(jQuery);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement