mediawiki-extensions-Echo/modules/ooui/mw.echo.ui.NotificationBadgeWidget.js
Moriel Schottlender 1ac72cc01a Split alerts and messages in Echo
Split the notifications into 'alert' and 'message' badget with two
different flyouts. Also clean up styling and module behavior.

** Depends on ooui change Id4bbe14ba0bf6c for footers in popups.
** Depends on ooui change Ie93e4d6ed5637c for fixing a bug in
   inverted icons.

** MobileFrontend must also be updated to support the new modules
   in this patch  I168f485d6e54cb4067

In this change:
* Split notifcations into alert and messages and display those in
  two different badges.
* Create two separate flyout/popups for each category with their
  notifications.
* Create a view-model to control notification state and emit events
  for both the popup and the badge to intercept and react to.
* Clean up module load and distribution:
  * Create an ext.echo.ui module for javascript-ui support and ooui
    widgets.
  * Create an ext.echo.nojs module that unifies all base classes that
    are needed for both nojs and js support, that the js version
    builds upon.
  * Create a separate ext.echo.logger module as a singleton that can
    be called to perform all logging.
* Clean up style uses
  * Move the special page LESS file into nojs module so all styles
    load properly even in nojs mode.
  * Transfer some of the styling from JS to LESS for consistency.
  * Make the 'read more' button load already with the styles it
    needs to look like a button, since its behavior is similar in
    nojs and js vesions, but before its classes were applied only
    by the js, making it inconsistent and also making its appearance
    'jump' from a link to a button.
* Delete and clean up all old and unused files.
* Moved 'Help.png' icon from modules/overlay to modules/icons for
  later use.

Bug: T108190
Change-Id: I55f440ed9f64c46817f620328a6bb522d44c9ca9
2015-09-02 15:36:37 -07:00

238 lines
7.1 KiB
JavaScript

( function ( mw, $ ) {
/**
* Notification badge button widget for echo popup.
*
* @class
* @extends OO.ui.ButtonWidget
*
* @constructor
* @param {Object} [config] Configuration object
* @cfg {string} [type='alert'] Notification type 'alert' or 'message'
* @cfg {number} [numItems=0] How many items are in the button display
* @cfg {boolean} [hasUnread=false] Whether there are unread items
* @cfg {boolean} [markReadWhenSeen=false] Mark all notifications as read on open
* @cfg {string|Object} [badgeIcon] The icons to use for this button.
* If this is a string, it will be used as the icon regardless of the state.
* If it is an object, it must include
* the properties 'unread' and 'read' with icons attached to both. For example:
* { badgeIcon: {
* unread: 'bellOn',
* read: 'bell'
* } }
*/
mw.echo.ui.NotificationBadgeWidget = function MwEchoUiNotificationBadgeButtonPopupWidget( config ) {
var buttonFlags, allNotificationsButton, preferencesButton, $footer;
config = config || {};
config.links = config.links || {};
// Mixin constructors
OO.ui.mixin.PendingElement.call( this, config );
this.type = config.type || 'alert';
this.numItems = config.numItems || 0;
this.hasUnread = !!config.hasUnread;
this.badgeIcon = config.badgeIcon || {};
this.markReadWhenSeen = !!config.markReadWhenSeen;
this.hasRunFirstTime = false;
buttonFlags = [ 'primary' ];
if ( this.hasUnread ) {
buttonFlags.push( 'unseen' );
}
// View model
this.notificationsModel = new mw.echo.dm.NotificationsModel( {
type: this.type,
limit: 25,
userLang: mw.config.get( 'wgUserLanguage' )
} );
// Notifications widget
this.notificationsWidget = new mw.echo.ui.NotificationsWidget(
this.notificationsModel,
{
type: this.type,
markReadWhenSeen: this.markReadWhenSeen
}
);
this.setPendingElement( this.notificationsWidget.$element );
// Footer
allNotificationsButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'next',
label: mw.msg( 'echo-overlay-link' ),
href: config.links.notifications,
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-allnotifs' ]
} );
preferencesButton = new OO.ui.ButtonWidget( {
framed: false,
icon: 'advanced',
label: mw.msg( 'mypreferences' ),
href: config.links.preferences,
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-preferences' ]
} );
$footer = $( '<div>' )
.addClass( 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer' )
.append(
allNotificationsButton.$element,
preferencesButton.$element
);
// Parent constructor
mw.echo.ui.NotificationBadgeWidget.parent.call( this, $.extend( {
framed: false,
flags: buttonFlags,
label: this.numItems,
icon: (
typeof this.badgeIcon === 'string' ?
this.badgeIcon :
this.badgeIcon[ this.hasUnread ? 'unread' : 'read' ]
),
popup: {
$content: this.notificationsWidget.$element,
$footer: $footer,
width: 450,
head: true,
// This covers the messages 'echo-notification-alert-text-only'
// and 'echo-notification-message-text-only'
label: mw.msg( 'echo-notification-' + this.type + '-text-only' )
}
}, config ) );
// HACK: Add an icon to the popup head label
this.popup.$head.prepend( new OO.ui.IconWidget( { icon: 'bell' } ).$element );
// Mark all as read button
this.markAllReadButton = new OO.ui.ButtonWidget( {
framed: false,
label: mw.msg( 'echo-mark-all-as-read' ),
classes: [ 'mw-echo-ui-notificationsWidget-markAllReadButton' ]
} );
// Hide the close button
this.popup.closeButton.toggle( false );
// Add the 'mark all as read' button to the header
this.popup.$head.append( this.markAllReadButton.$element );
this.markAllReadButton.toggle( !this.markReadWhenSeen && this.hasUnread );
// Events
this.markAllReadButton.connect( this, { click: 'onMarkAllReadButtonClick' } );
this.notificationsModel.connect( this, {
updateSeenTime: 'updateBadge',
add: 'updateBadge',
unseenChange: 'updateBadge',
unreadChange: 'updateBadge'
} );
this.$element
.addClass(
'mw-echo-ui-notificationBadgeButtonPopupWidget ' +
'mw-echo-ui-notificationBadgeButtonPopupWidget-' + this.type
);
};
/* Initialization */
OO.inheritClass( mw.echo.ui.NotificationBadgeWidget, OO.ui.PopupButtonWidget );
OO.mixinClass( mw.echo.ui.NotificationBadgeWidget, OO.ui.mixin.PendingElement );
/**
* Update the badge state and label based on changes to the model
*/
mw.echo.ui.NotificationBadgeWidget.prototype.updateBadge = function () {
var unseenCount = this.notificationsModel.getUnseenCount(),
unreadCount = this.notificationsModel.getUnreadCount();
// Update numbers and seen/unseen state
this.setFlags( { unseen: !!unseenCount } );
this.setLabel( String( unreadCount ) );
};
/**
* Respond to 'mark all as read' button click
*/
mw.echo.ui.NotificationBadgeWidget.prototype.onMarkAllReadButtonClick = function () {
this.notificationsModel.markAllRead();
};
/**
* Extend the response to button click so we can also update the notification list.
*/
mw.echo.ui.NotificationBadgeWidget.prototype.onAction = function () {
var widget = this,
time = mw.now();
// Parent method
mw.echo.ui.NotificationBadgeWidget.parent.prototype.onAction.call( this, arguments );
// Log the click event
mw.echo.logger.logInteraction(
'ui-badge-link-click',
mw.echo.Logger.static.context,
null,
this.type
);
if ( !this.notificationsModel.isFetchingNotifications() ) {
if ( this.hasRunFirstTime ) {
// Don't clear items on the first time we open the popup
this.notificationsModel.clearItems();
// HACK: Clippable doesn't resize the clippable area when
// it calculates the new size. Since the popup contents changed
// and the popup is "empty" now, we need to manually set its
// size to 1px so the clip calculations will resize it properly.
// See bug report: https://phabricator.wikimedia.org/T110759
this.popup.$clippable.css( 'height', '1px' );
this.popup.clip();
}
this.pushPending();
this.notificationsModel.fetchNotifications()
.then( function ( idArray ) {
// Clip again
widget.popup.clip();
// Log impressions
mw.echo.logger.logNotificationImpressions( this.type, idArray, mw.echo.Logger.static.context.popup );
// Log timing
mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - time );
// // Mark notifications as 'read' if markReadWhenSeen is set to true
if ( widget.markReadWhenSeen ) {
return widget.notificationsModel.markAllRead();
}
} )
.then( function () {
// Update seen time
widget.notificationsModel.updateSeenTime();
} )
.always( function () {
// Pop pending
widget.popPending();
// Nullify the promise; let the user fetch again
widget.fetchNotificationsPromise = null;
} );
this.hasRunFirstTime = true;
}
};
/**
* Get the notifications model attached to this widget
*
* @return {mw.echo.dm.NotificationsModel} Notifications model
*/
mw.echo.ui.NotificationBadgeWidget.prototype.getModel = function () {
return this.notificationsModel;
};
} )( mediaWiki, jQuery );