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, '' );
+ options.tag = options.tag.replace( /[ _-]+/g, '-' ).replace( /[^-a-z0-9]+/ig, '' );
if ( options.tag ) {
$notification.addClass( 'mw-notification-tag-' + options.tag );
} else {
if ( options.type ) {
// Sanitize options.type
- options.type = options.type.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
+ options.type = options.type.replace( /[ _-]+/g, '-' ).replace( /[^-a-z0-9]+/ig, '' );
$notification.addClass( 'mw-notification-type-' + options.type );
}
// 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.
+ // timeout: Holds appropriate methods to set/clear timeouts
this.autoHideSeconds = options.autoHideSeconds &&
notification.autoHideSeconds[ options.autoHideSeconds ] ||
notification.autoHideSeconds.short;
this.message = message;
this.options = options;
this.$notification = $notification;
+ if ( options.visibleTimeout ) {
+ this.timeout = require( 'mediawiki.visibleTimeout' );
+ } else {
+ this.timeout = {
+ set: setTimeout,
+ clear: clearTimeout
+ };
+ }
}
/**
Notification.prototype.start = function () {
var options, $notification, $tagMatches, autohideCount;
- $area.show();
+ $area.css( 'display', '' );
if ( this.isOpen ) {
return;
}
this.isPaused = true;
- if ( this.timeout ) {
- clearTimeout( this.timeout );
- delete this.timeout;
+ if ( this.timeoutId ) {
+ this.timeout.clear( this.timeoutId );
+ delete this.timeoutId;
}
};
*/
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 () {
+ notif.timeoutId = notif.timeout.set( function () {
// Already finished, so don't try to re-clear it
- delete notif.timeout;
+ delete notif.timeoutId;
notif.close();
}, this.autoHideSeconds * 1000 );
}
if ( openNotificationCount === 0 ) {
// Hide the area after the last notification closes. Otherwise, the padding on
// the area can be obscure content, despite the area being empty/invisible (T54659). // FIXME
- $area.hide();
+ $area.css( 'display', 'none' );
notif.$notification.remove();
} else {
notif.$notification.slideUp( 'fast', function () {
* @ignore
*/
function init() {
- var offset,
+ var offset, notif,
isFloating = false;
+ function updateAreaMode() {
+ var shouldFloat = window.pageYOffset > offset.top;
+ if ( isFloating === shouldFloat ) {
+ return;
+ }
+ isFloating = shouldFloat;
+ $area
+ .toggleClass( 'mw-notification-area-floating', isFloating )
+ .toggleClass( 'mw-notification-area-layout', !isFloating );
+ }
+
+ // Write to the DOM:
+ // Prepend the notification area to the content area and save its object.
$area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
// Pause auto-hide timers when the mouse is in the notification area.
.on( {
e.stopPropagation();
} );
- // Prepend the notification area to the content area and save it's object.
mw.util.$content.prepend( $area );
- offset = $area.offset();
- $area.hide();
- function updateAreaMode() {
- var shouldFloat = window.pageYOffset > offset.top;
- if ( isFloating === shouldFloat ) {
- return;
- }
- isFloating = shouldFloat;
- $area
- .toggleClass( 'mw-notification-area-floating', isFloating )
- .toggleClass( 'mw-notification-area-layout', !isFloating );
- }
+ // Read from the DOM:
+ // Must be in the next frame to avoid synchronous layout
+ // computation from offset()/getBoundingClientRect().
+ rAF( function () {
+ offset = $area.offset();
+
+ // Initial mode (reads, and then maybe writes)
+ updateAreaMode();
- $( window ).on( 'scroll', updateAreaMode );
+ // Once we have the offset for where it would normally render, set the
+ // initial state of the (currently empty) notification area to be hidden.
+ $area.css( 'display', 'none' );
- // Initial mode
- updateAreaMode();
+ $( window ).on( 'scroll', updateAreaMode );
+
+ // Handle pre-ready queue.
+ isPageReady = true;
+ while ( preReadyNotifQueue.length ) {
+ notif = preReadyNotifQueue.shift();
+ notif.start();
+ }
+ } );
}
/**
* - type:
* An optional string for the type of the message used for styling:
* Examples: 'info', 'warn', 'error'.
+ *
+ * - visibleTimeout:
+ * A boolean indicating if the autoHide timeout should be based on
+ * time the page was visible to user. Or if it should use wall clock time.
*/
defaults: {
autoHide: true,
autoHideSeconds: 'short',
tag: null,
title: null,
- type: null
+ type: null,
+ visibleTimeout: true
},
/**
autoHideLimit: 3
};
- $( function () {
- var notif;
-
- init();
-
- // Handle pre-ready queue.
- isPageReady = true;
- while ( preReadyNotifQueue.length ) {
- notif = preReadyNotifQueue.shift();
- notif.start();
- }
- } );
+ $( init );
mw.notification = notification;