simple-machines-forum/Themes/default/scripts/smf_jquery_plugins.js

759 lines
No EOL
22 KiB
JavaScript

/**
* SMFtooltip, Basic JQuery function to provide styled tooltips
*
* - will use the hoverintent plugin if available
* - shows the tooltip in a div with the class defined in tooltipClass
* - moves all selector titles to a hidden div and removes the title attribute to
* prevent any default browser actions
* - attempts to keep the tooltip on screen
*
* Simple Machines Forum (SMF)
*
* @package SMF
* @author Simple Machines https://www.simplemachines.org
* @copyright 2022 Simple Machines and individual contributors
* @license https://www.simplemachines.org/about/smf/license.php BSD
*
* @version 2.1.0
*
*/
(function($) {
$.fn.SMFtooltip = function(oInstanceSettings) {
$.fn.SMFtooltip.oDefaultsSettings = {
followMouse: 1,
hoverIntent: {sensitivity: 10, interval: 300, timeout: 50},
positionTop: 12,
positionLeft: 12,
tooltipID: 'smf_tooltip', // ID used on the outer div
tooltipTextID: 'smf_tooltipText', // as above but on the inner div holding the text
tooltipClass: 'tooltip', // The class applied to the outer div (that displays on hover), use this in your css
tooltipSwapClass: 'smf_swaptip', // a class only used internally, change only if you have a conflict
tooltipContent: 'html' // display captured title text as html or text
};
// account for any user options
var oSettings = $.extend({}, $.fn.SMFtooltip.oDefaultsSettings , oInstanceSettings || {});
// move passed selector titles to a hidden span, then remove the selector title to prevent any default browser actions
$(this).each(function()
{
var sTitle = $('<span class="' + oSettings.tooltipSwapClass + '">' + htmlspecialchars(this.title) + '</span>').hide();
$(this).append(sTitle).attr('title', '');
});
// determine where we are going to place the tooltip, while trying to keep it on screen
var positionTooltip = function(event)
{
var iPosx = 0;
var iPosy = 0;
if (!event)
var event = window.event;
if (event.pageX || event.pageY)
{
iPosx = event.pageX;
iPosy = event.pageY;
}
else if (event.clientX || event.clientY)
{
iPosx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
iPosy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
// Position of the tooltip top left corner and its size
var oPosition = {
x: iPosx + oSettings.positionLeft,
y: iPosy + oSettings.positionTop,
w: $('#' + oSettings.tooltipID).width(),
h: $('#' + oSettings.tooltipID).height()
}
// Display limits and window scroll postion
var oLimits = {
x: $(window).scrollLeft(),
y: $(window).scrollTop(),
w: $(window).width() - 24,
h: $(window).height() - 24
};
// don't go off screen with our tooltop
if ((oPosition.y + oPosition.h > oLimits.y + oLimits.h) && (oPosition.x + oPosition.w > oLimits.x + oLimits.w))
{
oPosition.x = (oPosition.x - oPosition.w) - 45;
oPosition.y = (oPosition.y - oPosition.h) - 45;
}
else if ((oPosition.x + oPosition.w) > (oLimits.x + oLimits.w))
{
oPosition.x = oPosition.x - (((oPosition.x + oPosition.w) - (oLimits.x + oLimits.w)) + 24);
}
else if (oPosition.y + oPosition.h > oLimits.y + oLimits.h)
{
oPosition.y = oPosition.y - (((oPosition.y + oPosition.h) - (oLimits.y + oLimits.h)) + 24);
}
// finally set the position we determined
$('#' + oSettings.tooltipID).css({'left': oPosition.x + 'px', 'top': oPosition.y + 'px'});
}
// used to show a tooltip
var showTooltip = function(){
$('#' + oSettings.tooltipID + ' #' + oSettings.tooltipTextID).show();
}
// used to hide a tooltip
var hideTooltip = function(valueOfThis){
$('#' + oSettings.tooltipID).fadeOut('slow').trigger("unload").remove();
}
// used to keep html encoded
function htmlspecialchars(string)
{
return $('<span>').text(string).html();
}
// for all of the elements that match the selector on the page, lets set up some actions
return this.each(function(index)
{
// if we find hoverIntent use it
if ($.fn.hoverIntent)
{
$(this).hoverIntent({
sensitivity: oSettings.hoverIntent.sensitivity,
interval: oSettings.hoverIntent.interval,
over: smf_tooltip_on,
timeout: oSettings.hoverIntent.timeout,
out: smf_tooltip_off
});
}
else
{
// plain old hover it is
$(this).hover(smf_tooltip_on, smf_tooltip_off);
}
// create the on tip action
function smf_tooltip_on(event)
{
// If we have text in the hidden span element we created on page load
if ($(this).children('.' + oSettings.tooltipSwapClass).text())
{
// create a ID'ed div with our style class that holds the tooltip info, hidden for now
$('body').append('<div id="' + oSettings.tooltipID + '" class="' + oSettings.tooltipClass + '"><div id="' + oSettings.tooltipTextID + '" style="display:none;"></div></div>');
// load information in to our newly created div
var tt = $('#' + oSettings.tooltipID);
var ttContent = $('#' + oSettings.tooltipID + ' #' + oSettings.tooltipTextID);
if (oSettings.tooltipContent == 'html')
ttContent.html($(this).children('.' + oSettings.tooltipSwapClass).html());
else
ttContent.text($(this).children('.' + oSettings.tooltipSwapClass).text());
// show, then position, or it may position off screen
tt.show();
showTooltip();
positionTooltip(event);
}
return false;
};
// create the Bye bye tip
function smf_tooltip_off(event)
{
hideTooltip(this);
return false;
};
// create the tip move with the cursor
if (oSettings.followMouse)
{
$(this).on("mousemove", function(event){
positionTooltip(event);
return false;
});
}
// clear the tip on a click
$(this).on("click", function(event){
hideTooltip(this);
return true;
});
});
};
// A simple plugin for deleting an element from the DOM.
$.fn.fadeOutAndRemove = function(speed){
if (typeof speed === 'undefined') speed = 400;
$(this).fadeOut(speed,function(){
$(this).remove();
});
};
// Range to percent.
$.fn.rangeToPercent = function(number, min, max){
return ((number - min) / (max - min));
};
// Percent to range.
$.fn.percentToRange = function(percent, min, max){
return((max - min) * percent + min);
};
})(jQuery);
/**
* AnimaDrag
* Animated jQuery Drag and Drop Plugin
* Version 0.5.1 beta
* Author Abel Mohler
* Released with the MIT License: https://opensource.org/licenses/mit-license.php
*/
(function($){
$.fn.animaDrag = function(o, callback) {
var defaults = {
speed: 400,
interval: 300,
easing: null,
cursor: 'move',
boundary: document.body,
grip: null,
overlay: true,
after: function(e) {},
during: function(e) {},
before: function(e) {},
afterEachAnimation: function(e) {}
}
if(typeof callback == 'function') {
defaults.after = callback;
}
o = $.extend(defaults, o || {});
return this.each(function() {
var id, startX, startY, draggableStartX, draggableStartY, dragging = false, Ev, draggable = this,
grip = ($(this).find(o.grip).length > 0) ? $(this).find(o.grip) : $(this);
if(o.boundary) {
var limitTop = $(o.boundary).offset().top, limitLeft = $(o.boundary).offset().left,
limitBottom = limitTop + $(o.boundary).innerHeight(), limitRight = limitLeft + $(o.boundary).innerWidth();
}
grip.mousedown(function(e) {
o.before.call(draggable, e);
var lastX, lastY;
dragging = true;
Ev = e;
startX = lastX = e.pageX;
startY = lastY = e.pageY;
draggableStartX = $(draggable).offset().left;
draggableStartY = $(draggable).offset().top;
$(draggable).css({
position: 'absolute',
left: draggableStartX + 'px',
top: draggableStartY + 'px',
cursor: o.cursor,
zIndex: '1010'
}).addClass('anima-drag').appendTo(document.body);
if(o.overlay && $('#anima-drag-overlay').length == 0) {
$('<div id="anima-drag-overlay"></div>').css({
position: 'absolute',
top: '0',
left: '0',
zIndex: '1000',
width: $(document.body).outerWidth() + 'px',
height: $(document.body).outerHeight() + 'px'
}).appendTo(document.body);
}
else if(o.overlay) {
$('#anima-drag-overlay').show();
}
id = setInterval(function() {
if(lastX != Ev.pageX || lastY != Ev.pageY) {
var positionX = draggableStartX - (startX - Ev.pageX), positionY = draggableStartY - (startY - Ev.pageY);
if(positionX < limitLeft && o.boundary) {
positionX = limitLeft;
}
else if(positionX + $(draggable).innerWidth() > limitRight && o.boundary) {
positionX = limitRight - $(draggable).outerWidth();
}
if(positionY < limitTop && o.boundary) {
positionY = limitTop;
}
else if(positionY + $(draggable).innerHeight() > limitBottom && o.boundary) {
positionY = limitBottom - $(draggable).outerHeight();
}
$(draggable).stop().animate({
left: positionX + 'px',
top: positionY + 'px'
}, o.speed, o.easing, function(){o.afterEachAnimation.call(draggable, Ev)});
}
lastX = Ev.pageX;
lastY = Ev.pageY;
}, o.interval);
($.browser.safari || e.preventDefault());
});
$(document).mousemove(function(e) {
if(dragging) {
Ev = e;
o.during.call(draggable, e);
}
});
$(document).mouseup(function(e) {
if(dragging) {
$(draggable).css({
cursor: '',
zIndex: '990'
}).removeClass('anima-drag');
$('#anima-drag-overlay').hide().appendTo(document.body);
clearInterval(id);
o.after.call(draggable, e);
dragging = false;
}
});
});
}
})(jQuery);
/*
* jQuery Superfish Menu Plugin - v1.7.7
* Copyright (c) 2015
*
* Dual licensed under the MIT and GPL licenses:
* https://opensource.org/licenses/mit-license.php
* https://www.gnu.org/licenses/gpl.html
*/
;(function ($, w) {
"use strict";
var methods = (function () {
// private properties and methods go here
var c = {
bcClass: 'sf-breadcrumb',
menuClass: 'sf-js-enabled',
anchorClass: 'sf-with-ul',
menuArrowClass: 'sf-arrows'
},
ios = (function () {
var ios = /^(?![\w\W]*Windows Phone)[\w\W]*(iPhone|iPad|iPod)/i.test(navigator.userAgent);
if (ios) {
// tap anywhere on iOS to unfocus a submenu
$('html').css('cursor', 'pointer').on('click', $.noop);
}
return ios;
})(),
wp7 = (function () {
var style = document.documentElement.style;
return ('behavior' in style && 'fill' in style && /iemobile/i.test(navigator.userAgent));
})(),
unprefixedPointerEvents = (function () {
return (!!w.PointerEvent);
})(),
toggleMenuClasses = function ($menu, o) {
var classes = c.menuClass;
if (o.cssArrows) {
classes += ' ' + c.menuArrowClass;
}
$menu.toggleClass(classes);
},
setPathToCurrent = function ($menu, o) {
return $menu.find('li.' + o.pathClass).slice(0, o.pathLevels)
.addClass(o.hoverClass + ' ' + c.bcClass)
.filter(function () {
return ($(this).children(o.popUpSelector).hide().show().length);
}).removeClass(o.pathClass);
},
toggleAnchorClass = function ($li) {
$li.children('a').toggleClass(c.anchorClass);
},
toggleTouchAction = function ($menu) {
var msTouchAction = $menu.css('ms-touch-action');
var touchAction = $menu.css('touch-action');
touchAction = touchAction || msTouchAction;
touchAction = (touchAction === 'pan-y') ? 'auto' : 'pan-y';
$menu.css({
'ms-touch-action': touchAction,
'touch-action': touchAction
});
},
applyHandlers = function ($menu, o) {
var targets = 'li:has(' + o.popUpSelector + ')';
if ($.fn.hoverIntent && !o.disableHI) {
$menu.hoverIntent(over, out, targets);
}
else {
$menu
.on('mouseenter.superfish', targets, over)
.on('mouseleave.superfish', targets, out);
}
var touchevent = 'MSPointerDown.superfish';
if (unprefixedPointerEvents) {
touchevent = 'pointerdown.superfish';
}
if (!ios) {
touchevent += ' touchend.superfish';
}
if (wp7) {
touchevent += ' mousedown.superfish';
}
$menu
.on('focusin.superfish', 'li', over)
.on('focusout.superfish', 'li', out)
.on(touchevent, 'a', o, touchHandler);
},
touchHandler = function (e) {
var $this = $(this),
o = getOptions($this),
$ul = $this.siblings(e.data.popUpSelector);
if (o.onHandleTouch.call($ul) === false) {
return this;
}
if ($ul.length > 0 && $ul.is(':hidden')) {
$this.one('click.superfish', false);
if (e.type === 'MSPointerDown' || e.type === 'pointerdown') {
$this.trigger('focus');
} else {
$.proxy(over, $this.parent('li'))();
}
}
},
over = function () {
var $this = $(this),
o = getOptions($this);
clearTimeout(o.sfTimer);
$this.siblings().superfish('hide').end().superfish('show');
},
out = function () {
var $this = $(this),
o = getOptions($this);
if (ios) {
$.proxy(close, $this, o)();
}
else {
clearTimeout(o.sfTimer);
o.sfTimer = setTimeout($.proxy(close, $this, o), o.delay);
}
},
close = function (o) {
o.retainPath = ($.inArray(this[0], o.$path) > -1);
this.superfish('hide');
if (!this.parents('.' + o.hoverClass).length) {
o.onIdle.call(getMenu(this));
if (o.$path.length) {
$.proxy(over, o.$path)();
}
}
},
getMenu = function ($el) {
return $el.closest('.' + c.menuClass);
},
getOptions = function ($el) {
return getMenu($el).data('sf-options');
};
return {
// public methods
hide: function (instant) {
if (this.length) {
var $this = this,
o = getOptions($this);
if (!o) {
return this;
}
var not = (o.retainPath === true) ? o.$path : '',
$ul = $this.find('li.' + o.hoverClass).add(this).not(not).removeClass(o.hoverClass).children(o.popUpSelector),
speed = o.speedOut;
if (instant) {
$ul.show();
speed = 0;
}
o.retainPath = false;
if (o.onBeforeHide.call($ul) === false) {
return this;
}
$ul.stop(true, true).animate(o.animationOut, speed, function () {
var $this = $(this);
o.onHide.call($this);
});
}
return this;
},
show: function () {
var o = getOptions(this);
if (!o) {
return this;
}
var $this = this.addClass(o.hoverClass),
$ul = $this.children(o.popUpSelector);
if (o.onBeforeShow.call($ul) === false) {
return this;
}
$ul.stop(true, true).animate(o.animation, o.speed, function () {
o.onShow.call($ul);
});
return this;
},
destroy: function () {
return this.each(function () {
var $this = $(this),
o = $this.data('sf-options'),
$hasPopUp;
if (!o) {
return false;
}
$hasPopUp = $this.find(o.popUpSelector).parent('li');
clearTimeout(o.sfTimer);
toggleMenuClasses($this, o);
toggleAnchorClass($hasPopUp);
toggleTouchAction($this);
// remove event handlers
$this.off('.superfish').off('.hoverIntent');
// clear animation's inline display style
$hasPopUp.children(o.popUpSelector).attr('style', function (i, style) {
return style.replace(/display[^;]+;?/g, '');
});
// reset 'current' path classes
o.$path.removeClass(o.hoverClass + ' ' + c.bcClass).addClass(o.pathClass);
$this.find('.' + o.hoverClass).removeClass(o.hoverClass);
o.onDestroy.call($this);
$this.removeData('sf-options');
});
},
init: function (op) {
return this.each(function () {
var $this = $(this);
if ($this.data('sf-options')) {
return false;
}
var o = $.extend({}, $.fn.superfish.defaults, op),
$hasPopUp = $this.find(o.popUpSelector).parent('li');
o.$path = setPathToCurrent($this, o);
$this.data('sf-options', o);
toggleMenuClasses($this, o);
toggleAnchorClass($hasPopUp);
toggleTouchAction($this);
applyHandlers($this, o);
$hasPopUp.not('.' + c.bcClass).superfish('hide', true);
o.onInit.call(this);
});
}
};
})();
$.fn.superfish = function (method, args) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
else if (typeof method === 'object' || ! method) {
return methods.init.apply(this, arguments);
}
else {
return $.error('Method ' + method + ' does not exist on jQuery.fn.superfish');
}
};
$.fn.superfish.defaults = {
popUpSelector: 'ul,.sf-mega', // within menu context
hoverClass: 'sfHover',
pathClass: 'overrideThisToUse',
pathLevels: 1,
delay: 800,
animation: {opacity: 'show'},
animationOut: {opacity: 'hide'},
speed: 'normal',
speedOut: 'fast',
cssArrows: true,
disableHI: false,
onInit: $.noop,
onBeforeShow: $.noop,
onShow: $.noop,
onBeforeHide: $.noop,
onHide: $.noop,
onIdle: $.noop,
onDestroy: $.noop,
onHandleTouch: $.noop
};
})(jQuery, window);
/**
* hoverIntent is similar to jQuery's built-in "hover" method except that
* instead of firing the handlerIn function immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the event. The handlerOut function is only
* called after a matching handlerIn.
*
* hoverIntent r7 // 2013.03.11 // jQuery 1.9.1+
* http://cherne.net/brian/resources/jquery.hoverIntent.html
*
* You may use hoverIntent under the terms of the MIT license. Basically that
* means you are free to use hoverIntent as long as this header is left intact.
* Copyright 2007, 2013 Brian Cherne
*
* // basic usage ... just like .hover()
* .hoverIntent( handlerIn, handlerOut )
* .hoverIntent( handlerInOut )
*
* // basic usage ... with event delegation!
* .hoverIntent( handlerIn, handlerOut, selector )
* .hoverIntent( handlerInOut, selector )
*
* // using a basic configuration object
* .hoverIntent( config )
*
* @param handlerIn function OR configuration object
* @param handlerOut function OR selector for delegation OR undefined
* @param selector selector OR undefined
* @author Brian Cherne <brian(at)cherne(dot)net>
**/
(function($) {
$.fn.hoverIntent = function(handlerIn,handlerOut,selector) {
// default configuration values
var cfg = {
interval: 100,
sensitivity: 7,
timeout: 0
};
if ( typeof handlerIn === "object" ) {
cfg = $.extend(cfg, handlerIn );
} else if ($.isFunction(handlerOut)) {
cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } );
} else {
cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } );
}
// instantiate variables
// cX, cY = current X and Y position of mouse, updated by mousemove event
// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
var cX, cY, pX, pY;
// A private function for getting mouse position
var track = function(ev) {
cX = ev.pageX;
cY = ev.pageY;
};
// A private function for comparing current and previous mouse position
var compare = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
// compare mouse positions to see if they've crossed the threshold
if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
$(ob).off("mousemove.hoverIntent",track);
// set hoverIntent state to true (so mouseOut can be called)
ob.hoverIntent_s = 1;
return cfg.over.apply(ob,[ev]);
} else {
// set previous coordinates for next time
pX = cX; pY = cY;
// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
}
};
// A private function for delaying the mouseOut function
var delay = function(ev,ob) {
ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
ob.hoverIntent_s = 0;
return cfg.out.apply(ob,[ev]);
};
// A private function for handling mouse 'hovering'
var handleHover = function(e) {
// copy objects to be passed into t (required for event object to be passed in IE)
var ev = jQuery.extend({},e);
var ob = this;
// cancel hoverIntent timer if it exists
if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
// if e.type == "mouseenter"
if (e.type == "mouseenter") {
// set "previous" X and Y position based on initial entry point
pX = ev.pageX; pY = ev.pageY;
// update "current" X and Y position based on mousemove
$(ob).on("mousemove.hoverIntent",track);
// start polling interval (self-calling timeout) to compare mouse coordinates over time
if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
// else e.type == "mouseleave"
} else {
// unbind expensive mousemove event
$(ob).off("mousemove.hoverIntent",track);
// if hoverIntent state is true, then call the mouseOut function after the specified delay
if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
}
};
// listen for mouseenter and mouseleave
return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector);
};
})(jQuery);
/* Takes every category header available and adds a collapse option */
$(function() {
if (smf_member_id > 0)
$('div.boardindex_table div.cat_bar').each(function(index, el)
{
var catid = el.id.replace('category_', '');
new smc_Toggle({
bToggleEnabled: true,
bCurrentlyCollapsed: $('#category_' + catid + '_upshrink').data('collapsed'),
aHeader: $('#category_' + catid),
aSwappableContainers: [
'category_' + catid + '_boards'
],
aSwapImages: [
{
sId: 'category_' + catid + '_upshrink',
msgExpanded: '',
msgCollapsed: ''
}
],
oThemeOptions: {
bUseThemeSettings: true,
sOptionName: 'collapse_category_' + catid,
sSessionVar: smf_session_var,
sSessionId: smf_session_id
}
});
});
});
/* Mobile Pop */
$(function() {
$( '.mobile_act' ).click(function() {
$( '#mobile_action' ).show();
});
$( '.hide_popup' ).click(function() {
$( '#mobile_action' ).hide();
});
$( '.mobile_mod' ).click(function() {
$( '#mobile_moderation' ).show();
});
$( '.hide_popup' ).click(function() {
$( '#mobile_moderation' ).hide();
});
$( '.mobile_user_menu' ).click(function() {
$( '#mobile_user_menu' ).show();
});
$( '.hide_popup' ).click(function() {
$( '#mobile_user_menu' ).hide();
});
});