Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * Create the XenForo namespace
- * @package XenForo
- */
- var XenForo = {};
- // _XF_JS_UNCOMPRESSED_TEST_ - do not edit/remove
- if (jQuery === undefined) jQuery = $ = {};
- if ($.tools === undefined) console.error('jQuery Tools is not loaded.');
- function animateCSS(node, animationName, callback) {
- var isString = animationName instanceof String;
- if (isString) {
- animationName = ['animated', animationName];
- } else {
- animationName = ['animated'].concat(animationName);
- }
- for (var i = 0; i < animationName.length; i++) {
- node.classList.add(animationName[i]);
- }
- function handleAnimationEnd() {
- for (var i = 0; i < animationName.length; i++) {
- node.classList.remove(animationName[i]);
- }
- node.removeEventListener('animationend', handleAnimationEnd);
- if (typeof callback === 'function') callback()
- }
- node.addEventListener('animationend', handleAnimationEnd);
- }
- var isScrolledIntoView = function (elem) {
- var docViewTop = $(window).scrollTop() + $('#header').height();
- var docViewBottom = docViewTop + $(window).height();
- var elemTop = $(elem).offset().top;
- var elemBottom = elemTop + $(elem).height();
- return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
- };
- var supports_html5_storage = function () {
- try {
- return 'localStorage' in window && window['localStorage'] !== null;
- } catch (e) {
- return false;
- }
- };
- function isElementInViewport(el) {
- var rect = el[0].getBoundingClientRect();
- return (
- rect.top >= 0 &&
- rect.left >= 0 &&
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)
- );
- }
- function scrollParentToChild(parent, child) {
- var parentRect = parent.getBoundingClientRect();
- var parentViewableArea = { height: parent.clientHeight, width: parent.clientWidth };
- var childRect = child.getBoundingClientRect();
- var isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height);
- if (!isViewable) {
- const scrollTop = childRect.top - parentRect.top;
- parent.scrollTop += scrollTop;
- }
- }
- /**
- * Deal with Firebug not being present
- */
- !function (w) {
- var fn, i = 0;
- if (!w.console) w.console = {};
- if (w.console.log && !w.console.debug) w.console.debug = w.console.log;
- fn = ['assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 'getFirebugElement', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 'notifyFirebug', 'profile', 'profileEnd', 'time', 'timeEnd', 'trace', 'warn'];
- for (i = 0; i < fn.length; ++i) if (!w.console[fn[i]]) w.console[fn[i]] = function () {
- };
- }(window);
- /** @param {jQuery} $ jQuery Object */
- !function ($, window, document, _undefined) {
- var isTouchBrowser = (function () {
- var _isTouchBrowserVal;
- try {
- _isTouchBrowserVal = 'ontouchstart' in document.documentElement;
- //_isTouchBrowserVal = !!('ontouchstart' in window || navigator.maxTouchPoints || navigator.msMaxTouchPoints);
- } catch (e) {
- _isTouchBrowserVal = !!(navigator.userAgent.indexOf('webOS') != -1);
- }
- return function () {
- return _isTouchBrowserVal;
- };
- })();
- var classes = ['hasJs'];
- classes.push(isTouchBrowser() ? 'Touch' : 'NoTouch');
- var div = document.createElement('div');
- classes.push(('draggable' in div) || ('ondragstart' in div && 'ondrop' in div) ? 'HasDragDrop' : 'NoDragDrop');
- // not especially nice but...
- if (navigator.userAgent.search(/\((iPhone|iPad|iPod);/) != -1) {
- classes.push('iOS');
- }
- var $html = $('html');
- $html.addClass(classes.join(' ')).removeClass('NoJs');
- /**
- * Fix IE abbr handling
- */
- document.createElement('abbr');
- /**
- * Detect mobile webkit
- */
- if (/webkit.*mobile/i.test(navigator.userAgent)) {
- XenForo._isWebkitMobile = true;
- }
- // preserve original jQuery Tools .overlay()
- jQuery.fn._jQueryToolsOverlay = jQuery.fn.overlay;
- /**
- * Extends jQuery core
- */
- jQuery.extend(true,
- {
- /**
- * Sets the context of 'this' within a called function.
- * Takes identical parameters to $.proxy, but does not
- * enforce the one-elment-one-method merging that $.proxy
- * does, allowing multiple objects of the same type to
- * bind to a single element's events (for example).
- *
- * @param function|object Function to be called | Context for 'this', method is a property of fn
- * @param function|string Context for 'this' | Name of method within fn to be called
- *
- * @return function
- */
- context: function (fn, context) {
- if (typeof context == 'string') {
- var _context = fn;
- fn = fn[context];
- context = _context;
- }
- return fn.bind(context)
- },
- /**
- * Sets a cookie.
- *
- * @param string cookie name (escaped)
- * @param mixed cookie value
- * @param string cookie expiry date
- *
- * @return mixed cookie value
- */
- setCookie: function (name, value, expires) {
- console.log('Set cookie %s = %s', name, value);
- document.cookie = XenForo._cookieConfig.prefix + name + '=' + encodeURIComponent(value)
- + (expires === undefined ? '' : ';expires=' + expires.toUTCString())
- + (XenForo._cookieConfig.path ? ';path=' + XenForo._cookieConfig.path : '')
- + (XenForo._cookieConfig.domain ? ';domain=' + XenForo._cookieConfig.domain : '');
- return value;
- },
- /**
- * Fetches the value of a named cookie.
- *
- * @param string Cookie name (escaped)
- *
- * @return string Cookie value
- */
- getCookie: function (name) {
- var expr, cookie;
- expr = new RegExp('(^| )' + XenForo._cookieConfig.prefix + name + '=([^;]+)(;|$)');
- cookie = expr.exec(document.cookie);
- if (cookie) {
- return decodeURIComponent(cookie[2]);
- } else {
- return null;
- }
- },
- /**
- * Deletes a cookie.
- *
- * @param string Cookie name (escaped)
- *
- * @return null
- */
- deleteCookie: function (name) {
- console.info('Delete cookie %s', name);
- document.cookie = XenForo._cookieConfig.prefix + name + '='
- + (XenForo._cookieConfig.path ? '; path=' + XenForo._cookieConfig.path : '')
- + (XenForo._cookieConfig.domain ? '; domain=' + XenForo._cookieConfig.domain : '')
- + '; expires=Thu, 01-Jan-70 00:00:01 GMT';
- return null;
- }
- });
- /**
- * Extends jQuery functions
- */
- jQuery.fn.extend(
- {
- /**
- * Wrapper for XenForo.activate, for 'this' element
- *
- * @return jQuery
- */
- xfActivate: function () {
- return XenForo.activate(this);
- },
- /**
- * Retuns .data(key) for this element, or the default value if there is no data
- *
- * @param string key
- * @param mixed defaultValue
- *
- * @return mixed
- */
- dataOrDefault: function (key, defaultValue) {
- var value = this.data(key);
- if (value === undefined) {
- return defaultValue;
- }
- return value;
- },
- /**
- * Like .val() but also trims trailing whitespace
- */
- strval: function () {
- return String(this.val()).replace(/\s+$/g, '');
- },
- /**
- * Get the 'name' attribute of an element, or if it exists, the value of 'data-fieldName'
- *
- * @return string
- */
- fieldName: function () {
- return this.data('fieldname') || this.attr('name');
- },
- /**
- * Get the value that would be submitted with 'this' element's name on form submit
- *
- * @return string
- */
- fieldValue: function () {
- switch (this.attr('type')) {
- case 'checkbox': {
- return $('input:checkbox[name="' + this.fieldName() + '"]:checked', this.context.form).val();
- }
- case 'radio': {
- return $('input:radio[name="' + this.fieldName() + '"]:checked', this.context.form).val();
- }
- default: {
- return this.val();
- }
- }
- },
- _jqSerialize: $.fn.serialize,
- /**
- * Overridden jQuery serialize method to ensure that RTE areas are serialized properly.
- */
- serialize: function () {
- $('textarea.BbCodeWysiwygEditor').each(function () {
- var data = $(this).data('XenForo.BbCodeWysiwygEditor');
- if (data) {
- data.syncEditor();
- }
- });
- return this._jqSerialize();
- },
- _jqSerializeArray: $.fn.serializeArray,
- /**
- * Overridden jQuery serializeArray method to ensure that RTE areas are serialized properly.
- */
- serializeArray: function () {
- $('textarea.BbCodeWysiwygEditor').each(function () {
- var data = $(this).data('XenForo.BbCodeWysiwygEditor');
- if (data) {
- data.syncEditor();
- }
- });
- return this._jqSerializeArray();
- },
- /**
- * Returns the position and size of an element, including hidden elements.
- *
- * If the element is hidden, it will very quickly un-hides a display:none item,
- * gets its offset and size, restore the element to its hidden state and returns values.
- *
- * @param string inner/outer/{none} Defines the jQuery size function to use
- * @param string offset/position/{none} Defines the jQuery position function to use (default: offset)
- *
- * @return object Offset { left: float, top: float }
- */
- coords: function (sizeFn, offsetFn) {
- var coords,
- visibility,
- display,
- widthFn,
- heightFn,
- hidden = this.is(':hidden');
- if (hidden) {
- visibility = this.css('visibility'),
- display = this.css('display');
- this.css(
- {
- visibility: 'hidden',
- display: 'block'
- });
- }
- switch (sizeFn) {
- case 'inner': {
- widthFn = 'innerWidth';
- heightFn = 'innerHeight';
- break;
- }
- case 'outer': {
- widthFn = 'outerWidth';
- heightFn = 'outerHeight';
- break;
- }
- default: {
- widthFn = 'width';
- heightFn = 'height';
- }
- }
- switch (offsetFn) {
- case 'position': {
- offsetFn = 'position';
- break;
- }
- default: {
- offsetFn = 'offset';
- break;
- }
- }
- coords = this[offsetFn]();
- coords.width = this[widthFn]();
- coords.height = this[heightFn]();
- if (hidden) {
- this.css(
- {
- display: display,
- visibility: visibility
- });
- }
- return coords;
- },
- /**
- * Sets a unique id for an element, if one is not already present
- */
- uniqueId: function () {
- if (!this.attr('id')) {
- this.attr('id', 'XenForoUniq' + XenForo._uniqueIdCounter++);
- }
- return this;
- },
- /**
- * Wrapper functions for commonly-used animation effects, so we can customize their behaviour as required
- */
- xfFadeIn: function (speed, callback) {
- return this.fadeIn(speed, function () {
- $(this).ieOpacityFix(callback);
- });
- },
- xfFadeOut: function (speed, callback) {
- return this.fadeOut(speed, callback);
- },
- xfShow: function (speed, callback) {
- return this.show(speed, function () {
- $(this).ieOpacityFix(callback);
- });
- },
- xfHide: function (speed, callback) {
- return this.hide(speed, callback);
- },
- xfSlideDown: function (speed, callback) {
- return this.slideDown(speed, function () {
- $(this).ieOpacityFix(callback);
- });
- },
- xfSlideUp: function (speed, callback) {
- return this.slideUp(speed, callback);
- },
- /**
- * Animates an element opening a space for itself, then fading into that space
- *
- * @param integer|string Speed of fade-in
- * @param function Callback function on completion
- *
- * @return jQuery
- */
- xfFadeDown: function (fadeSpeed, callback, finish) {
- this.filter(':hidden').xfHide().css('opacity', 0);
- fadeSpeed = fadeSpeed || XenForo.speed.normal;
- if (finish) {
- $(this).clearQueue().finish();
- }
- return this
- .xfSlideDown(XenForo.speed.fast)
- .animate({ opacity: 1 }, fadeSpeed, function () {
- $(this).ieOpacityFix(callback);
- });
- },
- /**
- * Animates an element fading out then closing the gap left behind
- *
- * @param integer|string Speed of fade-out - if this is zero, there will be no animation at all
- * @param function Callback function on completion
- * @param integer|string Slide speed - ignored if fadeSpeed is zero
- * @param string Easing method
- *
- * @return jQuery
- */
- xfFadeUp: function (fadeSpeed, callback, slideSpeed, easingMethod, finish) {
- fadeSpeed = ((typeof fadeSpeed == 'undefined' || fadeSpeed === null) ? XenForo.speed.normal : fadeSpeed);
- slideSpeed = ((typeof slideSpeed == 'undefined' || slideSpeed === null) ? fadeSpeed : slideSpeed);
- if (finish) {
- $(this).clearQueue().finish();
- }
- return this
- .slideUp({
- duration: Math.max(fadeSpeed, slideSpeed),
- easing: easingMethod || 'swing',
- complete: callback,
- queue: false
- })
- .animate({ opacity: 0, queue: false }, fadeSpeed);
- },
- /**
- * Inserts and activates content into the DOM, using xfFadeDown to animate the insertion
- *
- * @param string jQuery method with which to insert the content
- * @param string Selector for the previous parameter
- * @param string jQuery method with which to animate the showing of the content
- * @param string|integer Speed at which to run the animation
- * @param function Callback for when the animation is complete
- *
- * @return jQuery
- */
- xfInsert: function (insertMethod, insertReference, animateMethod, animateSpeed, callback) {
- if (insertMethod == 'replaceAll') {
- $(insertReference).xfFadeUp(animateSpeed);
- }
- this
- .addClass('__XenForoActivator')
- .css('display', 'none')
- [insertMethod || 'appendTo'](insertReference)
- .xfActivate()
- [animateMethod || 'xfFadeDown'](animateSpeed, callback);
- return this;
- },
- /**
- * Removes an element from the DOM, animating its removal with xfFadeUp
- * All parameters are optional.
- *
- * @param string animation method
- * @param function callback function
- * @param integer Sliding speed
- * @param string Easing method
- *
- * @return jQuery
- */
- xfRemove: function (animateMethod, callback, slideSpeed, easingMethod) {
- return this[animateMethod || 'xfFadeUp'](XenForo.speed.normal, function () {
- $(this).empty().remove();
- if ($.isFunction(callback)) {
- callback();
- }
- }, slideSpeed, easingMethod);
- },
- /**
- * Prepares an element for xfSlideIn() / xfSlideOut()
- *
- * @param boolean If true, return the height of the wrapper
- *
- * @return jQuery|integer
- */
- _xfSlideWrapper: function (getHeight) {
- if (!this.data('slidewrapper')) {
- this.data('slidewrapper', this.wrap('<div class="_swOuter"><div class="_swInner" /></div>')
- .closest('div._swOuter').css('overflow', 'hidden'));
- }
- if (getHeight) {
- try {
- return this.data('slidewrapper').height();
- } catch (e) {
- // so IE11 seems to be randomly throwing an exception in jQuery here, so catch it
- return 0;
- }
- }
- return this.data('slidewrapper');
- },
- /**
- * Slides content in (down), with content glued to lower edge, drawer-like
- *
- * @param duration
- * @param easing
- * @param callback
- *
- * @return jQuery
- */
- xfSlideIn: function (duration, easing, callback) {
- var $wrap = this._xfSlideWrapper().css('height', 'auto'),
- height = 0;
- $wrap.find('div._swInner').css('margin', 'auto');
- height = this.show(0).outerHeight();
- $wrap
- .css('height', 0)
- .animate({ height: height }, duration, easing, function () {
- $wrap.css('height', '');
- })
- .find('div._swInner')
- .css('marginTop', height * -1)
- .animate({ marginTop: 0 }, duration, easing, callback);
- return this;
- },
- /**
- * Slides content out (up), reversing xfSlideIn()
- *
- * @param duration
- * @param easing
- * @param callback
- *
- * @return jQuery
- */
- xfSlideOut: function (duration, easing, callback) {
- var height = this.outerHeight();
- this._xfSlideWrapper()
- .animate({ height: 0 }, duration, easing)
- .find('div._swInner')
- .animate({ marginTop: height * -1 }, duration, easing, callback);
- return this;
- },
- /**
- * Workaround for IE's font-antialiasing bug when dealing with opacity
- *
- * @param function Callback
- */
- ieOpacityFix: function (callback) {
- //ClearType Fix
- if (!$.support.opacity) {
- this.css('filter', '');
- this.attr('style', this.attr('style').replace(/filter:\s*;/i, ''));
- }
- if ($.isFunction(callback)) {
- callback.apply(this);
- }
- return this;
- },
- /**
- * Wraps around jQuery Tools .overlay().
- *
- * Prepares overlay options before firing overlay() for best possible experience.
- * For example, removes fancy (slow) stuff from options for touch browsers.
- *
- * @param options
- *
- * @returns jQuery
- */
- overlay: function (options) {
- if (XenForo.isTouchBrowser()) {
- return this._jQueryToolsOverlay($.extend(true, options,
- {
- //mask: false,
- speed: 0,
- loadSpeed: 0
- }));
- } else {
- return this._jQueryToolsOverlay(options);
- }
- }
- });
- /* jQuery Tools Extensions */
- /**
- * Effect method for jQuery.tools overlay.
- * Slides down a container, then fades up the content.
- * Closes by reversing the animation.
- */
- $.tools.overlay.addEffect('zoomIn',
- function (position, callback) {
- var $overlay = this.getOverlay();
- $overlay.find('.content').css('opacity', 0);
- if (this.getConf().fixed) {
- position.position = 'fixed';
- } else {
- position.position = 'absolute';
- position.top += $(window).scrollTop();
- position.left += $(window).scrollLeft();
- }
- $overlay.removeClass('animated zoomOut faster').css(position);
- $overlay.show(0, callback);
- animateCSS($overlay.get(0), ['zoomIn', 'faster']);
- },
- function (callback) {
- var $overlay = this.getOverlay();
- $overlay.removeClass('animated zoomIn faster');
- animateCSS($overlay.get(0), ['zoomOut', 'faster'], function () {
- callback();
- $overlay.hide(0, callback);
- });
- },
- );
- $.tools.overlay.addEffect('slideDownContentFade',
- function (position, callback) {
- var $overlay = this.getOverlay(),
- conf = this.getConf();
- $overlay.find('.content').css('opacity', 0);
- if (this.getConf().fixed) {
- position.position = 'fixed';
- } else {
- position.position = 'absolute';
- position.top += $(window).scrollTop();
- position.left += $(window).scrollLeft();
- }
- $overlay.css(position).xfSlideDown(XenForo.speed.fast, function () {
- $overlay.find('.content').animate({ opacity: 1 }, conf.speed, function () {
- $(this).ieOpacityFix(callback);
- });
- });
- },
- function (callback) {
- var $overlay = this.getOverlay();
- $overlay.find('.content').animate({ opacity: 0 }, this.getConf().speed, function () {
- $overlay.xfSlideUp(XenForo.speed.fast, callback);
- });
- }
- );
- $.tools.overlay.addEffect('slideDown',
- function (position, callback) {
- if (this.getConf().fixed) {
- position.position = 'fixed';
- } else {
- position.position = 'absolute';
- position.top += $(window).scrollTop();
- position.left += $(window).scrollLeft();
- }
- this.getOverlay()
- .css(position)
- .xfSlideDown(this.getConf().speed, callback);
- },
- function (callback) {
- this.getOverlay().hide(0, callback);
- }
- );
- // *********************************************************************
- $.extend(XenForo,
- {
- /**
- * Cache for overlays
- *
- * @var object
- */
- _OverlayCache: {},
- /**
- * Defines whether or not an AJAX request is known to be in progress
- *
- * @var boolean
- */
- _AjaxProgress: false,
- /**
- * Defines a variable that can be overridden to force/control the base HREF
- * used to canonicalize AJAX requests
- *
- * @var string
- */
- ajaxBaseHref: '',
- /**
- * Counter for unique ID generation
- *
- * @var integer
- */
- _uniqueIdCounter: 0,
- /**
- * Configuration for overlays, should be redefined in the PAGE_CONTAINER template HTML
- *
- * @var object
- */
- _overlayConfig: {},
- /**
- * Contains the URLs of all externally loaded resources from scriptLoader
- *
- * @var object
- */
- _loadedScripts: {},
- /**
- * Configuration for cookies
- *
- * @var object
- */
- _cookieConfig: { path: '/', domain: '', 'prefix': 'xf_' },
- /**
- * Flag showing whether or not the browser window has focus. On load, assume true.
- *
- * @var boolean
- */
- _hasFocus: true,
- /**
- * @var object List of server-related time info (now, today, todayDow)
- */
- serverTimeInfo: {},
- /**
- * @var object Information about the XenForo visitor. Usually contains user_id.
- */
- visitor: {},
- /**
- * @var integer Time the page was loaded.
- */
- _pageLoadTime: (new Date()).getTime() / 1000,
- /**
- * JS version key, to force refreshes when needed
- *
- * @var string
- */
- _jsVersion: '',
- /**
- * If true, disables reverse tabnabbing protection
- *
- * @var bool
- */
- _noRtnProtect: false,
- /**
- * CSRF Token
- *
- * @var string
- */
- _csrfToken: '',
- /**
- * URL to CSRF token refresh.
- *
- * @var string
- */
- _csrfRefreshUrl: '',
- _noSocialLogin: false,
- /**
- * Speeds for animation
- *
- * @var object
- */
- speed:
- {
- xxfast: 50,
- xfast: 100,
- fast: 200,
- normal: 400,
- slow: 600
- },
- /**
- * Multiplier for animation speeds
- *
- * @var float
- */
- _animationSpeedMultiplier: 1,
- /**
- * Enable overlays or use regular pages
- *
- * @var boolean
- */
- _enableOverlays: true,
- /**
- * Enables AJAX submission via AutoValidator. Doesn't change things other than
- * that. Useful to disable for debugging.
- *
- * @var boolean
- */
- _enableAjaxSubmit: true,
- /**
- * Determines whether the lightbox shows all images from the current page,
- * or just from an individual message
- *
- * @var boolean
- */
- _lightBoxUniversal: false,
- /**
- * @var object Phrases
- */
- phrases: {},
- /**
- * Binds all registered functions to elements within the DOM
- */
- init: function () {
- var dStart = new Date(),
- xfFocus = function () {
- XenForo._hasFocus = true;
- $(document).triggerHandler('XenForoWindowFocus');
- },
- xfBlur = function () {
- XenForo._hasFocus = false;
- $(document).triggerHandler('XenForoWindowBlur');
- },
- $html = $('html');
- if ($.browser.msie) {
- $(document).bind(
- {
- focusin: xfFocus,
- focusout: xfBlur
- });
- } else {
- $(window).bind(
- {
- focus: xfFocus,
- blur: xfBlur
- });
- }
- $(window).on('resize', function () {
- XenForo.checkQuoteSizing($(document));
- });
- // Set the animation speed based around the style property speed multiplier
- XenForo.setAnimationSpeed(XenForo._animationSpeedMultiplier);
- // Periodical timestamp refresh
- XenForo._TimestampRefresh = new XenForo.TimestampRefresh();
- // Find any ignored content that has not been picked up by PHP
- XenForo.prepareIgnoredContent();
- // init ajax progress indicators
- XenForo.AjaxProgress();
- // Activate all registered controls
- XenForo.activate(document);
- $(document).on('click', '.bbCodeQuote .quoteContainer .quoteExpand', function (e) {
- $(this).closest('blockquote.quote').css('max-height', 'unset')
- $(this).closest('.quoteContainer').toggleClass('expanded');
- });
- XenForo.watchProxyLinks();
- if (!XenForo._noRtnProtect) {
- XenForo.watchExternalLinks();
- }
- // make the breadcrumb and navigation responsive
- if (!$html.hasClass('NoResponsive')) {
- XenForo.updateVisibleBreadcrumbs();
- XenForo.updateVisibleNavigationTabs();
- XenForo.updateVisibleNavigationLinks();
- var resizeTimer, htmlWidth = $html.width();
- $(window).on('resize orientationchange load', function (e) {
- if (resizeTimer) {
- return;
- }
- if (e.type != 'load' && $html.width() == htmlWidth) {
- return;
- }
- htmlWidth = $html.width();
- resizeTimer = setTimeout(function () {
- resizeTimer = 0;
- XenForo.updateVisibleBreadcrumbs();
- XenForo.updateVisibleNavigationTabs();
- XenForo.updateVisibleNavigationLinks();
- }, 20);
- });
- $(document).on('click', '.breadcrumb .placeholder', function () {
- $(this).closest('.breadcrumb').addClass('showAll');
- XenForo.updateVisibleBreadcrumbs();
- });
- }
- // Periodical CSRF token refresh
- XenForo._CsrfRefresh = new XenForo.CsrfRefresh();
- // Autofocus for non-supporting browsers
- if (!('autofocus' in document.createElement('input'))) {
- //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
- $('input[autofocus], textarea[autofocus], select[autofocus]').first().focus();
- }
- // init Tweet buttons
- XenForo.tweetButtonInit();
- console.info('XenForo.init() %dms. jQuery %s/%s', new Date() - dStart, $().jquery, $.tools.version);
- if ($('#ManualDeferredTrigger').length) {
- setTimeout(XenForo.manualDeferredHandler, 100);
- }
- if ($('html.RunDeferred').length) {
- setTimeout(XenForo.runAutoDeferred, 100);
- }
- },
- runAutoDeferred: function () {
- XenForo.ajax('deferred.php', {}, function (ajaxData) {
- if (ajaxData && ajaxData.moreDeferred) {
- setTimeout(XenForo.runAutoDeferred, 100);
- }
- }, { error: false, global: false });
- },
- prepareIgnoredContent: function () {
- var $displayLink = $('a.DisplayIgnoredContent'),
- namesObj = {},
- namesArr = [];
- if ($displayLink.length) {
- $('.ignored').each(function () {
- var name = $(this).data('author');
- if (name) {
- namesObj[name] = true;
- }
- });
- $.each(namesObj, function (name) {
- namesArr.push(name);
- });
- if (namesArr.length) {
- $displayLink.attr('title', XenForo.phrases['show_hidden_content_by_x'].replace(/\{names\}/, namesArr.join(', ')));
- $displayLink.parent().show();
- }
- }
- },
- watchProxyLinks: function () {
- var proxyLinkClick = function (e) {
- var $this = $(this),
- proxyHref = $this.data('proxy-href'),
- lastEvent = $this.data('proxy-handler-last');
- if (!proxyHref) {
- return;
- }
- // we may have a direct click event and a bubbled event. Ensure they don't both fire.
- if (lastEvent && lastEvent == e.timeStamp) {
- return;
- }
- $this.data('proxy-handler-last', e.timeStamp);
- XenForo.ajax(proxyHref, {}, function (ajaxData) {
- }, { error: false, global: false });
- };
- $(document)
- .on('click', 'a.ProxyLink', proxyLinkClick)
- .on('focusin', 'a.ProxyLink', function (e) {
- // This approach is taken because middle click events do not bubble. This is a way of
- // getting the equivalent of event bubbling on middle clicks in Chrome.
- var $this = $(this);
- if ($this.data('proxy-handler')) {
- return;
- }
- $this.data('proxy-handler', true)
- .click(proxyLinkClick);
- });
- },
- watchExternalLinks: function () {
- var externalLinkClick = function (e) {
- if (e.isDefaultPrevented()) {
- return;
- }
- var $this = $(this),
- href = $this.attr('href'),
- lastEvent = $this.data('blank-handler-last');
- if (!href) {
- return;
- }
- const urlRegExp = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
- if (!urlRegExp.test(href)) {
- return;
- }
- href = XenForo.canonicalizeUrl(href);
- var regex = new RegExp('^[a-z]+://' + location.host + '(/|$|:)', 'i');
- if (regex.test(href) && !$this.hasClass('ProxyLink')) {
- // if the link is local, then don't do the special processing... unless it's a proxy link
- // so it's likely to be external after the redirect
- return;
- }
- // we may have a direct click event and a bubbled event. Ensure they don't both fire.
- if (lastEvent && lastEvent == e.timeStamp) {
- return;
- }
- $this.data('blank-handler-last', e.timeStamp);
- var ua = navigator.userAgent,
- isOldIE = ua.indexOf('MSIE') !== -1,
- isSafari = ua.indexOf('Safari') !== -1 && ua.indexOf('Chrome') == -1,
- isGecko = ua.indexOf('Gecko/') !== -1;
- if (e.shiftKey && isGecko) {
- // Firefox doesn't trigger when holding shift. If the code below runs, it will force
- // opening in a new tab instead of a new window, so stop. Note that Chrome still triggers here,
- // but it does open in a new window anyway so we run the normal code.
- return;
- }
- if (isSafari && (e.shiftKey || e.altKey)) {
- // this adds to reading list or downloads instead of opening a new tab
- return;
- }
- if (isOldIE) {
- // IE has mitigations for this and this blocks referrers
- return;
- }
- // now run the opener clearing
- if (isSafari) {
- // Safari doesn't work with the other approach
- // Concept from: https://github.com/danielstjules/blankshield
- var $iframe, iframeDoc, $script;
- $iframe = $('<iframe style="display: none" />').appendTo(document.body);
- iframeDoc = $iframe[0].contentDocument || $iframe[0].contentWindow.document;
- iframeDoc.__href = href; // set this so we don't need to do an eval-type thing
- $script = $('<script />', iframeDoc);
- $script[0].text = 'window.opener=null;' +
- 'window.parent=null;window.top=null;window.frameElement=null;' +
- 'window.open(document.__href).opener = null;';
- iframeDoc.body.appendChild($script[0]);
- $iframe.remove();
- } else {
- // use this approach for the rest to maintain referrers when possible
- var w = window.open(href);
- try {
- // this can potentially fail, don't want to break
- w.opener = null;
- } catch (e) {
- }
- }
- e.preventDefault();
- };
- $(document)
- .on('click', 'a[target=_blank]:not(.fr-element a)', externalLinkClick)
- .on('focusin', 'a[target=_blank]:not(.fr-element a)', function (e) {
- // This approach is taken because middle click events do not bubble. This is a way of
- // getting the equivalent of event bubbling on middle clicks in Chrome.
- var $this = $(this);
- if ($this.data('blank-handler')) {
- return;
- }
- $this.data('blank-handler', true)
- .click(externalLinkClick);
- });
- },
- /**
- * Asynchronously load the specified JavaScript, with an optional callback on completion.
- *
- * @param string Script source
- * @param object Callback function
- * @param string innerHtml for the script tags
- */
- loadJs: function (src, callback, innerHTML) {
- try {
- var script = document.createElement('script');
- script.async = true;
- if (innerHTML) {
- try {
- script.innerHTML = innerHTML;
- } catch (e2) {
- }
- }
- var f = function () {
- if (callback) {
- callback();
- callback = null;
- }
- };
- script.onload = f;
- script.onreadystatechange = function () {
- if (script.readyState === 'loaded') {
- f();
- }
- };
- script.src = src;
- document.getElementsByTagName('head')[0].appendChild(script);
- } catch (e) {
- }
- },
- /**
- * Asynchronously load the Twitter button JavaScript.
- */
- tweetButtonInit: function () {
- if ($('a.twitter-share-button').length) {
- XenForo.loadJs('https://platform.twitter.com/widgets.js');
- }
- },
- /**
- * Asynchronously load the +1 button JavaScript.
- */
- plusoneButtonInit: function (el) {
- if ($(el).find('div.g-plusone, .GoogleLogin').length) {
- var locale = $('html').attr('lang');
- var callback = function () {
- if (!window.gapi) {
- return;
- }
- $(el).find('.GoogleLogin').each(function () {
- var $button = $(this),
- clientId = $button.data('client-id'),
- auth2;
- gapi.load('auth2', function () {
- auth2 = gapi.auth2.init({
- client_id: clientId,
- scope: 'profile email',
- response_type: 'permission',
- cookie_policy: 'single_host_origin'
- });
- $button.on('click', function () {
- auth2.grantOfflineAccess({
- scope: 'profile email'
- })
- .then(function (resp) {
- var code = resp.code;
- if (code) {
- window.location = XenForo.canonicalizeUrl(
- $button.data('redirect-url').replace('__CODE__', code)
- );
- }
- })
- .catch(function () {
- });
- });
- });
- });
- };
- if (window.___gcfg && window.gapi) {
- callback();
- } else {
- window.___gcfg = {
- lang: locale,
- isSignedOut: true // this is to stop the "welcome back" prompt as it doesn't fit with our flow
- };
- XenForo.loadJs('https://apis.google.com/js/api:client.js', callback);
- }
- }
- },
- /**
- * Prevents Google Chrome's AutoFill from turning inputs yellow.
- * Adapted from http://www.benjaminmiles.com/2010/11/22/fixing-google-chromes-yellow-autocomplete-styles-with-jquery/
- */
- chromeAutoFillFix: function ($root) {
- if ($.browser.webkit && navigator.userAgent.toLowerCase().indexOf('chrome') >= 0) {
- if (!$root) {
- $root = $(document);
- }
- // trap an error here - CloudFlare RocketLoader causes an error with this.
- var $inputs;
- try {
- $inputs = $root.find('input:-webkit-autofill');
- } catch (e) {
- $inputs = $([]);
- }
- if ($inputs.length) {
- $inputs.each(function (i) {
- var $this = $(this),
- val = $this.val();
- if (!val || !val.length) {
- return;
- }
- $this.after($this.clone(true).val(val)).remove();
- });
- }
- }
- },
- updateVisibleBreadcrumbs: function () {
- $('.breadcrumb').each(function () {
- var container = this,
- $container = $(container);
- $container.find('.placeholder').remove();
- var $crusts = $container.find('.crust');
- $crusts.removeClass('firstVisibleCrumb').show();
- var $homeCrumb = $crusts.filter('.homeCrumb');
- $container.css('height', '');
- var beforeHeight = container.offsetHeight;
- $container.css('height', 'auto');
- if (container.offsetHeight <= beforeHeight) {
- $container.css('height', '');
- return;
- }
- var $lastHidden = null,
- hideSkipSelector = '.selectedTabCrumb, :last-child';
- $crusts.each(function () {
- var $crust = $(this);
- if ($crust.is(hideSkipSelector)) {
- return true;
- }
- $crust.hide();
- $lastHidden = $crust;
- return (container.offsetHeight > beforeHeight);
- });
- if (!$lastHidden) {
- $container.css('height', '');
- return;
- }
- var $placeholder = $('<span class="crust placeholder"><a class="crumb" href="javascript:"><span>...</span></a><span class="arrow"><span>></span></span></span>');
- $lastHidden.after($placeholder);
- if (container.offsetHeight > beforeHeight) {
- var $prev = $lastHidden.prevAll('.crust:not(' + hideSkipSelector + ')').last();
- if ($prev.length) {
- $prev.hide();
- }
- }
- if (container.offsetHeight > beforeHeight) {
- var $next = $lastHidden.nextAll('.crust:not(.placeholder, ' + hideSkipSelector + ')').first();
- if ($next.length) {
- $next.hide();
- $next.after($placeholder);
- }
- }
- if ($homeCrumb.length && !$homeCrumb.is(':visible')) {
- $container.find('.crust:visible:first').addClass('firstVisibleCrumb');
- }
- if (container.offsetHeight <= beforeHeight) {
- // firefox doesn't seem to contain the breadcrumbs despite the overflow hidden
- $container.css('height', '');
- }
- });
- },
- updateVisibleNavigationTabs: function () {
- var $tabs = $('#navigation').find('.navTabs');
- if (!$tabs.length) {
- return;
- }
- var tabsCoords = $tabs.coords(),
- $publicTabs = $tabs.find('.publicTabs'),
- $publicInnerTabs = $publicTabs.find('> .navTab'),
- $visitorTabs = $tabs.find('.visitorTabs'),
- $visitorInnerTabs = $visitorTabs.find('> .navTab'),
- $visitorCounter = $('#VisitorExtraMenu_Counter'),
- maxPublicWidth,
- $hiddenTab = $publicInnerTabs.filter('.navigationHiddenTabs');
- $publicInnerTabs.show();
- $hiddenTab.hide();
- $visitorInnerTabs.show();
- $visitorCounter.addClass('ResponsiveOnly');
- if ($tabs.is('.showAll')) {
- return;
- }
- maxPublicWidth = $tabs.width() - $visitorTabs.width() - 1;
- var hidePublicTabs = function () {
- var shownSel = '.selected, .navigationHiddenTabs';
- var $hideable = $publicInnerTabs.filter(':not(' + shownSel + ')'),
- $hiddenList = $('<ul />'),
- hiddenCount = 0,
- overflowMenuShown = false;
- $.each($hideable.get().reverse(), function () {
- var $this = $(this);
- if (isOverflowing($publicTabs.coords(), true)) {
- $hiddenList.prepend(
- $('<li />').html($this.find('.navLink').clone())
- );
- $this.hide();
- hiddenCount++;
- } else {
- if (hiddenCount) {
- $hiddenTab.show();
- if (isOverflowing($publicTabs.coords(), true)) {
- $hiddenList.prepend(
- $('<li />').html($this.find('.navLink').clone())
- );
- $this.hide();
- hiddenCount++;
- }
- $('#NavigationHiddenMenu').html($hiddenList).xfActivate();
- overflowMenuShown = true;
- } else {
- $hiddenTab.hide();
- }
- return false;
- }
- });
- if (hiddenCount && !overflowMenuShown) {
- $hiddenTab.show();
- $('#NavigationHiddenMenu').html($hiddenList).xfActivate();
- }
- },
- hideVisitorTabs = function () {
- $visitorInnerTabs.hide();
- $visitorInnerTabs.filter('.account, .selected').show();
- $visitorCounter.removeClass('ResponsiveOnly');
- },
- isOverflowing = function (coords, checkMax) {
- if (
- coords.top >= tabsCoords.top + tabsCoords.height
- || coords.height >= tabsCoords.height * 2
- ) {
- return true;
- }
- if (checkMax && coords.width > maxPublicWidth) {
- return true;
- }
- return false;
- };
- if ($visitorTabs.length) {
- if (isOverflowing($visitorTabs.coords())) {
- hidePublicTabs();
- if (isOverflowing($visitorTabs.coords())) {
- hideVisitorTabs();
- }
- }
- } else if (isOverflowing($publicTabs.coords())) {
- hidePublicTabs();
- }
- },
- updateVisibleNavigationLinks: function () {
- var $linksList = $('#navigation').find('.navTab.selected .blockLinksList');
- if (!$linksList.length) {
- return;
- }
- var $links = $linksList.find('> li'),
- listOffset = $linksList.offset(),
- $hidden = $links.filter('.navigationHidden'),
- $firstHidden = false;
- $links.show();
- $hidden.hide();
- if ($linksList.is('.showAll')) {
- return;
- }
- var hiddenForMenu = [],
- $lastLink = $links.filter(':not(.navigationHidden)').last(),
- hideOffset = 0,
- hasHidden = false,
- lastCoords,
- $link;
- if (!$lastLink.length) {
- return;
- }
- do {
- lastCoords = $lastLink.coords();
- if (lastCoords.top > listOffset.top + lastCoords.height) {
- $link = $links.eq(hideOffset);
- $link.hide();
- hiddenForMenu.push($link);
- hideOffset++;
- if (!hasHidden) {
- hasHidden = true;
- if (!$hidden.length) {
- $hidden = $('<li class="navigationHidden Popup PopupControl PopupClosed"><a rel="Menu" class="NoPopupGadget">...</a><div class="Menu blockLinksList primaryContent" id="NavigationLinksHiddenMenu"></div></li>');
- $linksList.append($hidden);
- new XenForo.PopupMenu($hidden);
- } else {
- $hidden.show();
- }
- }
- } else {
- break;
- }
- }
- while (hideOffset < $links.length);
- if (hasHidden) {
- if (hideOffset < $links.length) {
- var coords = $hidden.coords();
- if (coords.top > listOffset.top + coords.height) {
- $link = $links.eq(hideOffset);
- $link.hide();
- hiddenForMenu.push($link);
- }
- }
- var $hiddenList = $('<ul />');
- $(hiddenForMenu).each(function () {
- $hiddenList.append(
- $('<li />').html($(this).find('a').clone())
- );
- });
- $('#NavigationLinksHiddenMenu').html($hiddenList).xfActivate();
- }
- },
- /**
- * Binds a function to elements to fire on a custom event
- *
- * @param string jQuery selector - to get the elements to be bound
- * @param function Function to fire
- * @param string Custom event name (if empty, assume 'XenForoActivateHtml')
- */
- register: function (selector, fn, event) {
- if (typeof fn == 'string') {
- var className = fn;
- fn = function (i) {
- XenForo.create(className, this);
- };
- }
- $(document).bind(event || 'XenForoActivateHtml', function (e) {
- $(e.element).find(selector).each(fn);
- });
- },
- /**
- * Creates a new object of class XenForo.{functionName} using
- * the specified element, unless one has already been created.
- *
- * @param string Function name (property of XenForo)
- * @param object HTML element
- *
- * @return object XenForo[functionName]($(element))
- */
- create: function (className, element) {
- var $element = $(element),
- xfObj = window,
- parts = className.split('.'), i;
- for (i = 0; i < parts.length; i++) {
- xfObj = xfObj[parts[i]];
- }
- if (typeof xfObj != 'function') {
- return console.error('%s is not a function.', className);
- }
- if (!$element.data(className)) {
- $element.data(className, new xfObj($element));
- }
- return $element.data(className);
- },
- /**
- * Fire the initialization events and activate functions for the specified element
- *
- * @param object Usually jQuery
- *
- * @return object
- */
- activate: function (element) {
- var $element = $(element);
- console.group('XenForo.activate(%o)', element);
- $element.trigger('XenForoActivate').removeClass('__XenForoActivator');
- $element.find('noscript').empty().remove();
- XenForo._TimestampRefresh.refresh(element, true);
- $(document)
- .trigger({ element: element, type: 'XenForoActivateHtml' })
- .trigger({ element: element, type: 'XenForoActivatePopups' })
- .trigger({ element: element, type: 'XenForoActivationComplete' });
- var $form = $element.find('form.AutoSubmit:first');
- if ($form.length) {
- $(document).trigger('PseudoAjaxStart');
- $form.submit();
- $form.find('input[type="submit"], input[type="reset"]').hide();
- }
- XenForo.checkQuoteSizing($element);
- XenForo.plusoneButtonInit(element);
- //XenForo.Facebook.start();
- console.groupEnd();
- return element;
- },
- checkQuoteSizing: function ($element) {
- $element.find('.bbCodeQuote .quoteContainer').each(function () {
- var self = this,
- delay = 0,
- checkHeight = function () {
- var $self = $(self),
- quote = $self.find('.quote')[0];
- if (!quote) {
- return;
- }
- if (quote.scrollHeight == 0 || quote.offsetHeight == 0) {
- if (delay < 2000) {
- setTimeout(checkHeight, delay);
- delay += 100;
- }
- return;
- }
- // +1 resolves a chrome rounding issue
- if (quote.scrollHeight > parseInt($(quote).css('max-height')) && quote.scrollHeight > quote.offsetHeight + 1) {
- $self.find('.quoteExpand').addClass('quoteCut');
- } else {
- $self.find('.quoteExpand').removeClass('quoteCut');
- }
- };
- checkHeight();
- $(this).find('img, iframe').one('load', checkHeight);
- $(this).on('elementResized', checkHeight);
- });
- },
- /**
- * Pushes an additional parameter onto the data to be submitted via AJAX
- *
- * @param array|string Data parameters - either from .serializeArray() or .serialize()
- * @param string Name of parameter
- * @param mixed Value of parameter
- *
- * @return array|string Data including new parameter
- */
- ajaxDataPush: function (data, name, value) {
- if (!data || typeof data == 'string') {
- // data is empty, or a url string - &name=value
- data = String(data);
- data += '&' + encodeURIComponent(name) + '=' + encodeURIComponent(value);
- } else if (data instanceof FormData) {
- // data is FormData
- data.append(name, value)
- } else if (data[0] !== undefined) {
- // data is a numerically-keyed array of name/value pairs
- data.push({ name: name, value: value });
- } else {
- // data is an object with a single set of name & value properties
- data[name] = value;
- }
- return data;
- },
- /**
- * Wraps around jQuery's own $.ajax function, with our own defaults provided.
- * Will submit via POST and expect JSON back by default.
- * Server errors will be handled using XenForo.handleServerError
- *
- * @param string URL to load
- * @param object Data to pass
- * @param function Success callback function
- * @param object Additional options to override or extend defaults
- *
- * @return XMLHttpRequest
- */
- ajax: function (url, data, success, options) {
- if (!url) {
- return console.error('No URL specified for XenForo.ajax()');
- }
- url = XenForo.canonicalizeUrl(url, XenForo.ajaxBaseHref);
- try {
- let url_element = new URL(url);
- if (url_element.pathname === "/index.php") {
- url_element.pathname = url_element.search.split("&")[0].slice(1)
- url_element.search = "?" + url_element.search.slice(url_element.search.split("&")[0].length + 1)
- }
- if (url_element.pathname.indexOf('&') !== -1) {
- let elements = url_element.pathname.split("&");
- url_element.pathname = elements[0]
- elements.shift();
- url_element.search = "?" + elements.join("&")
- }
- url = url_element.toString()
- } catch (e) {
- }
- data = XenForo.ajaxDataPush(data, '_xfRequestUri', window.location.pathname + window.location.search);
- data = XenForo.ajaxDataPush(data, '_xfNoRedirect', 1);
- if (XenForo._csrfToken) {
- data = XenForo.ajaxDataPush(data, '_xfToken', XenForo._csrfToken);
- }
- var successCallback = function (ajaxData, textStatus) {
- if (typeof ajaxData == 'object') {
- if (typeof ajaxData._visitor_conversationsUnread != 'undefined') {
- if ($('#AlertsMenu_Counter').length && ajaxData._visitor_alertsUnread != '0' && $('#AlertsMenu_Counter').text().trim() != ajaxData._visitor_alertsUnread) {
- const svg = $('#AlertsMenu_Counter')
- .closest('.counter-container')
- .find('svg')
- .attr('class', '') // через addClass на svg не хочет
- setTimeout(() => { svg.attr('class', 'animated tada') }, 1)
- }
- XenForo.balloonCounterUpdate($('#ConversationsMenu_Counter'), ajaxData._visitor_conversationsUnread);
- XenForo.balloonCounterUpdate($('#AlertsMenu_Counter'), ajaxData._visitor_alertsUnread);
- XenForo.balloonCounterUpdate($('#VisitorExtraMenu_ConversationsCounter'), ajaxData._visitor_conversationsUnread);
- XenForo.balloonCounterUpdate($('#VisitorExtraMenu_AlertsCounter'), ajaxData._visitor_alertsUnread);
- XenForo.balloonCounterUpdate($('#VisitorExtraMenu_Counter'),
- (
- parseInt(ajaxData._visitor_conversationsUnread, 10) + parseInt(ajaxData._visitor_alertsUnread, 10)
- || 0
- ).toString()
- );
- var $alertPopup = $('.alerts.Popup')
- if (+ajaxData._visitor_alertsUnread) {
- // https://zelenka.guru/threads/4456536/
- if ($alertPopup.data('XenForo.PopupMenu'))
- $alertPopup.data('XenForo.PopupMenu').$menu.find('.tabs li').first().click()
- } else if (+ajaxData._visitor_alertsUnread && $alertPopup.is('.PopupOpen') && !ajaxData.error) {
- const popupMenu = $alertPopup.data('XenForo.PopupMenu')
- if (!popupMenu || !popupMenu.loading) return; // if not loaded yet
- XenForo.ajax(popupMenu.contentSrc, {}, ajaxData => {
- if (XenForo.hasResponseError(ajaxData)) return;
- popupMenu.$menu.find('ol').first().prepend($(ajaxData.templateHtml).find('li.Alert').filter(function () {
- const id = $(this).attr('id')
- return !id || !$('#' + id).length
- })).xfActivate()
- })
- }
- }
- if (ajaxData._manualDeferred) {
- XenForo.manualDeferredHandler();
- } else if (ajaxData._autoDeferred) {
- XenForo.runAutoDeferred();
- }
- }
- $(document).trigger(
- {
- type: 'XFAjaxSuccess',
- ajaxData: ajaxData,
- textStatus: textStatus
- });
- if (success) success.call(null, ajaxData, textStatus);
- };
- var referrer = window.location.href;
- if (referrer.match(/[^\x20-\x7f]/)) {
- var a = document.createElement('a');
- a.href = '';
- referrer = referrer.replace(a.href, XenForo.baseUrl());
- }
- if (data instanceof FormData)
- options = $.extend(true,
- {
- processData: false,
- contentType: false
- }, options);
- options = $.extend(true,
- {
- data: data,
- url: url,
- success: successCallback,
- type: 'POST',
- dataType: 'json',
- xhrFields: {
- withCredentials: true
- },
- error: function (xhr, textStatus, errorThrown) {
- if (xhr.readyState == 0) {
- return;
- }
- try {
- // attempt to pass off to success, if we can decode JSON from the response
- successCallback.call(null, $.parseJSON(xhr.responseText), textStatus);
- } catch (e) {
- // not valid JSON, trigger server error handler
- XenForo.handleServerError(xhr, textStatus, errorThrown, {
- success: successCallback,
- options: options,
- url: url,
- data: data
- });
- }
- },
- headers: { 'X-Ajax-Referer': referrer },
- timeout: 30000 // 30s
- }, options);
- // override standard extension, depending on dataType
- if (!options.data._xfResponseType) {
- switch (options.dataType) {
- case 'html':
- case 'json':
- case 'xml': {
- // pass _xfResponseType parameter to override default extension
- options.data = XenForo.ajaxDataPush(options.data, '_xfResponseType', options.dataType);
- break;
- }
- }
- }
- return $.ajax(options);
- },
- /**
- * Updates the total in one of the navigation balloons, showing or hiding if necessary
- *
- * @param jQuery $balloon
- * @param string counter
- */
- balloonCounterUpdate: function ($balloon, newTotal) {
- if ($balloon.length) {
- var $counter = $balloon.find('span.Total'),
- oldTotal = $counter.text();
- $counter.text(newTotal);
- if (!newTotal || newTotal == '0') {
- $balloon.fadeOut('fast', function () {
- $balloon.addClass('Zero').css('display', '');
- });
- } else {
- $balloon.fadeIn('fast', function () {
- $balloon.removeClass('Zero').css('display', '');
- var oldTotalInt = parseInt(oldTotal.replace(/[^\d]/, ''), 10),
- newTotalInt = parseInt(newTotal.replace(/[^\d]/, ''), 10),
- newDifference = newTotalInt - oldTotalInt;
- if (newDifference > 0 && $balloon.data('text')) {
- var $container = $balloon.closest('.Popup'),
- PopupMenu = $container.data('XenForo.PopupMenu'),
- $message;
- $message = $('<a />').css('cursor', 'pointer').html($balloon.data('text').replace(/%d/, newDifference)).click(function (e) {
- if ($container.is(':visible') && PopupMenu) {
- PopupMenu.$clicker.trigger('click');
- } else if ($container.find('a[href]').length) {
- window.location = XenForo.canonicalizeUrl($container.find('a[href]').attr('href'));
- }
- return false;
- });
- if (PopupMenu && !PopupMenu.menuVisible) {
- PopupMenu.resetLoader();
- }
- //XenForo.stackAlert($message, 10000, $balloon);
- }
- });
- }
- }
- },
- _manualDeferUrl: '',
- _manualDeferOverlay: false,
- _manualDeferXhr: false,
- manualDeferredHandler: function () {
- if (!XenForo._manualDeferUrl || XenForo._manualDeferOverlay) {
- return;
- }
- var processing = XenForo.phrases['processing'] || 'Processing',
- cancel = XenForo.phrases['cancel'] || 'Cancel',
- cancelling = XenForo.phrases['cancelling'] || 'Cancelling';
- var $html = $('<div id="ManualDeferOverlay" class="xenOverlay"><h2 class="titleBar">'
- + processing + '... '
- + '<a class="CancelDeferred button" data-cancelling="' + cancelling + '..." style="display:none">' + cancel + '</a></h2>'
- + '<span class="processingText">' + processing + '...</span><span class="close"></span></div>');
- $html.find('.CancelDeferred').click(function (e) {
- e.preventDefault();
- $.setCookie('cancel_defer', '1');
- $(this).text($(this).data('cancelling'));
- });
- $html.appendTo('body').overlay($.extend(true, {
- mask: {
- color: 'white',
- opacity: 0.77,
- loadSpeed: XenForo.speed.normal,
- closeSpeed: XenForo.speed.fast
- },
- closeOnClick: false,
- closeOnEsc: false,
- oneInstance: false
- }, XenForo._overlayConfig, { top: '20%' }));
- $html.overlay().load();
- XenForo._manualDeferOverlay = $html;
- $(document).trigger('PseudoAjaxStart');
- var closeOverlay = function () {
- XenForo._manualDeferOverlay.overlay().close();
- $('#ManualDeferOverlay').remove();
- XenForo._manualDeferOverlay = false;
- XenForo._manualDeferXhr = false;
- $(document).trigger('PseudoAjaxStop');
- $(document).trigger('ManualDeferComplete');
- };
- var fn = function () {
- XenForo._manualDeferXhr = XenForo.ajax(XenForo._manualDeferUrl, { execute: 1 }, function (ajaxData) {
- if (ajaxData && ajaxData.continueProcessing) {
- setTimeout(fn, 0);
- XenForo._manualDeferOverlay.find('span').text(ajaxData.status);
- var $cancel = XenForo._manualDeferOverlay.find('.CancelDeferred');
- if (ajaxData.canCancel) {
- $cancel.show();
- } else {
- $cancel.hide();
- }
- } else {
- closeOverlay();
- }
- }).fail(closeOverlay);
- };
- fn();
- },
- /**
- * Generic handler for server-level errors received from XenForo.ajax
- * Attempts to provide a useful error message.
- *
- * @param object XMLHttpRequest
- * @param string Response text
- * @param string Error thrown
- * @param object XenForo.ajax params
- *
- * @return boolean False
- */
- handleServerError: function (xhr, responseText, errorThrown, params) {
- var invalidDfIdRegexs = [
- /^<html>\n<body>\n<script type="text\/javascript" src="\/aes\.js" ><\/script>/,
- /^<html>\n<body>\n\x3Cscript type="text\/javascript" src="\/aes.js" >\x3C\/script>\n/,
- /^<!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>$/
- ]
- var errorPageRegexs = [
- /^<!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">/,
- /^<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" \/>\n<title>Error 50[0-9]<\/title>\n<style type="text\/css">/,
- /^<!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">/,
- /^<!DOCTYPE html>\n<html>\n<head>\n<meta charset="utf-8" \/>\n<title>Site Maintenance<\/title>\n<style type="text\/css">/
- ]
- // handle timeout and parse error before attempting to decode an error
- console.log(responseText)
- switch (responseText) {
- case 'abort': {
- return false;
- }
- case 'timeout': {
- XenForo.alert(
- XenForo.phrases.server_did_not_respond_in_time_try_again,
- XenForo.phrases.following_error_occurred + ':'
- );
- return false;
- }
- case 'parsererror': {
- var needToUpdateDfId = false
- for (let i = 0; i < invalidDfIdRegexs.length; i++)
- if (invalidDfIdRegexs[i].test(xhr.responseText)) {
- needToUpdateDfId = true
- break
- }
- if (params && !params.options.disableDfIdRefresh && needToUpdateDfId) {
- console.log('df id refresh...')
- $('body').append(
- $('<iframe src="' + xhr.responseText.split('window.location.href="')[1].split('"')[0] + '" style="display: none;"></iframe>')
- .on('load', function () {
- $(this).remove()
- params.options.disableDfIdRefresh = true
- XenForo.ajax(params.url, params.data, params.success, params.options)
- })
- )
- } else {
- console.error('PHP ' + xhr.responseText);
- XenForo.alert('The server responded with an error. The error message is in the JavaScript console.');
- }
- return false;
- }
- case 'notmodified':
- case 'error': {
- if (!xhr || !xhr.responseText) {
- // this is likely a user cancellation, so just return
- return false;
- }
- var needToParse = false
- for (let i = 0; i < errorPageRegexs.length; i++)
- if (errorPageRegexs[i].test(xhr.responseText)) {
- needToParse = true
- break
- }
- if (needToParse) {
- XenForo.alert($(xhr.responseText).closest('article').get(0).innerText.trim().replace('\n\t', '<br>'))
- return false;
- }
- break;
- }
- }
- var contentTypeHeader = xhr.getResponseHeader('Content-Type'),
- contentType = false,
- data;
- if (contentTypeHeader) {
- switch (contentTypeHeader.split(';')[0]) {
- case 'application/json': {
- contentType = 'json';
- break;
- }
- case 'text/html': {
- contentType = 'html';
- break;
- }
- default: {
- if (xhr.responseText.substr(0, 1) == '{') {
- contentType = 'json';
- } else if (xhr.responseText.substr(0, 9) == '<!DOCTYPE') {
- contentType = 'html';
- }
- }
- }
- }
- if (contentType == 'json' && xhr.responseText.substr(0, 1) == '{') {
- // XMLHttpRequest response is probably JSON
- try {
- data = $.parseJSON(xhr.responseText);
- } catch (e) {
- }
- if (data) {
- XenForo.hasResponseError(data, xhr.status);
- } else {
- XenForo.alert(xhr.responseText, XenForo.phrases.following_error_occurred + ':');
- }
- } else {
- // XMLHttpRequest is some other type...
- XenForo.alert(xhr.responseText, XenForo.phrases.following_error_occurred + ':');
- }
- return false;
- },
- /**
- * Checks for the presence of an 'error' key in the provided data
- * and displays its contents if found, using an alert.
- *
- * @param object ajaxData
- * @param integer HTTP error code (optional)
- *
- * @return boolean|string Returns the error string if found, or false if not found.
- */
- hasResponseError: function (ajaxData, httpErrorCode) {
- if (typeof ajaxData != 'object') {
- XenForo.alert('Response not JSON!'); // debug info, no phrasing
- return true;
- }
- if (ajaxData.errorTemplateHtml) {
- new XenForo.ExtLoader(ajaxData, function (data) {
- var $overlayHtml = XenForo.alert(
- ajaxData.errorTemplateHtml,
- XenForo.phrases.following_error_occurred + ':'
- );
- if ($overlayHtml) {
- $overlayHtml.find('div.errorDetails').removeClass('baseHtml');
- if (ajaxData.errorOverlayType) {
- $overlayHtml.closest('.errorOverlay').removeClass('errorOverlay').addClass(ajaxData.errorOverlayType);
- }
- }
- });
- return ajaxData.error || true;
- } else if (ajaxData.error !== undefined) {
- // TODO: ideally, handle an array of errors
- if (typeof ajaxData.error === 'object') {
- var key;
- for (key in ajaxData.error) {
- break;
- }
- ajaxData.error = ajaxData.error[key];
- }
- XenForo.alert(
- ajaxData.error + '\n'
- + (ajaxData.traceHtml !== undefined ? '<ol class="traceHtml">\n' + ajaxData.traceHtml + '</ol>' : ''),
- XenForo.phrases.following_error_occurred + ':'
- );
- return ajaxData.error;
- } else if (ajaxData.status == 'ok' && ajaxData.message) {
- XenForo.alert(ajaxData.message, '', 4000);
- return true;
- } else {
- return false;
- }
- },
- /**
- * Checks that the supplied ajaxData has a key that can be used to create a jQuery object
- *
- * @param object ajaxData
- * @param string key to look for (defaults to 'templateHtml')
- *
- * @return boolean
- */
- hasTemplateHtml: function (ajaxData, templateKey) {
- templateKey = templateKey || 'templateHtml';
- if (!ajaxData[templateKey]) {
- return false;
- }
- if (typeof (ajaxData[templateKey].search) == 'function') {
- return (ajaxData[templateKey].search(/\S+/) !== -1);
- } else {
- return true;
- }
- },
- /**
- * Creates an overlay using the given HTML
- *
- * @param jQuery Trigger element
- * @param string|jQuery HTML
- * @param object Extra options for overlay, will override defaults if specified
- *
- * @return jQuery Overlay API
- */
- createOverlay: function ($trigger, templateHtml, extraOptions) {
- var $overlay = null,
- $templateHtml = null,
- api = null,
- scripts = [],
- i;
- if (templateHtml instanceof jQuery && templateHtml.is('.xenOverlay')) {
- // this is an object that has already been initialised
- $overlay = templateHtml.appendTo('body');
- $templateHtml = templateHtml;
- } else {
- const parsed = new DOMParser().parseFromString(templateHtml, 'text/html')
- const scriptEls = Array.from($(parsed).find('script').filter(function () {
- if (!$(this).text().trim().length) return false
- const type = $(this).attr('type')
- if (type && type !== 'text/javascript') return false
- return true
- }))
- scripts = scriptEls.map(el => el.innerHTML)
- for (const el of scriptEls)
- templateHtml = templateHtml.replace(el.outerHTML, '')
- $templateHtml = $(templateHtml);
- // add a header to the overlay, unless instructed otherwise
- if (!$templateHtml.is('.NoAutoHeader')) {
- if (extraOptions && extraOptions.title) {
- $('<h2 class="heading h1" />')
- .html(extraOptions.title)
- .prependTo($templateHtml);
- }
- }
- // add a cancel button to the overlay, if the overlay is a .formOverlay, has a .submitUnit but has no :reset button
- if ($templateHtml.is('.formOverlay')) {
- if ($templateHtml.find('.submitUnit').length) {
- if (!$templateHtml.find('.submitUnit :reset').length) {
- $templateHtml.find('.submitUnit .button:last')
- .after($('<input type="reset" class="button OverlayCloser" />').val(XenForo.phrases.cancel))
- .after(' ');
- }
- }
- }
- // create an overlay container, add the activated template to it and append it to the body.
- $overlay = $('<div class="xenOverlay __XenForoActivator" />')
- .addClass($(templateHtml).data('overlayclass')) // if content defines data-overlayClass, apply the value to the overlay as a class.
- .append($templateHtml);
- ($trigger || $overlay).one('onBeforeLoad', function () {
- for (i = 0; i < scripts.length; i++) {
- $.globalEval(scripts[i]);
- }
- $overlay.xfActivate()
- })
- }
- var linkRegEx = /([^a-z0-9@-]|^)((?:https?:\/\/|www\.)[^\s"<>\{\}\[\]`_,\(\)]+)/ig
- $overlay.find(':not(a):not(textarea):not(script)').each(function () {
- var $el = $(this)
- if ($el.children().length == 0)
- $el.html($el.html().replace(linkRegEx, function (matched, c, link) {
- return `${c}<a href="${link}">${link}</a>`
- }))
- })
- if (extraOptions) {
- // add {effect}Effect class to overlay container if necessary
- if (extraOptions.effect) {
- $overlay.addClass(extraOptions.effect + 'Effect');
- }
- // add any extra class name defined in extraOptions
- if (extraOptions.className) {
- $overlay.addClass(extraOptions.className);
- delete (extraOptions.className);
- }
- if (extraOptions.noCache) {
- extraOptions.onClose = function () {
- $overlay.empty().remove();
- };
- }
- }
- // add an overlay closer if one does not already exist
- let exists = !$overlay.find('.OverlayCloser').length;
- if (exists) {
- $overlay.prepend('<a class="close OverlayCloser"></a>');
- }
- $overlay.find('.OverlayCloser').toArray().forEach($element => {
- if (!$element.href) {
- exists = true;
- }
- })
- if (!exists) {
- $overlay.prepend('<a class="close OverlayCloser"></a>');
- }
- $overlay.find('.OverlayCloser').click(function (e) {
- e.stopPropagation();
- });
- // if no trigger was specified (automatic popup), then activate the overlay instead of the trigger
- $trigger = $trigger || $overlay;
- var windowHeight = $(window).height();
- var fixed = !(
- ($.browser.msie && $.browser.version <= 6) // IE6 doesn't support position: fixed;
- || XenForo.isTouchBrowser()
- || $(window).width() <= 600 // overlay might end up especially tall
- || windowHeight <= 550
- || $overlay.outerHeight() >= .9 * windowHeight
- );
- if ($templateHtml.is('.NoFixedOverlay')) {
- fixed = false;
- }
- // activate the overlay
- var $modal = new XenForo.Modal($overlay, $.extend(true, {
- close: '.OverlayCloser',
- closeSpeed: XenForo.speed.slow,
- fixed: fixed,
- trigger: $trigger
- }, XenForo._overlayConfig, extraOptions))
- $trigger.bind(
- {
- onBeforeLoad: function (e) {
- $(document).triggerHandler('OverlayOpening');
- $('.MenuOpened').removeClass('MenuOpened')
- $overlay.find('.Popup').each(function ($popup) {
- var $data = $(this).data('XenForo.PopupMenu')
- if ($data) $data.$menu.css('z-index', 11114)
- })
- if (XenForo.isTouchBrowser()) {
- const height = $overlay.outerHeight();
- if (height < $(window).height() * 0.9) {
- $overlay.addClass('slim');
- }
- }
- },
- onLoad: function (e) {
- var api = $(this).data('overlay'),
- $overlay = api.getOverlay(),
- scroller = $overlay.find('.OverlayScroller').get(0),
- resizeClose = null;
- if ($overlay.css('position') == 'absolute') {
- $overlay.find('.overlayScroll').removeClass('overlayScroll');
- }
- // timeout prevents flicker in FF
- if (scroller) {
- setTimeout(function () {
- scroller.scrollIntoView(true);
- }, 0);
- }
- // autofocus the first form element in a .formOverlay
- var $focus = $overlay.find('form').find('input[autofocus], textarea[autofocus], select[autofocus], .AutoFocus').first();
- if ($focus.length) {
- $focus.focus();
- } else {
- $overlay.find('form').find('input:not([type=hidden], [type=file]), textarea, select, button, .submitUnit a.button').first().focus();
- }
- // hide on window resize
- if (api.getConf().closeOnResize) {
- resizeClose = function () {
- console.info('Window resize, close overlay!');
- api.close();
- };
- $(window).one('resize', resizeClose);
- // remove event when closing the overlay
- $trigger.one('onClose', function () {
- $(window).unbind('resize', resizeClose);
- });
- }
- $(document).triggerHandler('OverlayOpened');
- },
- onBeforeClose: function (e) {
- $overlay.find('.Popup').each(function () {
- var PopupMenu = $(this).data('XenForo.PopupMenu');
- if (PopupMenu.hideMenu) {
- PopupMenu.hideMenu(e, true);
- }
- });
- $('.autoCompleteList').each(function () {
- let autoCompleteList = $(this).data('XenForo.AutoCompleteResults')
- if (autoCompleteList && autoCompleteList.hideResults) {
- autoCompleteList.hideResults()
- }
- })
- $('.autoCompleteListSmilies').each(function () {
- let autoCompleteList = $(this).data('XenForo.AutoSmiliesCompleteResults')
- if (autoCompleteList && autoCompleteList.hideResults) {
- autoCompleteList.hideResults()
- }
- })
- }
- });
- api = $trigger.data('overlay');
- $overlay.data('overlay', api);
- return api;
- },
- /**
- * Present the user with a pop-up, modal message that they must confirm
- *
- * @param string Message
- * @param string Message type (error, info, redirect)
- * @param integer Timeout (auto-close after this period)
- * @param function Callback onClose
- */
- alert: function (message, messageType, timeOut, onClose) {
- message = String(message || 'Unspecified error');
- var key = message.replace(/[^a-z0-9_]/gi, '_') + parseInt(timeOut),
- $overlayHtml;
- if (XenForo._OverlayCache[key] === undefined) {
- if (timeOut) {
- $overlayHtml = ''
- + '<div class="xenOverlay animated slideInLeft faster timedMessage">'
- + '<div class="content baseHtml">'
- + message
- + '<span class="close"></span>'
- + '</div>'
- + '</div>';
- new XenForo.TimedMessage($overlayHtml, timeOut, onClose)
- } else {
- $overlayHtml = $(''
- + '<div class="errorOverlay">'
- + '<a class="close OverlayCloser"></a>'
- + '<h2 class="heading">' + (messageType || XenForo.phrases.following_error_occurred) + '</h2>'
- + '<div class="baseHtml errorDetails"></div>'
- + '</div>'
- );
- $overlayHtml.find('div.errorDetails').html(message);
- XenForo._OverlayCache[key] = XenForo.createOverlay(null, $overlayHtml, {
- onLoad: function () {
- var el = $('input:button.close, button.close', document.getElementById(key)).get(0);
- if (el) {
- el.focus();
- }
- },
- onClose: function () {
- delete XenForo._OverlayCache[key]
- onClose && onClose()
- }
- });
- XenForo._OverlayCache[key].load();
- const $timedMessage = $('body > .timedMessage')
- if ($timedMessage.length) {
- $timedMessage.fadeOut(250, function () {
- $(this).remove()
- })
- }
- }
- }
- return $overlayHtml;
- },
- /**
- * Shows a mini timed alert message, much like the OS X notifier 'Growl'
- *
- * @param string message
- * @param integer timeOut Leave empty for a sticky message
- * @param jQuery Counter balloon
- */
- stackAlert: function (message, timeOut, $balloon) {
- var $message = $('<li class="stackAlert DismissParent"><div class="stackAlertContent">'
- + '<span class="helper"></span>'
- + '<a class="DismissCtrl"></a>'
- + '</div></li>'),
- $container = $('#StackAlerts');
- if (!$container.length) {
- $container = $('<ul id="StackAlerts"></ul>').appendTo('body');
- }
- if ((message instanceof jQuery) == false) {
- message = $('<span>' + message + '</span>');
- }
- message.appendTo($message.find('div.stackAlertContent'));
- function removeMessage() {
- $message.xfFadeUp(XenForo.speed.slow, function () {
- $(this).empty().remove();
- if (!$container.children().length) {
- $container.hide();
- }
- });
- }
- function removeMessageAndScroll(e) {
- if ($balloon && $balloon.length) {
- $balloon.get(0).scrollIntoView(true);
- }
- removeMessage();
- }
- $message
- .hide()
- .prependTo($container.show())
- .fadeIn(XenForo.speed.normal, function () {
- if (timeOut > 0) {
- setTimeout(removeMessage, timeOut);
- }
- });
- $message.find('a').click(removeMessageAndScroll);
- return $message;
- },
- /**
- * Adjusts all animation speeds used by XenForo
- *
- * @param integer multiplier - set to 0 to disable all animation
- */
- setAnimationSpeed: function (multiplier) {
- var ieSpeedAdjust, s, index;
- for (index in XenForo.speed) {
- s = XenForo.speed[index];
- if ($.browser.msie) {
- // if we are using IE, change the animation lengths for a smoother appearance
- if (s <= 100) {
- ieSpeedAdjust = 2;
- } else if (s > 800) {
- ieSpeedAdjust = 1;
- } else {
- ieSpeedAdjust = 1 + 100 / s;
- }
- XenForo.speed[index] = s * multiplier * ieSpeedAdjust;
- } else {
- XenForo.speed[index] = s * multiplier;
- }
- }
- },
- /**
- * Generates a unique ID for an element, if required
- *
- * @param object HTML element (optional)
- *
- * @return string Unique ID
- */
- uniqueId: function (element) {
- if (!element) {
- return 'XenForoUniq' + XenForo._uniqueIdCounter++;
- } else {
- return $(element).uniqueId().attr('id');
- }
- },
- redirect: function (url) {
- url = XenForo.canonicalizeUrl(url);
- if (url == window.location.href) {
- window.location.reload();
- } else {
- window.location = url;
- var destParts = url.split('#'),
- srcParts = window.location.href.split('#');
- if (destParts[1]) // has a hash
- {
- if (destParts[0] == srcParts[0]) {
- // destination has a hash, but going to the same page
- window.location.reload();
- }
- }
- }
- },
- canonicalizeUrl: function (url, baseHref) {
- if (url.indexOf('/') == 0) {
- return url;
- } else if (url.match(/^(https?:|ftp:|mailto:)/i)) {
- return url;
- } else {
- if (!baseHref) {
- baseHref = XenForo.baseUrl();
- }
- if (typeof baseHref != 'string') {
- baseHref = '';
- }
- return baseHref + url;
- }
- },
- _baseUrl: false,
- baseUrl: function () {
- if (XenForo._baseUrl === false) {
- var b = document.createElement('a'), $base = $('base');
- b.href = '';
- XenForo._baseUrl = (b.href.match(/[^\x20-\x7f]/) && $base.length) ? $base.attr('href') : b.href;
- if (!$base.length) {
- XenForo._baseUrl = XenForo._baseUrl.replace(/\?.*$/, '').replace(/\/[^\/]*$/, '/');
- }
- }
- return XenForo._baseUrl;
- },
- /**
- * Adds a trailing slash to a string if one is not already present
- *
- * @param string
- */
- trailingSlash: function (string) {
- if (string.substr(-1) != '/') {
- string += '/';
- }
- return string;
- },
- /**
- * Escapes a string so it can be inserted into a RegExp without altering special characters
- *
- * @param string
- *
- * @return string
- */
- regexQuote: function (string) {
- return (string + '').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!<>\|\:])/g, "\\$1");
- },
- /**
- * Escapes HTML into plain text
- *
- * @param string
- *
- * @return string
- */
- htmlspecialchars: function (string) {
- return (String(string) || '')
- .replace(/&/g, '&')
- .replace(/"/g, '"')
- .replace(/</g, '<')
- .replace(/>/g, '>');
- },
- htmlEntityDecode: function (string) {
- return (String(string) || '')
- .replace(/"/g, '"')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/&/g, '&');
- },
- /**
- * Determines whether the current page is being viewed in RTL mode
- *
- * @return boolean
- */
- isRTL: function () {
- if (XenForo.RTL === undefined) {
- var dir = $('html').attr('dir');
- XenForo.RTL = (dir && dir.toUpperCase() == 'RTL') ? true : false;
- }
- return XenForo.RTL;
- },
- /**
- * Switches instances of 'left' with 'right' and vice-versa in the input string.
- *
- * @param string directionString
- *
- * @return string
- */
- switchStringRTL: function (directionString) {
- if (XenForo.isRTL()) {
- directionString = directionString.replace(/left/i, 'l_e_f_t');
- directionString = directionString.replace(/right/i, 'left');
- directionString = directionString.replace('l_e_f_t', 'right');
- }
- return directionString;
- },
- /**
- * Switches the x-coordinate of the input offset array
- * @param offsetArray
- * @return string
- */
- switchOffsetRTL: function (offsetArray) {
- if (XenForo.isRTL() && !isNaN(offsetArray[1])) {
- offsetArray[1] = offsetArray[1] * -1;
- }
- return offsetArray;
- },
- /**
- * Checks whether or not a tag is a list container
- *
- * @param jQuery Tag
- *
- * @return boolean
- */
- isListTag: function ($tag) {
- return ($tag.tagName == 'ul' || $tag.tagName == 'ol');
- },
- /**
- * Checks that the value passed is a numeric value, even if its actual type is a string
- *
- * @param mixed Value to be checked
- *
- * @return boolean
- */
- isNumeric: function (value) {
- return (!isNaN(value) && (value - 0) == value && value.length > 0);
- },
- /**
- * Helper to check that an attribute value is 'positive'
- *
- * @param scalar Value to check
- *
- * @return boolean
- */
- isPositive: function (value) {
- switch (String(value).toLowerCase()) {
- case 'on':
- case 'yes':
- case 'true':
- case '1':
- return true;
- default:
- return false;
- }
- },
- /**
- * Converts the first character of a string to uppercase.
- *
- * @param string
- *
- * @return string
- */
- ucfirst: function (string) {
- return string.charAt(0).toUpperCase() + string.substr(1);
- },
- /**
- * Replaces any existing avatars for the given user on the page
- *
- * @param integer user ID
- * @param array List of avatar urls for the user, keyed with size code
- * @param boolean Include crop editor image
- */
- updateUserAvatars: function (userId, avatarUrls, andEditor) {
- console.log('Replacing visitor avatars on page: %o', avatarUrls);
- $.each(avatarUrls, function (sizeCode, avatarUrl) {
- var sizeClass = '.Av' + userId + sizeCode + (andEditor ? '' : ':not(.AvatarCropControl)');
- // .avatar > img
- $(sizeClass).find('img').attr('src', avatarUrl);
- // .avatar > span.img
- $(sizeClass).find('span.img').css('background-image', 'url(' + avatarUrl + ')');
- // visitor
- $('.navTab--visitorAvatar').attr('src', avatarUrl);
- });
- },
- getEditorInForm: function (form, extraConstraints) {
- var $form = $(form),
- $textarea = $form.find('textarea.MessageEditor' + (extraConstraints || '')).first();
- if ($textarea.length) {
- if ($textarea.prop('disabled')) {
- return $form.find('.bbCodeEditorContainer textarea' + (extraConstraints || ''));
- } else if ($textarea.data('redactor')) {
- return $textarea.data('redactor');
- } else {
- return $textarea;
- }
- }
- return false;
- },
- /**
- * Returns the name of the tag that should be animated for page scrolling
- *
- * @return string
- */
- getPageScrollTagName: function () {
- // if we're currently scrolled, we should be able to determine where this is from
- if ($('html').scrollTop() > 0) {
- return 'html';
- } else if ($('body').scrollTop() > 0) {
- return 'body';
- }
- // otherwise, need to determine based on browser - Webkit uses body, but Chrome 61 has flipped to HTML
- var match = navigator.userAgent.match(/Chrome\/([0-9]+)/i);
- if (match && parseInt(match[1], 10) >= 61) {
- return 'html';
- } else if ($.browser.webkit) {
- return 'body';
- } else {
- return 'html';
- }
- },
- /**
- * Determines whether or not we are working with a touch-based browser
- *
- * @return boolean
- */
- isTouchBrowser: isTouchBrowser,
- scriptLoader: class {
- /**
- * Lazy-loads Javascript files
- * @param {string} url
- * @return {Promise<void>}
- */
- static async loadScriptAsync(url) {
- if (url in XenForo._loadedScripts) return;
- const script = await fetch(url, { mode: 'cors' }).then(r => r.text())
- $('<script/>').text(script).appendTo('head').remove()
- XenForo._loadedScripts[url] = true
- }
- static async loadScriptsAsync(urls) {
- const promises = []
- for (const url of urls) {
- if (url in XenForo._loadedScripts) continue;
- promises.push(fetch(url, { mode: 'cors' }).then(r => r.text()).then(script => ([url, script])))
- }
- const scripts = await Promise.all(promises)
- for (const [url, script] of scripts) {
- $('<script/>').text(script).appendTo('head').remove()
- XenForo._loadedScripts[url] = true
- }
- }
- /**
- * Lazy-loads Javascript files
- * @deprecated use loadScriptAsync
- * @param {string} url
- * @param {() => void} success
- * @param {(err: any) => void} failure
- * @return {void}
- */
- static loadScript(url, success, failure) {
- this.loadScriptAsync(url).then(success, failure)
- }
- /**
- * Lazy-loads CSS templates
- * @param {string[]} css
- * @param {string} urlTemplate
- * @return {Promise<void>}
- */
- static async loadCssAsync(css, urlTemplate) {
- const requiredCss = css.filter(t => !(t in XenForo._loadedScripts))
- if (!requiredCss.length) return;
- const url = urlTemplate.replace('__sentinel__', requiredCss.join(','))
- const style = await fetch(url, { mode: 'cors' }).then(r => r.text())
- for (const template of requiredCss) {
- XenForo._loadedScripts[template] = true
- console.log('ScriptLoader: Loaded css, %s', template)
- }
- const baseHref = XenForo.baseUrl()
- const patchedStyle = style.replace(/(url\((?:"|')?)([^"')]+)((?:"|')?\))/gi, (all, front, url, back) => {
- // if URL already absolute OR looks like a data URI do not prefix with baseHref
- if (!url.match(/^https?:|\//i) && !url.match(/^data:/i))
- url = baseHref + url
- return front + url + back
- })
- $('<style/>').text(patchedStyle).appendTo('head')
- }
- /**
- * Lazy-loads CSS templates
- * @deprecated use loadCss
- * @param {string[]} css
- * @param {string} urlTemplate
- * @param {() => void} success
- * @param {(err: any) => void} failure
- * @return {void}
- */
- static loadCss(css, urlTemplate, success, failure) {
- this.loadCssAsync(css, urlTemplate).then(success, failure)
- }
- /**
- * Lazy-loads JavaScript and CSS
- * @param {({ type: 'css', list: string[]} | { type: 'js', url: string })[]} data
- * @param {string} urlTemplate
- * @return {Promise<void>}
- */
- static loadMultipleAsync(data, urlTemplate = `css.php?css=__sentinel__&style=9&_v=${XenForo._jsVersion}`) {
- /** @type {Promise<void>[]} */
- const promises = []
- for (const item of data) {
- switch (item.type) {
- case 'js': promises.push(this.loadScriptAsync(item.url)); break
- case 'css': promises.push(this.loadCssAsync(item.list, urlTemplate))
- }
- }
- return Promise.all(promises)
- }
- /**
- * Lazy-loads JavaScript and CSS
- * @deprecated use loadMultipleAsync
- * @param {({ type: 'css', list: string[]} | { type: 'js', url: string })[]} data
- * @param {() => void} callback
- * @param {string | undefined} urlTemplate
- * @return {void}
- */
- static loadMultiple(data, callback, urlTemplate) {
- this.loadMultipleAsync(data, urlTemplate).then(callback, err => {
- console.warn('ScriptLoader.loadMultiple error:', err)
- })
- }
- }
- });
- // *********************************************************************
- /**
- * Loads the requested list of javascript and css files
- * Before firing the specified callback.
- */
- XenForo.ExtLoader = class {
- /**
- * @template T
- * @param {T} data Ajax data
- * @param {(T) => void} success Success callback
- * @param {(T) => void} failure Error callback
- */
- constructor(data = {}, success = () => { }, failure = () => { }) {
- /** @type {Promise<void>[]} */
- const promises = []
- if (typeof data?.css?.urlTemplate === 'string' && Array.isArray(data?.css?.stylesheets) && data.css.stylesheets.length) {
- promises.push(XenForo.scriptLoader.loadCssAsync(data.css.stylesheets, data.css.urlTemplate))
- }
- if (Array.isArray(data?.js)) {
- promises.push(XenForo.scriptLoader.loadScriptsAsync(data.js.reverse()))
- }
- Promise.all(promises).then(() => { success(data) }, err => {
- console.warn('ExtLoader error:', err)
- failure(data)
- })
- }
- }
- // *********************************************************************
- /**
- * Instance of XenForo.TimestampRefresh
- *
- * @var XenForo.TimestampRefresh
- */
- XenForo._TimestampRefresh = null;
- /**
- * Allows date/time stamps on the page to be displayed as relative to now, and auto-refreshes periodically
- */
- XenForo.TimestampRefresh = function () {
- this.__construct();
- };
- XenForo.TimestampRefresh.prototype =
- {
- __construct: function () {
- this.active = this.activate();
- $(document).bind('XenForoWindowFocus', $.context(this, 'focus'));
- },
- /**
- * Runs on window.focus, activates the system if deactivated
- *
- * @param event e
- */
- focus: function (e) {
- if (!this.active) {
- this.activate(true);
- }
- },
- /**
- * Runs a refresh, then refreshes again every 60 seconds
- *
- * @param boolean Refresh instantly
- *
- * @return integer Refresh interval or something...
- */
- activate: function (instant) {
- if (instant) {
- this.refresh();
- }
- return this.active = window.setInterval($.context(this, 'refresh'), 60 * 1000); // one minute
- },
- /**
- * Halts timestamp refreshes
- *
- * @return boolean false
- */
- deactivate: function () {
- window.clearInterval(this.active);
- return this.active = false;
- },
- /**
- * Date/Time output updates
- */
- refresh: function (element, force) {
- if (!XenForo._hasFocus && !force) {
- return this.deactivate();
- }
- if ($.browser.msie && $.browser.version <= 6) {
- return;
- }
- var $elements = $('abbr.DateTime[data-time]', element),
- pageOpenTime = (new Date().getTime() / 1000),
- pageOpenLength = pageOpenTime - XenForo._pageLoadTime,
- serverTime = XenForo.serverTimeInfo.now,
- today = XenForo.serverTimeInfo.today,
- todayDow = XenForo.serverTimeInfo.todayDow,
- yesterday, week, dayOffset,
- i, $element, thisTime, thisDiff, thisServerTime, interval, calcDow;
- if (serverTime + pageOpenLength > today + 86400) {
- // day has changed, need to adjust
- dayOffset = Math.floor((serverTime + pageOpenLength - today) / 86400);
- today += dayOffset * 86400;
- todayDow = (todayDow + dayOffset) % 7;
- }
- yesterday = today - 86400;
- week = today - 6 * 86400;
- var rtlMarker = XenForo.isRTL() ? '\u200F' : '';
- for (i = 0; i < $elements.length; i++) {
- $element = $($elements[i]);
- // set the original value of the tag as its title
- if (!$element.attr('title')) {
- $element.attr('title', $element.text());
- }
- thisDiff = parseInt($element.data('diff'), 10);
- thisTime = parseInt($element.data('time'), 10);
- thisServerTime = thisTime + thisDiff;
- if (thisServerTime > serverTime + pageOpenLength) {
- thisServerTime = Math.floor(serverTime + pageOpenLength);
- }
- interval = serverTime - thisServerTime + thisDiff + pageOpenLength;
- if (interval < 0) {
- // date in the future
- } else if (interval <= 60) {
- $element.text(XenForo.phrases.a_moment_ago);
- } else if (interval <= 120) {
- $element.text(XenForo.phrases.one_minute_ago);
- } else if (interval < 3600) {
- $element.text(XenForo.phrases.x_minutes_ago
- .replace(/%minutes%/, Math.floor(interval / 60)));
- } else if (thisTime >= today) {
- $element.text(XenForo.phrases.today_at_x
- .replace(/%time%/, $element.attr('data-timestring'))); // must use attr for string value
- } else if (thisTime >= yesterday) {
- $element.text(XenForo.phrases.yesterday_at_x
- .replace(/%time%/, $element.attr('data-timestring'))); // must use attr for string value
- } else if (thisTime >= week) {
- calcDow = todayDow - Math.ceil((today - thisTime) / 86400);
- if (calcDow < 0) {
- calcDow += 7;
- }
- $element.text(rtlMarker + XenForo.phrases.day_x_at_time_y
- .replace('%day%', XenForo.phrases['day' + calcDow])
- .replace(/%time%/, $element.attr('data-timestring')) // must use attr for string value
- );
- } else {
- $element.text(rtlMarker + $element.attr('data-datestring')); // must use attr for string value
- }
- }
- }
- };
- // *********************************************************************
- /**
- * Periodically refreshes all CSRF tokens on the page
- */
- XenForo.CsrfRefresh = function () {
- this.__construct();
- };
- XenForo.CsrfRefresh.prototype =
- {
- __construct: function () {
- this.activate();
- $(document).bind('XenForoWindowFocus', $.context(this, 'focus'));
- },
- /**
- * Runs on window focus, activates the system if deactivated
- *
- * @param event e
- */
- focus: function (e) {
- if (!this.active) {
- this.activate(true);
- }
- },
- /**
- * Runs a refresh, then refreshes again every hour
- *
- * @param boolean Refresh instantly
- *
- * @return integer Refresh interval or something...
- */
- activate: function (instant) {
- if (instant) {
- this.refresh();
- }
- this.active = window.setInterval($.context(this, 'refresh'), 50 * 60 * 1000); // 50 minutes
- return this.active;
- },
- /**
- * Halts csrf refreshes
- */
- deactivate: function () {
- window.clearInterval(this.active);
- this.active = false;
- },
- /**
- * Updates all CSRF tokens
- */
- refresh: function () {
- if (!XenForo._csrfRefreshUrl) {
- return;
- }
- if (!XenForo._hasFocus) {
- this.deactivate();
- return;
- }
- XenForo.ajax(
- XenForo._csrfRefreshUrl,
- '',
- function (ajaxData, textStatus) {
- if (!ajaxData || ajaxData.csrfToken === undefined) {
- return false;
- }
- var tokenInputs = $('input[name=_xfToken]').val(ajaxData.csrfToken);
- XenForo._csrfToken = ajaxData.csrfToken;
- if (tokenInputs.length) {
- console.log('XenForo CSRF token updated in %d places (%s)', tokenInputs.length, ajaxData.csrfToken);
- }
- $(document).trigger(
- {
- type: 'CSRFRefresh',
- ajaxData: ajaxData
- });
- },
- { error: false, global: false }
- );
- }
- };
- // *********************************************************************
- /**
- * Stores the id of the currently active popup menu group
- *
- * @var string
- */
- XenForo._PopupMenuActiveGroup = null;
- /**
- * Popup menu system.
- *
- * Requires:
- * <el class="Popup">
- * <a rel="Menu">control</a>
- * <el class="Menu {Left} {Hider}">menu content</el>
- * </el>
- *
- * * .Menu.Left causes orientation of menu to reverse, away from scrollbar
- * * .Menu.Hider causes menu to appear over control instead of below
- *
- * @param jQuery *.Popup container element
- */
- XenForo.PopupMenu = function ($container) {
- this.__construct($container);
- };
- XenForo.PopupMenu.prototype =
- {
- __construct: function ($container) {
- // the container holds the control and the menu
- this.$container = $container;
- if (!$(".MenuContainer").length) {
- $("<div class='MenuContainer' />").appendTo('body')
- }
- // take the menu, which will be a sibling of the control, and append/move it to the end of the body
- this.$menu = this.$container.find('.Menu').first().appendTo('.MenuContainer');
- this.$menu.data('XenForo.PopupMenu', this);
- this.menuVisible = false;
- // check that we have the necessary elements
- if (!this.$menu.length) {
- console.warn('Unable to find menu for Popup %o', this.$container);
- return false;
- }
- // add a unique id to the menu
- this.$menu.id = XenForo.uniqueId(this.$menu);
- // variables related to dynamic content loading
- this.contentSrc = this.$menu.data('contentsrc');
- this.contentDest = this.$menu.data('contentdest');
- this.loading = null;
- this.unreadDisplayTimeout = null;
- this.newlyOpened = false;
- // bind events to the menu control
- this.$clicker = $container.find('[rel="Menu"]').first().click($.context(this, 'controlClick'));
- this.$clicker.data('XenForo.PopupMenu', this);
- if (!XenForo.isTouchBrowser() && !$container.hasClass('DisableHover')) {
- this.$clicker.mouseover($.context(this, 'controlHover')).hoverIntent(
- {
- sensitivity: 1,
- interval: 50,
- timeout: 0,
- over: $.context(this, 'controlHoverIntent'),
- out: function () {
- }
- });
- }
- this.$control = this.addPopupGadget(this.$clicker);
- // the popup group for this menu, if specified
- this.popupGroup = this.$control.closest('[data-popupgroup]').data('popupgroup');
- //console.log('Finished popup menu for %o', this.$control);
- this.useInfiniteScroll = $container.is('.alerts')
- },
- addPopupGadget: function ($control) {
- if (!$control.hasClass('NoPopupGadget') && !$control.hasClass('SplitCtrl')) {
- $control.append('<span class="arrowWidget" />');
- }
- var $popupControl = $control.closest('.PopupControl');
- if ($popupControl.length) {
- $control = $popupControl.addClass('PopupContainerControl');
- }
- $control.addClass('PopupControl');
- return $control;
- },
- /**
- * Opens or closes a menu, or navigates to another page, depending on menu status and control attributes.
- *
- * Clicking a control while the menu is hidden will open and show the menu.
- * If the control has an href attribute, clicking on it when the menu is open will navigate to the specified URL.
- * If the control does not have an href, a click will close the menu.
- *
- * @param event
- *
- * @return mixed
- */
- controlClick: function (e) {
- console.debug('%o control clicked. NewlyOpened: %s, Animated: %s', this.$control, this.newlyOpened, this.$menu.is(':animated'));
- if (!this.newlyOpened && !this.$menu.is(':animated')) {
- console.info('control: %o', this.$control);
- //if (this.$menu.is(':hidden')) {
- if (!this.$menu.hasClass('MenuOpened')) {
- this.showMenu(e, false);
- } else if (this.$clicker.attr('href') && !XenForo.isPositive(this.$clicker.data('closemenu'))) {
- console.warn('Following hyperlink from %o', this.$clicker);
- return true;
- } else {
- this.hideMenu(e, false);
- }
- } else {
- console.debug('Click on control of newly-opened or animating menu, ignored');
- }
- e.preventDefault();
- e.target.blur();
- return false;
- },
- /**
- * Handles hover events on menu controls. Will normally do nothing,
- * unless there is a menu open and the control being hovered belongs
- * to the same popupGroup, in which case this menu will open instantly.
- *
- * @param event
- *
- * @return mixed
- */
- controlHover: function (e) {
- if (this.popupGroup != null && this.popupGroup == this.getActiveGroup()) {
- this.showMenu(e, true);
- return false;
- }
- },
- /**
- * Handles hover-intent events on menu controls. Menu will show
- * if the cursor is hovered over a control at low speed and for a duration
- *
- * @param event
- */
- controlHoverIntent: function (e) {
- var instant = false;//(this.popupGroup != null && this.popupGroup == this.getActiveGroup());
- if (this.$clicker.hasClass('SplitCtrl')) {
- instant = true;
- }
- this.showMenu(e, instant);
- },
- /**
- * Opens and shows a popup menu.
- *
- * If the menu requires dynamic content to be loaded, this will load the content.
- * To define dynamic content, the .Menu element should have:
- * * data-contentSrc = URL to JSON that contains templateHtml to be inserted
- * * data-contentDest = jQuery selector specifying the element to which the templateHtml will be appended. Defaults to this.$menu.
- *
- * @param event
- * @param boolean Show instantly (true) or fade in (false)
- */
- showMenu: function (e, instant) {
- /*if (this.$menu.is(':visible')) {
- return false;
- }*/
- if (this.$menu.hasClass('MenuOpened') || this.$menu.closest(".xenOverlay").hasClass('faster')) {
- return false;
- }
- //console.log('Show menu event type = %s', e.type);
- var $eShow = new $.Event('PopupMenuShow');
- $eShow.$menu = this.$menu;
- $eShow.instant = instant;
- $(document).trigger($eShow);
- if ($eShow.isDefaultPrevented()) {
- return false;
- }
- this.menuVisible = true;
- this.setMenuPosition('showMenu');
- if (this.$menu.hasClass('BottomControl')) {
- instant = true;
- }
- if (this.contentSrc && !this.loading) {
- this.loading = XenForo.ajax(
- this.contentSrc, '',
- $.context(this, 'loadSuccess'),
- { type: 'GET' }
- );
- this.timeout = setTimeout(function () {
- this.$menu.find('.Progress').addClass('InProgress');
- }.bind(this), 500);
- instant = true;
- }
- this.setActiveGroup();
- this.$control.addClass('PopupOpen').removeClass('PopupClosed');
- this.$menu.addClass('MenuOpened');
- this.menuShown();
- if (!this.menuEventsInitialized) {
- var $html = $('html'),
- t = this,
- htmlSize = [$html.width(), $html.height()];
- // TODO: make this global?
- // TODO: touch interfaces don't like this
- $(document).bind({
- PopupMenuShow: $.context(this, 'hideIfOther'),
- XFOverlay: $.context(this, 'menuHidden')
- });
- if (XenForo.isTouchBrowser()) {
- for (const e of ['click', 'touchend'])
- document.addEventListener(e, e => {
- if (!this.menuVisible || !this.$menu.hasClass('MenuOpened')) return;
- if (!e.screenX && !e.screenY && e.type === 'click') return; // fix for $().click()
- if (!e.target.closest('nav') && !e.target.closest('.MenuOpened')) {
- e.preventDefault()
- e.stopImmediatePropagation()
- $(document).trigger('HideAllMenus')
- this.hideMenu($(e))
- }
- }, true)
- }
- // Webkit mobile kinda does not support document.click, bind to other elements
- if (XenForo._isWebkitMobile) {
- $(document.body.children).click($.context(this, 'hideMenu'));
- } else {
- $(document).click($.context(this, 'hideMenu'));
- }
- $(document).on('HideAllMenus', function (e) {
- if (t.menuVisible) {
- t._hideMenu(e, true);
- }
- });
- let hovered = true;
- t.$menu.hover(() => { hovered = true }, () => { hovered = false })
- if (!XenForo.isTouchBrowser()) {
- $(window).on('scroll', function (e) {
- if (t.menuVisible && !t.$menu.hasClass('HeaderMenu') && !hovered) {
- t._hideMenu(e, true);
- }
- });
- }
- $(window).bind(
- {
- resize: function (e) {
- // only trigger close if the window size actually changed - some mobile browsers trigger without size change
- var w = $html.width(), h = $html.height();
- if (w != htmlSize[0] || h != htmlSize[1]) {
- htmlSize[0] = w;
- htmlSize[1] = h;
- t._hideMenu(e);
- }
- }
- });
- this.$menu.delegate('a', 'click', $.context(this, 'menuLinkClick'));
- this.$menu.delegate('.MenuCloser', 'click', $.context(this, 'hideMenu'));
- this.$control.parents('#AlertsDestinationWrapper').on('scroll touchmove', $.context(this, 'hideMenu'));
- this.menuEventsInitialized = true;
- }
- },
- /**
- * Hides an open popup menu (conditionally)
- *
- * @param event
- * @param boolean Hide instantly (true) or fade out (false)
- */
- hideMenu: function (e, instant) {
- /*if (this.$menu.is(':visible') && this.triggersMenuHide(e)) {
- this._hideMenu(e, !instant);
- }*/
- this.$control.parents('#AlertsDestinationWrapper').off('scroll touchmove hover', $.context(this, 'hideMenu'));
- if (this.$menu.hasClass('MenuOpened') && this.triggersMenuHide(e)) {
- this._hideMenu(e, !instant);
- }
- },
- /**
- * Hides an open popup menu, without checking context or environment
- *
- * @param event
- * @param boolean Fade out the menu (true) or hide instantly out (false)
- */
- _hideMenu: function (e, fade) {
- //console.log('Hide menu \'%s\' %o TYPE = %s', this.$control.text(), this.$control, e.type);
- this.menuVisible = false;
- this.setActiveGroup(null);
- if (this.$menu.hasClass('BottomControl')) {
- fade = false;
- }
- // stop any unread content fading into its read state
- clearTimeout(this.unreadDisplayTimeout);
- this.$menu.find('.Unread').stop();
- /*var top = this.$menu.css('top');
- this.$menu.css('top', '+=10');
- this.$menu.xfHide(0, $.context(this, 'menuHidden')).css('top');*/
- this.menuHidden();
- },
- /**
- * Fires when the menu showing animation is completed and the menu is displayed
- */
- menuShown: function () {
- // if the menu has a data-contentSrc attribute, we can assume that it requires dynamic content, which has not yet loaded
- var contentLoaded = (this.$menu.data('contentsrc') ? false : true),
- $input = null;
- this.$control.addClass('PopupOpen').removeClass('PopupClosed');
- this.$menu.addClass('MenuOpened');
- this.newlyOpened = true;
- setTimeout($.context(function () {
- this.newlyOpened = false;
- }, this), 50);
- this.$menu.trigger('ShowComplete', [contentLoaded]);
- this.setMenuPosition('menuShown');
- this.highlightUnreadContent();
- if (!XenForo.isTouchBrowser()) {
- $input = this.$menu.find('input[type=text], input[type=search], textarea, select').first();
- if ($input.length) {
- if ($input.data('nofocus')) {
- return;
- }
- $input.select();
- }
- }
- const mm = $('#menu.mm--open')
- if (mm && mm.length) {
- $('#navigation .mobileMenuButtonClose').click()
- }
- },
- /**
- * Fires when the menu hiding animations is completed and the menu is hidden
- */
- menuHidden: function () {
- this.$control.removeClass('PopupOpen').addClass('PopupClosed');
- this.$menu.removeClass('MenuOpened');
- this.hideTimeout = setTimeout(() => {
- this.hideTimeout = null
- // https://zelenka.guru/threads/4163365/
- this.$menu.css('top', '0px').css('left', '0px')
- }, 150)
- this.$menu.trigger('MenuHidden');
- },
- /**
- * Fires in response to the document triggering 'PopupMenuShow' and hides the current menu
- * if the menu that fired the event is not itself.
- *
- * @param event
- */
- hideIfOther: function (e) {
- const eventMenuProp = e.$menu.prop($.expando);
- const contextMenuProp = this.$menu.prop($.expando);
- if (!eventMenuProp && !contextMenuProp || eventMenuProp != contextMenuProp) {
- if (!e.$menu.data('XenForo.PopupMenu').$container.hasClass('PopupInPopup')) {
- this.hideMenu(e, e.instant);
- }
- }
- },
- /**
- * Checks to see if an event should hide the menu.
- *
- * Returns false if:
- * * Event target is a child of the menu, or is the menu itself
- *
- * @param event
- *
- * @return boolean
- */
- triggersMenuHide: function (e) {
- var $target = $(e.target);
- if (e.ctrlKey || e.shiftKey || e.altKey) {
- return false;
- }
- if (e.which > 1) {
- // right or middle click, don't close
- return false;
- }
- if ($target.is('.MenuCloser')) {
- return true;
- }
- // is the control a hyperlink that has not had its default action prevented?
- if ($target.is('a[href]') && !e.isDefaultPrevented()) {
- return true;
- }
- if ($target.is('ul')) {
- return false;
- }
- if (e.target === document || !$target.closest('#' + this.$menu.id).length) {
- return true;
- }
- return false;
- },
- /**
- * Sets the position of the popup menu, based on the position of the control
- */
- setMenuPosition: function (caller) {
- if (this.hideTimeout) clearTimeout(this.hideTimeout)
- //console.info('setMenuPosition(%s)', caller);
- var $controlParent,
- controlLayout, // control coordinates
- menuLayout, // menu coordinates
- contentLayout, // #content coordinates
- $content,
- $window,
- proposedLeft,
- proposedTop;
- controlLayout = this.$control.coords('outer');
- this.$menu.css('position', '').removeData('position');
- $controlParent = this.$control;
- while ($controlParent && $controlParent.length && $controlParent.get(0) != document) {
- if ($controlParent.css('position') == 'fixed') {
- controlLayout.top -= $(window).scrollTop();
- controlLayout.left -= $(window).scrollLeft();
- this.$menu.css('position', 'fixed').data('position', 'fixed');
- break;
- }
- $controlParent = $controlParent.parent();
- }
- this.$control.removeClass('BottomControl');
- // set the menu to sit flush with the left of the control, immediately below it
- this.$menu.removeClass('BottomControl').css(
- {
- left: controlLayout.left,
- top: controlLayout.top + controlLayout.height - 1 // fixes a weird thing where the menu doesn't join the control
- });
- menuLayout = this.$menu.coords('outer');
- $content = $('#content .pageContent');
- if ($content.length) {
- contentLayout = $content.coords('outer');
- } else {
- contentLayout = $('body').coords('outer');
- }
- $window = $(window);
- var sT = $window.scrollTop(),
- sL = $window.scrollLeft(),
- windowWidth = $window.width();
- /*
- * if the menu's right edge is off the screen, check to see if
- * it would be better to position it flush with the right edge of the control.
- * RTL displays will try to do this if possible.
- */
- if (XenForo.isRTL() || menuLayout.left + menuLayout.width > contentLayout.left + contentLayout.width) {
- proposedLeft = Math.max(0, controlLayout.left + controlLayout.width - menuLayout.width);
- if (proposedLeft > sL) {
- this.$menu.css('left', proposedLeft);
- }
- }
- if (parseInt(this.$menu.css('left'), 10) + menuLayout.width > windowWidth + sL) {
- this.$menu.css('left', Math.min(parseInt(this.$menu.css('left'), 10), windowWidth + sL - menuLayout.width));
- }
- /*
- * if the menu's bottom edge is off the screen, check to see if
- * it would be better to position it above the control
- */
- if (menuLayout.top + menuLayout.height > $window.height() + sT) {
- proposedTop = controlLayout.top - menuLayout.height;
- if (proposedTop > sT) {
- this.$control.addClass('BottomControl');
- this.$menu.addClass('BottomControl');
- this.$menu.css('top', controlLayout.top - this.$menu.outerHeight());
- }
- }
- },
- /**
- * Fires when dynamic content for a popup menu has been loaded.
- *
- * Checks for errors and if there are none, appends the new HTML to the element selected by this.contentDest.
- *
- * @param object ajaxData
- * @param string textStatus
- */
- loadSuccess: function (ajaxData, textStatus) {
- if (XenForo.hasResponseError(ajaxData) || !XenForo.hasTemplateHtml(ajaxData)) {
- this._hideMenu()
- this.loading = null
- return false;
- }
- // check for content destination
- if (!this.contentDest) {
- console.warn('Menu content destination not specified, using this.$menu.');
- this.contentDest = this.$menu;
- }
- console.info('Content destination: %o', this.contentDest);
- var self = this;
- new XenForo.ExtLoader(ajaxData, function (data) {
- self.$menu.trigger('LoadComplete');
- if (self.timeout) clearTimeout(self.timeout);
- var $templateHtml = $(data.templateHtml);
- // append the loaded content to the destination
- $templateHtml.xfInsert(
- self.$menu.data('insertfn') || 'appendTo',
- self.contentDest,
- 'slideDown', 0,
- function () {
- self.$menu.css('min-width', '199px');
- setTimeout(function () {
- self.$menu.css('min-width', '');
- }, 0);
- if (self.$control.hasClass('PopupOpen')) {
- self.menuShown();
- }
- self.$menu.addClass('Loaded');
- }
- );
- if ($(self.contentDest).hasClass('Scrollbar')) {
- $(self.contentDest).scrollbar();
- }
- self.$menu.find('.Progress').removeClass('InProgress');
- if (self.useInfiniteScroll) {
- var $scroll = self.$menu.find('#AlertPanels #AlertsDestinationWrapper')
- var $status = $('\
- <div class="infinite-scroll-status">\
- <div class="spinner infinite-scroll-request" style="display: none;">\
- <div class="bounce1"></div>\
- <div class="bounce2"></div>\
- <div class="bounce3"></div>\
- </div>\
- </div>').appendTo($scroll)
- $scroll.find('.alertsPopup > ol').infiniteScroll({
- path: function () {
- return XenForo.canonicalizeUrl(self.contentSrc + (/\?/.test(self.contentSrc) ? '&' : '?') + 'page=' + (this.pageIndex + 1))
- },
- status: $status[0],
- append: '.alertsPopup > ol > li',
- xf: true,
- xfAjaxOptions: { global: false },
- xfAjaxDataProcess: function (ajaxData) {
- return ajaxData.templateHtml
- },
- history: false,
- elementScroll: $scroll[0]
- })
- }
- });
- },
- resetLoader: function () {
- if (this.contentDest && this.loading) {
- delete (this.loading);
- $(this.contentDest).empty();
- this.$menu.find('.Progress').addClass('InProgress');
- }
- },
- menuLinkClick: function (e) {
- this.hideMenu(e, true);
- },
- /**
- * Sets the name of the globally active popup group
- *
- * @param mixed If specified, active group will be set to this value.
- *
- * @return string Active group name
- */
- setActiveGroup: function (value) {
- var activeGroup = (value === undefined ? this.popupGroup : value);
- return XenForo._PopupMenuActiveGroup = activeGroup;
- },
- /**
- * Returns the name of the globally active popup group
- *
- * @return string Active group name
- */
- getActiveGroup: function () {
- return XenForo._PopupMenuActiveGroup;
- },
- /**
- * Fade return the background color of unread items to the normal background
- */
- highlightUnreadContent: function () {
- var $unreadContent = this.$menu.find('.Unread'),
- defaultBackground = null,
- counterSelector = null;
- if ($unreadContent.length) {
- defaultBackground = $unreadContent.data('defaultbackground');
- if (defaultBackground) {
- $unreadContent.css('backgroundColor', null);
- this.unreadDisplayTimeout = setTimeout($.context(function () {
- // removes an item specified by data-removeCounter on the menu element
- if (counterSelector = this.$menu.data('removecounter')) {
- XenForo.balloonCounterUpdate($(counterSelector), 0);
- }
- $unreadContent.animate({ backgroundColor: defaultBackground }, 2000, $.context(function () {
- $unreadContent.removeClass('Unread');
- this.$menu.trigger('UnreadDisplayComplete');
- }, this));
- }, this), 1000);
- }
- }
- },
- reload: function () {
- if (!this.contentSrc || (this.loading && this.loading.readyState !== 4)) return;
- this.resetLoader()
- this.loading = XenForo.ajax(
- this.contentSrc, '',
- $.context(this, 'loadSuccess'),
- { type: 'GET' }
- );
- },
- addToMenu: function ($element) {
- this.$menu.find('.secondaryContent').append($element).xfActivate();
- },
- removeFromMenu: function ($selector) {
- this.$menu.find($selector).remove();
- }
- };
- // *********************************************************************
- /**
- * Shows and hides global request pending progress indicators for AJAX calls.
- *
- * Binds to the global ajaxStart and ajaxStop jQuery events.
- * Also binds to the PseudoAjaxStart and PseudoAjaxStop events,
- * see XenForo.AutoInlineUploader
- *
- * Initialized by XenForo.init()
- */
- XenForo.AjaxProgress = function () {
- var overlay = null,
- showOverlay = function () {
- // mini indicators
- //$('.Progress, .xenForm .ctrlUnit.submitUnit dt').addClass('InProgress');
- // the overlay
- if (!overlay) {
- overlay = $('<div id="AjaxProgress" class="xenOverlay"><div class="content"><span class="close" /></div></div>')
- .appendTo('body')
- .css({
- top: 0,
- right: 0,
- opacity: 0,
- position: 'fixed',
- display: 'block',
- width: 'inherit'
- }).animate({ opacity: 1 }, XenForo.speed.fast)
- }
- },
- hideOverlay = function () {
- // mini indicators
- $('.Progress, .xenForm .ctrlUnit.submitUnit dt')
- .removeClass('InProgress');
- // the overlay
- if (overlay) {
- overlay.animate({ opacity: 0 }, XenForo.speed.fast, function () {
- $(this).remove()
- })
- overlay = null
- }
- };
- $(document).bind(
- {
- ajaxStart: function (e) {
- XenForo._AjaxProgress = true;
- showOverlay();
- },
- ajaxStop: function (e) {
- XenForo._AjaxProgress = false;
- hideOverlay();
- },
- PseudoAjaxStart: function (e) {
- showOverlay();
- },
- PseudoAjaxStop: function (e) {
- hideOverlay();
- }
- });
- if ($.browser.msie && $.browser.version < 7) {
- $(document).bind('scroll', function (e) {
- if (overlay && overlay.isOpened() && !overlay.getConf().fixed) {
- overlay.getOverlay().css('top', overlay.getConf().top + $(window).scrollTop());
- }
- });
- }
- };
- // *********************************************************************
- /**
- * Handles the scrollable pagenav gadget, allowing selection of any page between 1 and (end)
- * while showing only {range*2+1} pages plus first and last at once.
- *
- * @param jQuery .pageNav
- */
- XenForo.PageNav = function ($pageNav) {
- this.__construct($pageNav);
- };
- XenForo.PageNav.prototype =
- {
- __construct: function ($pageNav) {
- if (XenForo.isRTL()) {
- // scrollable doesn't support RTL yet
- return false;
- }
- this.$pageNav = $pageNav;
- var $scroller = $pageNav.find('.scrollable');
- if (!$scroller.length) {
- return false;
- }
- console.info('PageNav %o', $pageNav);
- this.start = parseInt($pageNav.data('start'));
- this.page = parseInt($pageNav.data('page'));
- this.end = parseInt($pageNav.data('end'));
- this.last = parseInt($pageNav.data('last'));
- this.range = parseInt($pageNav.data('range'));
- this.size = (this.range * 2 + 1);
- this.baseurl = $pageNav.data('baseurl');
- this.sentinel = $pageNav.data('sentinel');
- this.notShowPrevButton = false;
- this.notShowNextButton = false;
- $scroller.scrollable(
- {
- speed: XenForo.speed.slow,
- easing: 'easeOutBounce',
- keyboard: false,
- prev: '#nullPrev',
- next: '#nullNext',
- touch: false
- });
- this.api = $scroller.data('scrollable').onBeforeSeek($.context(this, 'beforeSeek'));
- this.$prevButton = $pageNav.find('.PageNavPrev').click($.context(this, 'prevPage'));
- this.$nextButton = $pageNav.find('.PageNavNext').click($.context(this, 'nextPage'));
- this.setControlVisibility(this.api.getIndex(), 0);
- },
- /**
- * Scrolls to the previous 'page' of page links, creating them if necessary
- *
- * @param Event e
- */
- prevPage: function (e) {
- if (this.api.getIndex() == 0 && this.start > 2) {
- var i = 0,
- minPage = Math.max(2, (this.start - this.size));
- for (i = this.start - 1; i >= minPage; i--) {
- this.prepend(i);
- }
- this.start = minPage;
- }
- this.api.seekTo(Math.max(this.api.getIndex() - this.size, 0));
- },
- /**
- * Scrolls to the next 'page' of page links, creating them if necessary
- *
- * @param Event e
- */
- nextPage: function (e) {
- if ((this.api.getIndex() + 1 + 2 * this.size) > this.api.getSize() && this.end < this.last - 1) {
- var i = 0,
- maxPage = Math.min(this.last - 1, this.end + this.size);
- for (i = this.end + 1; i <= maxPage; i++) {
- this.append(i);
- }
- this.end = maxPage;
- }
- this.api.seekTo(Math.min(this.api.getSize() - this.size, this.api.getIndex() + this.size));
- },
- /**
- * Adds an additional page link to the beginning of the scrollable section, out of sight
- *
- * @param integer page
- */
- prepend: function (page) {
- this.buildPageLink(page).prependTo(this.api.getItemWrap());
- this.api.next(0);
- },
- /**
- * Adds an additional page link to the end of the scrollable section, out of sight
- *
- * @param integer page
- */
- append: function (page) {
- this.buildPageLink(page).appendTo(this.api.getItemWrap());
- },
- /**
- * Buids a single page link
- *
- * @param integer page
- *
- * @return jQuery page link html
- */
- buildPageLink: function (page) {
- return $('<a />',
- {
- href: this.buildPageUrl(page),
- text: page,
- 'class': (page > 999 ? 'gt999' : '')
- });
- },
- /**
- * Converts the baseUrl into a page url by replacing the sentinel value
- *
- * @param integer page
- *
- * @return string page URL
- */
- buildPageUrl: function (page) {
- return this.baseurl
- .replace(this.sentinel, page)
- .replace(escape(this.sentinel), page);
- },
- /**
- * Runs immediately before the pagenav seeks to a new index,
- * Toggles visibility of the next/prev controls based on whether they are needed or not
- *
- * @param jQuery Event e
- * @param integer index
- */
- beforeSeek: function (e, index) {
- this.setControlVisibility(index, XenForo.speed.fast);
- this.$pageNav.trigger('seek');
- },
- /**
- * Sets the visibility of the scroll controls, based on whether using them would do anything
- * (hide the prev-page control if on the first page, etc.)
- *
- * @param integer Target index of the current scroll
- *
- * @param mixed Speed of animation
- */
- setControlVisibility: function (index, speed) {
- const notShowPrevButtonUpdated = index === 0 && this.start <= 2;
- const notShowNextButtonUpdated = this.api.getSize() - this.size <= index && this.end >= this.last - 1;
- if (notShowPrevButtonUpdated !== this.notShowPrevButton) {
- this.notShowPrevButton = notShowPrevButtonUpdated;
- if (this.notShowPrevButton) {
- this.$prevButton.hide(speed);
- } else {
- this.$prevButton.show(speed);
- }
- }
- if (notShowNextButtonUpdated !== this.notShowNextButton) {
- this.notShowNextButton = notShowNextButtonUpdated;
- if (this.notShowNextButton) {
- this.$nextButton.hide(speed);
- } else {
- this.$nextButton.show(speed);
- }
- }
- }
- };
- // *********************************************************************
- XenForo.ToggleTrigger = function ($trigger) {
- this.__construct($trigger);
- };
- XenForo.ToggleTrigger.prototype =
- {
- __construct: function ($trigger) {
- this.$trigger = $trigger;
- this.loaded = false;
- this.targetVisible = false;
- this.$target = null;
- if ($trigger.data('target')) {
- var anchor = $trigger.closest('.ToggleTriggerAnchor');
- if (!anchor.length) {
- anchor = $('body');
- }
- var target = anchor.find($trigger.data('target'));
- if (target.length) {
- this.$target = target;
- var toggleClass = target.data('toggle-class');
- this.targetVisible = toggleClass ? target.hasClass(toggleClass) : target.is(':visible');
- }
- }
- if ($trigger.data('only-if-hidden')
- && XenForo.isPositive($trigger.data('only-if-hidden'))
- && this.targetVisible
- ) {
- return;
- }
- $trigger.click($.context(this, 'toggle'));
- },
- toggle: function (e) {
- e.preventDefault();
- var $trigger = this.$trigger,
- $target = this.$target;
- if ($trigger.data('toggle-if-pointer') && XenForo.isPositive($trigger.data('toggle-if-pointer'))) {
- if ($trigger.css('cursor') !== 'pointer') {
- return;
- }
- }
- $trigger.clearQueue().finish();
- if ($trigger.data('toggle-text')) {
- var toggleText = $trigger.text();
- $trigger.text($trigger.data('toggle-text'));
- $trigger.data('toggle-text', toggleText);
- }
- if (e.pageX || e.pageY) {
- $trigger.blur();
- }
- if ($target) {
- $(document).trigger('ToggleTriggerEvent',
- {
- closing: this.targetVisible,
- $target: $target
- });
- this.hideSelfIfNeeded();
- var triggerTargetEvent = function () {
- $target.trigger('elementResized');
- };
- var hideTrigger = function () {
- $target.attr('style', 'display: block');
- triggerTargetEvent();
- }
- var toggleClass = $target.data('toggle-class');
- if (this.targetVisible) {
- if (toggleClass) {
- $target.removeClass(toggleClass);
- triggerTargetEvent();
- } else {
- $target.stop(true, true).xfFadeUp(null, triggerTargetEvent);
- }
- } else {
- if (toggleClass) {
- $target.addClass(toggleClass);
- hideTrigger();
- } else {
- $target.stop(true, true).xfFadeDown(null, hideTrigger);
- }
- }
- this.targetVisible = !this.targetVisible;
- } else {
- this.load();
- }
- },
- hideSelfIfNeeded: function () {
- var hideSel = this.$trigger.data('hide');
- if (!hideSel) {
- return false;
- }
- var $el;
- if (hideSel == 'self') {
- $el = this.$trigger;
- } else {
- var anchor = this.$trigger.closest('.ToggleTriggerAnchor');
- if (!anchor.length) {
- anchor = $('body');
- }
- $el = anchor.find(hideSel);
- }
- $el.hide();
- return;
- //$el.xfFadeUp();
- },
- load: function () {
- if (this.loading || !this.$trigger.attr('href')) {
- return;
- }
- var self = this;
- var $position = $(this.$trigger.data('position'));
- if (!$position.length) {
- $position = this.$trigger.closest('.ToggleTriggerAnchor');
- if (!$position.length) {
- console.warn("Could not match toggle target position selector %s", this.$trigger.data('position'));
- return false;
- }
- }
- var method = this.$trigger.data('position-method') || 'insertAfter';
- this.loading = true;
- XenForo.ajax(this.$trigger.attr('href'), {}, function (ajaxData) {
- self.loading = false;
- if (XenForo.hasResponseError(ajaxData)) {
- return false;
- }
- // received a redirect rather than a view - follow it.
- if (ajaxData._redirectStatus && ajaxData._redirectTarget) {
- var fn = function () {
- XenForo.redirect(ajaxData._redirectTarget);
- };
- if (XenForo._manualDeferOverlay) {
- $(document).one('ManualDeferComplete', fn);
- } else {
- fn();
- }
- return false;
- }
- if (!ajaxData.templateHtml) {
- return false;
- }
- new XenForo.ExtLoader(ajaxData, function (data) {
- self.$target = $(data.templateHtml);
- self.$target.xfInsert(method, $position);
- self.targetVisible = true;
- self.hideSelfIfNeeded();
- });
- });
- }
- };
- // *********************************************************************
- /**
- * Triggers an overlay from a regular link or button
- * Triggers can provide an optional data-cacheOverlay attribute
- * to allow multiple trigers to access the same overlay.
- *
- * @param jQuery .OverlayTrigger
- */
- XenForo.OverlayTrigger = function ($trigger, options) {
- this.__construct($trigger, options);
- };
- XenForo.OverlayTrigger.prototype =
- {
- __construct: function ($trigger, options) {
- this.$trigger = $trigger.click($.context(this, 'show'));
- this.options = options;
- },
- /**
- * Begins the process of loading and showing an overlay
- *
- * @param event e
- */
- show: function (e) {
- //xfActivate
- var parentOverlay = this.$trigger.closest('.xenOverlay').data('overlay'),
- cache,
- options,
- isUserLink = (this.$trigger.is('.username, .avatar')),
- cardHref;
- if (!parseInt(XenForo._enableOverlays)) {
- // if no overlays, use <a href /> by preference
- if (this.$trigger.attr('href')) {
- return true;
- } else if (this.$trigger.data('href')) {
- if (this.$trigger.closest('.AttachmentUploader, #AttachmentUploader').length == 0) {
- // open the overlay target as a regular link, unless it's the attachment uploader
- XenForo.redirect(this.$trigger.data('href'));
- return false;
- }
- } else {
- // can't do anything - should not happen
- console.warn('No alternative action found for OverlayTrigger %o', this.$trigger);
- return true;
- }
- }
- // abort if this is a username / avatar overlay with NoOverlay specified
- if (isUserLink && this.$trigger.hasClass('NoOverlay')) {
- return true;
- }
- // abort if the event has a modifier key
- if (e.ctrlKey || e.shiftKey || e.altKey) {
- return true;
- }
- // abort if the event is a middle or right-button click
- if (e.which > 1) {
- return true;
- }
- if (this.options && this.options.onBeforeTrigger) {
- var newE = $.Event();
- newE.clickEvent = e;
- this.options.onBeforeTrigger(newE);
- if (newE.isDefaultPrevented()) {
- return;
- }
- }
- var beforeE = $.Event('BeforeOverlayTrigger');
- this.$trigger.trigger(beforeE);
- if (beforeE.isDefaultPrevented()) {
- return;
- }
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- if (parentOverlay && parentOverlay.isOpened()) {
- var self = this;
- parentOverlay.getTrigger().one('onClose', function (innerE) {
- setTimeout(function () {
- self.show(innerE);
- }, 0);
- });
- parentOverlay.getConf().mask.closeSpeed = 0;
- parentOverlay.close();
- return;
- }
- $('#exposeMask').remove();
- if (!this.OverlayLoader) {
- options = (typeof this.options == 'object' ? this.options : {});
- options = $.extend(options, this.$trigger.data('overlayoptions'));
- cache = this.$trigger.data('cacheoverlay');
- if (cache !== undefined) {
- if (XenForo.isPositive(cache)) {
- cache = true;
- } else {
- cache = false;
- options.onClose = $.context(this, 'deCache');
- }
- } else if (this.$trigger.is('input:submit')) {
- cache = false;
- options.onClose = $.context(this, 'deCache');
- }
- if (isUserLink && !this.$trigger.hasClass('OverlayTrigger')) {
- if (!this.$trigger.data('cardurl') && this.$trigger.attr('href')) {
- cardHref = this.$trigger.attr('href').replace(/#.*$/, '');
- if (cardHref.indexOf('?') >= 0) {
- cardHref += '&card=1';
- } else {
- cardHref += '?card=1';
- }
- this.$trigger.data('cardurl', cardHref);
- }
- cache = true;
- options.speed = XenForo.speed.fast;
- }
- this.OverlayLoader = new XenForo.OverlayLoader(this.$trigger, cache, options);
- this.OverlayLoader.load();
- e.preventDefault();
- return true;
- }
- this.OverlayLoader.show();
- },
- deCache: function () {
- if (this.OverlayLoader && this.OverlayLoader.overlay) {
- console.info('DeCache %o', this.OverlayLoader.overlay.getOverlay());
- this.OverlayLoader.overlay.getTrigger().removeData('overlay');
- this.OverlayLoader.overlay.getOverlay().empty().remove();
- }
- delete (this.OverlayLoader);
- }
- };
- // *********************************************************************
- XenForo.LightBoxTrigger = function ($link) {
- $link.attr('data-fancybox', 'gallery');
- $link.on('click', function (e) {
- if (!XenForo.Fancybox) {
- XenForo.Fancybox = true;
- e.preventDefault();
- XenForo.scriptLoader.loadMultiple([{ 'type': 'js', 'url': 'js/fancybox/fancybox.umd.js' }, { 'type': 'css', 'list': ['fancybox'] }], function () {
- $link.children().click()
- })
- }
- })
- };
- // *********************************************************************
- XenForo.OverlayLoaderCache = {};
- /**
- * Loads HTML and related external resources for an overlay
- *
- * @param jQuery Overlay trigger object
- * @param boolean If true, cache the overlay HTML for this URL
- * @param object Object of options for the overlay
- */
- XenForo.OverlayLoader = function ($trigger, cache, options) {
- this.__construct($trigger, options, cache);
- };
- XenForo.OverlayLoader.prototype =
- {
- __construct: function ($trigger, options, cache) {
- this.$trigger = $trigger;
- this.cache = cache;
- this.options = options;
- },
- /**
- * Initiates the loading of the overlay, or returns it from cache
- *
- * @param function Callback to run on successful load
- */
- load: function (callback) {
- // special case for submit buttons
- if (this.$trigger.is('input:submit') || this.$trigger.is('button:submit')) {
- this.cache = false;
- if (!this.xhr) {
- var $form = this.$trigger.closest('form'),
- serialized = $form.serializeArray();
- serialized.push(
- {
- name: this.$trigger.attr('name'),
- value: this.$trigger.attr('value')
- });
- this.xhr = XenForo.ajax(
- $form.attr('action'),
- serialized,
- $.context(this, 'loadSuccess')
- );
- }
- return;
- }
- //TODO: ability to point to extant overlay HTML, rather than loading via AJAX
- this.href = this.$trigger.data('cardurl') || this.$trigger.data('href') || this.$trigger.attr('href');
- if (!this.href) {
- console.warn('No overlay href found for control %o', this.$trigger);
- return false;
- }
- console.info('OverlayLoader for %s', this.href);
- this.callback = callback;
- if (this.cache && XenForo.OverlayLoaderCache[this.href]) {
- XenForo.OverlayLoaderCache[this.href].load()
- } else if (!this.xhr) {
- this.xhr = XenForo.ajax(
- this.href, '',
- $.context(this, 'loadSuccess'), { type: 'GET' }
- );
- }
- },
- /**
- * Handles the returned ajaxdata from an overlay xhr load,
- * Stores the template HTML then inits externals (js, css) loading
- *
- * @param object ajaxData
- * @param string textStatus
- */
- loadSuccess: function (ajaxData, textStatus) {
- delete (this.xhr);
- if (XenForo.hasResponseError(ajaxData)) {
- return false;
- }
- // received a redirect rather than a view - follow it.
- if (ajaxData._redirectStatus && ajaxData._redirectTarget) {
- var fn = function () {
- XenForo.redirect(ajaxData._redirectTarget);
- };
- if (XenForo._manualDeferOverlay) {
- $(document).one('ManualDeferComplete', fn);
- } else {
- fn();
- }
- return false;
- }
- this.options.title = ajaxData.h1 || ajaxData.title;
- new XenForo.ExtLoader(ajaxData, $.context(this, 'createOverlay'));
- },
- /**
- * Creates an overlay containing the appropriate template HTML,
- * runs the callback specified in .load() and then shows the overlay.
- *
- * @param jQuery Cached $overlay object
- */
- createOverlay: function ($overlay) {
- var contents = ($overlay && $overlay.templateHtml) ? $overlay.templateHtml : $overlay;
- this.overlay = XenForo.createOverlay(this.$trigger, contents, this.options);
- if (this.cache && this.overlay) {
- XenForo.OverlayLoaderCache[this.href] = this.overlay
- }
- if (typeof this.callback == 'function') {
- this.callback();
- }
- this.show();
- },
- /**
- * Shows a finished overlay
- */
- show: function () {
- if (!this.overlay && this.href === "misc/lightbox") {
- return;
- }
- if (!this.overlay) {
- console.warn('Attempted to call XenForo.OverlayLoader.show() for %s before overlay is created', this.href);
- this.load(this.callback);
- return;
- }
- this.overlay.load();
- $(document).trigger({
- type: 'XFOverlay',
- overlay: this.overlay,
- trigger: this.$trigger
- });
- }
- };
- // *********************************************************************
- XenForo.LoginBar = function ($loginBar) {
- var $form = $('#login').appendTo($loginBar.find('.pageContent')),
- /**
- * Opens the login form
- *
- * @param event
- */
- openForm = function (e) {
- e.preventDefault();
- XenForo.chromeAutoFillFix($form);
- $form.xfSlideIn(XenForo.speed.slow, 'easeOutBack', function () {
- $('#LoginControl').select();
- $loginBar.expose($.extend(XenForo._overlayConfig.mask,
- {
- loadSpeed: XenForo.speed.slow,
- onBeforeLoad: function (e) {
- $form.css('outline', '0px solid black');
- },
- onLoad: function (e) {
- $form.css('outline', '');
- },
- onBeforeClose: function (e) {
- closeForm(false, true);
- return true;
- }
- }));
- });
- },
- /**
- * Closes the login form
- *
- * @param event
- * @param boolean
- */
- closeForm = function (e, isMaskClosing) {
- if (e) e.target.blur();
- $form.xfSlideOut(XenForo.speed.fast);
- if (!isMaskClosing && $.mask) {
- $.mask.close();
- }
- };
- /**
- * Toggles the login form
- */
- $('label[for="LoginControl"]').click(function (e) {
- if ($(this).closest('#login').length == 0) {
- e.preventDefault();
- if ($form._xfSlideWrapper(true)) {
- closeForm(e);
- } else {
- $(XenForo.getPageScrollTagName()).scrollTop(0);
- openForm(e);
- }
- }
- });
- /**
- * Changes the text of the Log in / Sign up submit button depending on state
- */
- $loginBar.delegate('input[name="register"]', 'click', function (e) {
- var $button = $form.find('input.button.primary'),
- register = $form.find('input[name="register"]:checked').val();
- $form.find('input.button.primary').val(register == '1'
- ? $button.data('signupphrase')
- : $button.data('loginphrase'));
- $form.find('label.rememberPassword').css('visibility', (register == '1' ? 'hidden' : 'visible'));
- });
- // close form if any .click elements within it are clicked
- $loginBar.delegate('.close', 'click', closeForm);
- };
- // *********************************************************************
- XenForo.QuickSearch = function ($target) { this.__construct($target); };
- XenForo.QuickSearch.prototype = {
- $target: null,
- $form: null,
- $queryInput: null,
- $clearButton: null,
- $searchResults: null,
- updateTimer: null,
- xhr: null,
- runCount: 0,
- __construct: function ($target) {
- this.$target = $target;
- const $form = this.$form = $target.find('form');
- this.$queryInput = $form.find('.QuickSearchQuery')
- .on('focus', $.context(this, 'focus'))
- .on('input', $.context(this, 'input'));
- this.$clearButton = $form.find('.QuickSearchClear')
- .on('click', $.context(this, 'clearQueryInput'));
- this.$searchResults = $('.SearchResultsList');
- $('#QuickSearchPlaceholder').on('click', $.context(this, 'placeholderClick'));
- },
- placeholderClick: function (e) {
- e.preventDefault();
- setTimeout(function () {
- $('#QuickSearch').addClass('show');
- $('#QuickSearchPlaceholder').addClass('hide');
- $('#QuickSearchQuery').focus();
- if (XenForo.isTouchBrowser()) {
- $('#QuickSearchQuery').blur();
- }
- });
- },
- focus: function (e) {
- const $target = this.$target,
- runCount = ++this.runCount;
- console.log('Show quick search menu (%d)', runCount);
- if (runCount === 1) {
- if ($.browser.msie && $.browser.version < 9) {
- const $form = this.$form;
- $form.find('input').on('keydown', function (e) {
- if (e.keyCode === 13) {
- if (e.which === 13) {
- clearTimeout(this.updateTimer);
- }
- $form.submit();
- return false;
- }
- });
- }
- $(XenForo._isWebkitMobile