/** * Implements mediaWiki.notification library */ ( function ( mw, $ ) { 'use strict'; var isPageReady = false, isInitialized = false, preReadyNotifQueue = [], /** * @var {jQuery} * 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()). * * @constructor * @see mw.notification.notify */ function Notification( message, options ) { var $notification, $notificationTitle, $notificationContent; $notification = $( '
' ) .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 = $( '' ) .text( options.title ) .appendTo( $notification ); } $notificationContent = $( '' ); 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; 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: '' } ); } } ); // 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. var self = this; self.$replacementPlaceholder = $( '