| Current Path : /var/www/element/data/www/wiki.element.ru/resources/mediawiki/ |
| Current File : /var/www/element/data/www/wiki.element.ru/resources/mediawiki/mediawiki.notification.js |
( function ( mw, $ ) {
'use strict';
var notification,
isPageReady = false,
preReadyNotifQueue = [],
// The #mw-notification-area div that all notifications are contained inside.
$area = null;
/**
* Creates a Notification object for 1 message.
* Does not insert anything into the document (see #start).
*
* The "_" in the name is to avoid a bug (http://github.com/senchalabs/jsduck/issues/304)
* It is not part of the actual class name.
*
* @class mw.Notification_
* @alternateClassName mw.Notification
* @private
*
* @constructor
*/
function Notification( message, options ) {
var $notification, $notificationTitle, $notificationContent;
$notification = $( '<div class="mw-notification"></div>' )
.data( 'mw.notification', this )
.addClass( options.autoHide ? 'mw-notification-autohide' : 'mw-notification-noautohide' );
if ( options.tag ) {
// Sanitize options.tag before it is used by any code. (Including Notification class methods)
options.tag = options.tag.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
if ( options.tag ) {
$notification.addClass( 'mw-notification-tag-' + options.tag );
} else {
delete options.tag;
}
}
if ( options.title ) {
$notificationTitle = $( '<div class="mw-notification-title"></div>' )
.text( options.title )
.appendTo( $notification );
}
$notificationContent = $( '<div class="mw-notification-content"></div>' );
if ( typeof message === 'object' ) {
// Handle mw.Message objects separately from DOM nodes and jQuery objects
if ( message instanceof mw.Message ) {
$notificationContent.html( message.parse() );
} else {
$notificationContent.append( message );
}
} else {
$notificationContent.text( message );
}
$notificationContent.appendTo( $notification );
// Private state parameters, meant for internal use only
// isOpen: Set to true after .start() is called to avoid double calls.
// Set back to false after .close() to avoid duplicating the close animation.
// isPaused: false after .resume(), true after .pause(). Avoids duplicating or breaking the hide timeouts.
// Set to true initially so .start() can call .resume().
// message: The message passed to the notification. Unused now but may be used in the future
// to stop replacement of a tagged notification with another notification using the same message.
// options: The options passed to the notification with a little sanitization. Used by various methods.
// $notification: jQuery object containing the notification DOM node.
this.isOpen = false;
this.isPaused = true;
this.message = message;
this.options = options;
this.$notification = $notification;
}
/**
* Start the notification.
* This inserts it into the page, closes any matching tagged notifications,
* handles the fadeIn animations and repacement transitions, and starts autoHide timers.
*/
Notification.prototype.start = function () {
var
// Local references
$notification, options,
// Original opacity so that we can animate back to it later
opacity,
// Other notification elements matching the same tag
$tagMatches,
outerHeight,
placeholderHeight,
autohideCount,
notif;
if ( this.isOpen ) {
return;
}
this.isOpen = true;
options = this.options;
$notification = this.$notification;
opacity = this.$notification.css( 'opacity' );
// Set the opacity to 0 so we can fade in later.
$notification.css( 'opacity', 0 );
if ( options.tag ) {
// Check to see if there are any tagged notifications with the same tag as the new one
$tagMatches = $area.find( '.mw-notification-tag-' + options.tag );
}
// If we found a tagged notification use the replacement pattern instead of the new
// notification fade-in pattern.
if ( options.tag && $tagMatches.length ) {
// Iterate over the tag matches to find the outerHeight we should use
// for the placeholder.
outerHeight = 0;
$tagMatches.each( function () {
var notif = $( this ).data( 'mw.notification' );
if ( notif ) {
// Use the notification's height + padding + border + margins
// as the placeholder height.
outerHeight = notif.$notification.outerHeight( true );
if ( notif.$replacementPlaceholder ) {
// Grab the height of a placeholder that has not finished animating.
placeholderHeight = notif.$replacementPlaceholder.height();
// Remove any placeholders added by a previous tagged
// notification that was in the middle of replacing another.
// This also makes sure that we only grab the placeholderHeight
// for the most recent notification.
notif.$replacementPlaceholder.remove();
delete notif.$replacementPlaceholder;
}
// Close the previous tagged notification
// Since we're replacing it do this with a fast speed and don't output a placeholder
// since we're taking care of that transition ourselves.
notif.close( { speed: 'fast', placeholder: false } );
}
} );
if ( placeholderHeight !== undefined ) {
// If the other tagged notification was in the middle of replacing another
// tagged notification, continue from the placeholder's height instead of
// using the outerHeight of the notification.
outerHeight = placeholderHeight;
}
$notification
// Insert the new notification before the tagged notification(s)
.insertBefore( $tagMatches.first() )
.css( {
// Use an absolute position so that we can use a placeholder to gracefully push other notifications
// into the right spot.
position: 'absolute',
width: $notification.width()
} )
// Fade-in the notification
.animate( { opacity: opacity },
{
duration: 'slow',
complete: function () {
// After we've faded in clear the opacity and let css take over
$( this ).css( { opacity: '' } );
}
} );
notif = this;
// Create a clear placeholder we can use to make the notifications around the notification that is being
// replaced expand or contract gracefully to fit the height of the new notification.
notif.$replacementPlaceholder = $( '<div>' )
// Set the height to the space the previous notification or placeholder took
.css( 'height', outerHeight )
// Make sure that this placeholder is at the very end of this tagged notification group
.insertAfter( $tagMatches.eq( -1 ) )
// Animate the placeholder height to the space that this new notification will take up
.animate( { height: $notification.outerHeight( true ) },
{
// Do space animations fast
speed: 'fast',
complete: function () {
// Reset the notification position after we've finished the space animation
// However do not do it if the placeholder was removed because another tagged
// notification went and closed this one.
if ( notif.$replacementPlaceholder ) {
$notification.css( 'position', '' );
}
// Finally, remove the placeholder from the DOM
$( this ).remove();
}
} );
} else {
// Append to the notification area and fade in to the original opacity.
$notification
.appendTo( $area )
.animate( { opacity: opacity },
{
duration: 'fast',
complete: function () {
// After we've faded in clear the opacity and let css take over
$( this ).css( 'opacity', '' );
}
}
);
}
// By default a notification is paused.
// If this notification is within the first {autoHideLimit} notifications then
// start the auto-hide timer as soon as it's created.
autohideCount = $area.find( '.mw-notification-autohide' ).length;
if ( autohideCount <= notification.autoHideLimit ) {
this.resume();
}
};
/**
* Pause any running auto-hide timer for this notification
*/
Notification.prototype.pause = function () {
if ( this.isPaused ) {
return;
}
this.isPaused = true;
if ( this.timeout ) {
clearTimeout( this.timeout );
delete this.timeout;
}
};
/**
* Start autoHide timer if not already started.
* Does nothing if autoHide is disabled.
* Either to resume from pause or to make the first start.
*/
Notification.prototype.resume = function () {
var notif = this;
if ( !notif.isPaused ) {
return;
}
// Start any autoHide timeouts
if ( notif.options.autoHide ) {
notif.isPaused = false;
notif.timeout = setTimeout( function () {
// Already finished, so don't try to re-clear it
delete notif.timeout;
notif.close();
}, notification.autoHideSeconds * 1000 );
}
};
/**
* Close/hide the notification.
*
* @param {Object} options An object containing options for the closing of the notification.
* These are typically only used internally.
*
* - speed: Use a close speed different than the default 'slow'.
* - placeholder: Set to false to disable the placeholder transition.
*/
Notification.prototype.close = function ( options ) {
if ( !this.isOpen ) {
return;
}
this.isOpen = false;
// Clear any remaining timeout on close
this.pause();
options = $.extend( {
speed: 'slow',
placeholder: true
}, options );
// Remove the mw-notification-autohide class from the notification to avoid
// having a half-closed notification counted as a notification to resume
// when handling {autoHideLimit}.
this.$notification.removeClass( 'mw-notification-autohide' );
// Now that a notification is being closed. Start auto-hide timers for any
// notification that has now become one of the first {autoHideLimit} notifications.
notification.resume();
this.$notification
.css( {
// Don't trigger any mouse events while fading out, just in case the cursor
// happens to be right above us when we transition upwards.
pointerEvents: 'none',
// Set an absolute position so we can move upwards in the animation.
// Notification replacement doesn't look right unless we use an animation like this.
position: 'absolute',
// We must fix the width to avoid it shrinking horizontally.
width: this.$notification.width()
} )
// Fix the top/left position to the current computed position from which we
// can animate upwards.
.css( this.$notification.position() );
// This needs to be done *after* notification's position has been made absolute.
if ( options.placeholder ) {
// Insert a placeholder with a height equal to the height of the
// notification plus it's vertical margins in place of the notification
var $placeholder = $( '<div>' )
.css( 'height', this.$notification.outerHeight( true ) )
.insertBefore( this.$notification );
}
// Animate opacity and top to create fade upwards animation for notification closing
this.$notification
.animate( {
opacity: 0,
top: '-=35'
}, {
duration: options.speed,
complete: function () {
// Remove the notification
$( this ).remove();
if ( options.placeholder ) {
// Use a fast slide up animation after closing to make it look like the notifications
// below slide up into place when the notification disappears
$placeholder.slideUp( 'fast', function () {
// Remove the placeholder
$( this ).remove();
} );
}
}
} );
};
/**
* Helper function, take a list of notification divs and call
* a function on the Notification instance attached to them.
*
* @param {jQuery} $notifications A jQuery object containing notification divs
* @param {string} fn The name of the function to call on the Notification instance
*/
function callEachNotification( $notifications, fn ) {
$notifications.each( function () {
var notif = $( this ).data( 'mw.notification' );
if ( notif ) {
notif[fn]();
}
} );
}
/**
* Initialisation.
* Must only be called once, and not before the document is ready.
* @ignore
*/
function init() {
$area = $( '<div id="mw-notification-area"></div>' )
// Pause auto-hide timers when the mouse is in the notification area.
.on( {
mouseenter: notification.pause,
mouseleave: notification.resume
} )
// When clicking on a notification close it.
.on( 'click', '.mw-notification', function () {
var notif = $( this ).data( 'mw.notification' );
if ( notif ) {
notif.close();
}
} )
// Stop click events from <a> tags from propogating to prevent clicking.
// on links from hiding a notification.
.on( 'click', 'a', function ( e ) {
e.stopPropagation();
} );
// Prepend the notification area to the content area and save it's object.
mw.util.$content.prepend( $area );
}
/**
* @class mw.notification
* @singleton
*/
notification = {
/**
* Pause auto-hide timers for all notifications.
* Notifications will not auto-hide until resume is called.
* @see mw.Notification#pause
*/
pause: function () {
callEachNotification(
$area.children( '.mw-notification' ),
'pause'
);
},
/**
* Resume any paused auto-hide timers from the beginning.
* Only the first #autoHideLimit timers will be resumed.
*/
resume: function () {
callEachNotification(
// Only call resume on the first #autoHideLimit notifications.
// Exclude noautohide notifications to avoid bugs where #autoHideLimit
// `{ autoHide: false }` notifications are at the start preventing any
// auto-hide notifications from being autohidden.
$area.children( '.mw-notification-autohide' ).slice( 0, notification.autoHideLimit ),
'resume'
);
},
/**
* Display a notification message to the user.
*
* @param {HTMLElement|jQuery|mw.Message|string} message
* @param {Object} options The options to use for the notification.
* See #defaults for details.
*/
notify: function ( message, options ) {
var notif;
options = $.extend( {}, notification.defaults, options );
notif = new Notification( message, options );
if ( isPageReady ) {
notif.start();
} else {
preReadyNotifQueue.push( notif );
}
},
/**
* @property {Object}
* The defaults for #notify options parameter.
*
* - autoHide:
* A boolean indicating whether the notifification should automatically
* be hidden after shown. Or if it should persist.
*
* - tag:
* An optional string. When a notification is tagged only one message
* with that tag will be displayed. Trying to display a new notification
* with the same tag as one already being displayed will cause the other
* notification to be closed and this new notification to open up inside
* the same place as the previous notification.
*
* - title:
* An optional title for the notification. Will be displayed above the
* content. Usually in bold.
*/
defaults: {
autoHide: true,
tag: false,
title: undefined
},
/**
* @property {number}
* Number of seconds to wait before auto-hiding notifications.
*/
autoHideSeconds: 5,
/**
* @property {number}
* Maximum number of notifications to count down auto-hide timers for.
* Only the first #autoHideLimit notifications being displayed will
* auto-hide. Any notifications further down in the list will only start
* counting down to auto-hide after the first few messages have closed.
*
* This basically represents the number of notifications the user should
* be able to process in #autoHideSeconds time.
*/
autoHideLimit: 3
};
$( function () {
var notif;
init();
// Handle pre-ready queue.
isPageReady = true;
while ( preReadyNotifQueue.length ) {
notif = preReadyNotifQueue.shift();
notif.start();
}
} );
mw.notification = notification;
}( mediaWiki, jQuery ) );