2015-08-13 00:54:16 +00:00
|
|
|
( function ( mw, $ ) {
|
|
|
|
/**
|
|
|
|
* Notification badge button widget for echo popup.
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends OO.ui.ButtonWidget
|
|
|
|
*
|
|
|
|
* @constructor
|
2015-10-26 22:23:22 +00:00
|
|
|
* @param {mw.echo.dm.NotificationsModel} model Notifications view model
|
2015-08-13 00:54:16 +00:00
|
|
|
* @param {Object} [config] Configuration object
|
|
|
|
* @cfg {number} [numItems=0] How many items are in the button display
|
2015-09-03 21:24:03 +00:00
|
|
|
* @cfg {boolean} [hasUnseen=false] Whether there are unseen items
|
2015-08-13 00:54:16 +00:00
|
|
|
* @cfg {boolean} [markReadWhenSeen=false] Mark all notifications as read on open
|
2015-09-09 20:18:52 +00:00
|
|
|
* @cfg {number} [popupWidth=450] The width of the popup
|
2015-08-13 00:54:16 +00:00
|
|
|
* @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
|
2015-09-03 21:24:03 +00:00
|
|
|
* the properties 'unseen' and 'seen' with icons attached to both. For example:
|
2015-08-13 00:54:16 +00:00
|
|
|
* { badgeIcon: {
|
2015-09-03 21:24:03 +00:00
|
|
|
* unseen: 'bellOn',
|
|
|
|
* seen: 'bell'
|
2015-08-13 00:54:16 +00:00
|
|
|
* } }
|
2015-09-24 01:13:37 +00:00
|
|
|
* @cfg {string} [href] URL the badge links to
|
2015-11-21 01:54:12 +00:00
|
|
|
* @cfg {jQuery} [$overlay] A jQuery element functioning as an overlay
|
|
|
|
* for popups.
|
2015-08-13 00:54:16 +00:00
|
|
|
*/
|
2015-10-26 22:23:22 +00:00
|
|
|
mw.echo.ui.NotificationBadgeWidget = function MwEchoUiNotificationBadgeButtonPopupWidget( model, config ) {
|
2015-09-09 20:18:52 +00:00
|
|
|
var buttonFlags, allNotificationsButton, preferencesButton, footerButtonGroupWidget, $footer;
|
2015-08-13 00:54:16 +00:00
|
|
|
|
|
|
|
config = config || {};
|
|
|
|
config.links = config.links || {};
|
|
|
|
|
2015-09-17 00:05:52 +00:00
|
|
|
// Parent constructor
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.parent.call( this, config );
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
// Mixin constructors
|
|
|
|
OO.ui.mixin.PendingElement.call( this, config );
|
|
|
|
|
2015-11-21 01:54:12 +00:00
|
|
|
this.$overlay = config.$overlay || this.$element;
|
2015-10-16 23:18:25 +00:00
|
|
|
// Create a menu overlay
|
|
|
|
this.$menuOverlay = $( '<div>' )
|
|
|
|
.addClass( 'mw-echo-ui-NotificationBadgeWidget-overlay-menu' );
|
|
|
|
this.$overlay.append( this.$menuOverlay );
|
2015-11-21 01:54:12 +00:00
|
|
|
|
2015-10-26 22:23:22 +00:00
|
|
|
// View model
|
|
|
|
this.notificationsModel = model;
|
|
|
|
this.type = this.notificationsModel.getType();
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
this.numItems = config.numItems || 0;
|
|
|
|
this.markReadWhenSeen = !!config.markReadWhenSeen;
|
2015-09-17 00:05:52 +00:00
|
|
|
this.badgeIcon = config.badgeIcon || {};
|
2015-08-13 00:54:16 +00:00
|
|
|
this.hasRunFirstTime = false;
|
2015-09-23 00:04:14 +00:00
|
|
|
this.currentUnreadCountInBadge = 0;
|
2015-08-13 00:54:16 +00:00
|
|
|
|
|
|
|
buttonFlags = [ 'primary' ];
|
2015-09-03 21:24:03 +00:00
|
|
|
if ( !!config.hasUnseen ) {
|
2015-08-13 00:54:16 +00:00
|
|
|
buttonFlags.push( 'unseen' );
|
|
|
|
}
|
|
|
|
|
2015-09-17 00:05:52 +00:00
|
|
|
this.badgeButton = new mw.echo.ui.BadgeLinkWidget( {
|
|
|
|
label: this.numItems,
|
|
|
|
flags: buttonFlags,
|
|
|
|
badgeIcon: config.badgeIcon,
|
|
|
|
// The following messages can be used here:
|
|
|
|
// tooltip-pt-notifications-alert
|
|
|
|
// tooltip-pt-notifications-message
|
2015-09-24 01:13:37 +00:00
|
|
|
title: mw.msg( 'tooltip-pt-notifications-' + this.type ),
|
|
|
|
href: config.href
|
2015-09-17 00:05:52 +00:00
|
|
|
} );
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
// Notifications widget
|
|
|
|
this.notificationsWidget = new mw.echo.ui.NotificationsWidget(
|
|
|
|
this.notificationsModel,
|
|
|
|
{
|
|
|
|
type: this.type,
|
2015-10-16 23:18:25 +00:00
|
|
|
$overlay: this.$menuOverlay,
|
2015-08-13 00:54:16 +00:00
|
|
|
markReadWhenSeen: this.markReadWhenSeen
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Footer
|
|
|
|
allNotificationsButton = new OO.ui.ButtonWidget( {
|
|
|
|
icon: 'next',
|
|
|
|
label: mw.msg( 'echo-overlay-link' ),
|
|
|
|
href: config.links.notifications,
|
|
|
|
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-allnotifs' ]
|
|
|
|
} );
|
|
|
|
|
|
|
|
preferencesButton = new OO.ui.ButtonWidget( {
|
|
|
|
icon: 'advanced',
|
|
|
|
label: mw.msg( 'mypreferences' ),
|
|
|
|
href: config.links.preferences,
|
|
|
|
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer-preferences' ]
|
|
|
|
} );
|
|
|
|
|
2015-09-09 20:18:52 +00:00
|
|
|
footerButtonGroupWidget = new OO.ui.ButtonGroupWidget( {
|
|
|
|
items: [ allNotificationsButton, preferencesButton ]
|
|
|
|
} );
|
2015-08-13 00:54:16 +00:00
|
|
|
$footer = $( '<div>' )
|
|
|
|
.addClass( 'mw-echo-ui-notificationBadgeButtonPopupWidget-footer' )
|
2015-09-09 20:18:52 +00:00
|
|
|
.append( footerButtonGroupWidget.$element );
|
2015-09-17 00:05:52 +00:00
|
|
|
|
|
|
|
this.popup = new OO.ui.PopupWidget( {
|
|
|
|
$content: this.notificationsWidget.$element,
|
|
|
|
$footer: $footer,
|
2016-01-13 03:36:47 +00:00
|
|
|
width: config.popupWidth || 500,
|
2015-09-17 00:05:52 +00:00
|
|
|
autoClose: true,
|
2015-10-16 23:18:25 +00:00
|
|
|
// Also ignore clicks from the nested action menu items, that
|
|
|
|
// actually exist in the overlay
|
|
|
|
$autoCloseIgnore: this.$element.add( this.$menuOverlay ),
|
2015-09-17 00:05:52 +00:00
|
|
|
head: true,
|
2015-09-10 22:17:11 +00:00
|
|
|
// The following messages can be used here:
|
2015-09-17 00:05:52 +00:00
|
|
|
// echo-notification-alert-text-only
|
|
|
|
// echo-notification-message-text-only
|
2015-09-25 21:17:54 +00:00
|
|
|
label: mw.msg( 'echo-notification-' + this.type + '-text-only' ),
|
|
|
|
classes: [ 'mw-echo-ui-notificationBadgeButtonPopupWidget-popup' ]
|
2015-09-17 00:05:52 +00:00
|
|
|
} );
|
2015-08-13 00:54:16 +00:00
|
|
|
// HACK: Add an icon to the popup head label
|
2015-09-03 21:24:03 +00:00
|
|
|
this.popupHeadIcon = new OO.ui.IconWidget();
|
|
|
|
this.popup.$head.prepend( this.popupHeadIcon.$element );
|
|
|
|
|
2015-09-10 22:24:21 +00:00
|
|
|
this.setPendingElement( this.popup.$head );
|
2015-09-17 00:05:52 +00:00
|
|
|
this.updateIcon( !!config.hasUnseen );
|
2015-09-10 22:24:21 +00:00
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
// 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 );
|
2015-09-28 20:21:37 +00:00
|
|
|
this.markAllReadButton.toggle( false );
|
2015-08-13 00:54:16 +00:00
|
|
|
|
|
|
|
// Events
|
|
|
|
this.markAllReadButton.connect( this, { click: 'onMarkAllReadButtonClick' } );
|
|
|
|
this.notificationsModel.connect( this, {
|
|
|
|
updateSeenTime: 'updateBadge',
|
|
|
|
add: 'updateBadge',
|
|
|
|
unseenChange: 'updateBadge',
|
|
|
|
unreadChange: 'updateBadge'
|
|
|
|
} );
|
2015-09-08 00:11:31 +00:00
|
|
|
this.popup.connect( this, { toggle: 'onPopupToggle' } );
|
2015-09-17 00:05:52 +00:00
|
|
|
this.badgeButton.connect( this, {
|
|
|
|
click: 'onBadgeButtonClick'
|
|
|
|
} );
|
2015-08-13 00:54:16 +00:00
|
|
|
|
|
|
|
this.$element
|
2015-09-17 00:05:52 +00:00
|
|
|
.prop( 'id', 'pt-notifications-' + this.type )
|
|
|
|
// The following classes can be used here:
|
|
|
|
// mw-echo-ui-notificationBadgeButtonPopupWidget-alert
|
|
|
|
// mw-echo-ui-notificationBadgeButtonPopupWidget-message
|
2015-08-13 00:54:16 +00:00
|
|
|
.addClass(
|
|
|
|
'mw-echo-ui-notificationBadgeButtonPopupWidget ' +
|
|
|
|
'mw-echo-ui-notificationBadgeButtonPopupWidget-' + this.type
|
2015-09-17 00:05:52 +00:00
|
|
|
)
|
|
|
|
.append(
|
|
|
|
this.badgeButton.$element,
|
|
|
|
this.popup.$element
|
2015-08-13 00:54:16 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Initialization */
|
|
|
|
|
2015-09-17 00:05:52 +00:00
|
|
|
OO.inheritClass( mw.echo.ui.NotificationBadgeWidget, OO.ui.Widget );
|
2015-08-13 00:54:16 +00:00
|
|
|
OO.mixinClass( mw.echo.ui.NotificationBadgeWidget, OO.ui.mixin.PendingElement );
|
|
|
|
|
2015-09-17 00:05:52 +00:00
|
|
|
/* Static properties */
|
|
|
|
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.static.tagName = 'li';
|
|
|
|
|
2015-09-17 20:47:23 +00:00
|
|
|
/* Events */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @event allRead
|
|
|
|
* All notifications were marked as read
|
|
|
|
*/
|
|
|
|
|
2016-01-15 22:11:33 +00:00
|
|
|
/**
|
|
|
|
* @event finishLoading
|
|
|
|
* Notifications have successfully finished being processed and are fully loaded
|
|
|
|
*/
|
|
|
|
|
2015-09-17 00:05:52 +00:00
|
|
|
/* Methods */
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Respond to badge button click
|
|
|
|
*/
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.prototype.onBadgeButtonClick = function () {
|
2016-01-15 22:11:33 +00:00
|
|
|
if ( !this.popup.isVisible() ) {
|
|
|
|
// Force a new API request for notifications
|
|
|
|
this.notificationsModel.fetchNotifications( true );
|
|
|
|
}
|
|
|
|
|
2015-09-30 00:02:48 +00:00
|
|
|
this.popup.toggle();
|
2015-09-17 00:05:52 +00:00
|
|
|
};
|
|
|
|
|
2015-09-03 21:24:03 +00:00
|
|
|
/**
|
|
|
|
* Update the badge icon with the read/unread versions if they exist.
|
|
|
|
*
|
|
|
|
* @param {boolean} hasUnseen Widget has unseen notifications
|
|
|
|
*/
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.prototype.updateIcon = function ( hasUnseen ) {
|
|
|
|
var icon = typeof this.badgeIcon === 'string' ?
|
|
|
|
this.badgeIcon :
|
|
|
|
this.badgeIcon[ hasUnseen ? 'unseen' : 'seen' ];
|
2015-09-17 00:05:52 +00:00
|
|
|
|
|
|
|
this.badgeButton.setIcon( icon );
|
2015-09-03 21:24:03 +00:00
|
|
|
this.popupHeadIcon.setIcon( icon );
|
|
|
|
};
|
|
|
|
|
2015-08-13 00:54:16 +00:00
|
|
|
/**
|
|
|
|
* Update the badge state and label based on changes to the model
|
|
|
|
*/
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.prototype.updateBadge = function () {
|
|
|
|
var unseenCount = this.notificationsModel.getUnseenCount(),
|
2015-10-16 23:18:25 +00:00
|
|
|
unreadCount = this.notificationsModel.getUnreadCount(),
|
2016-02-18 22:36:58 +00:00
|
|
|
nonBundledUnreadCount = this.notificationsModel.getNonbundledUnreadCount(),
|
|
|
|
wgEchoMaxNotificationCount,
|
|
|
|
badgeLabel;
|
2015-08-13 00:54:16 +00:00
|
|
|
|
|
|
|
// Update numbers and seen/unseen state
|
2015-09-23 00:04:14 +00:00
|
|
|
// If the popup is open, only allow a "demotion" of the badge
|
|
|
|
// to grey; ignore change of color to 'unseen'
|
|
|
|
if ( this.popup.isVisible() ) {
|
|
|
|
if ( !unseenCount ) {
|
|
|
|
this.badgeButton.setFlags( { unseen: false } );
|
|
|
|
this.updateIcon( false );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.badgeButton.setFlags( { unseen: !!unseenCount } );
|
|
|
|
this.updateIcon( !!unseenCount );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update badge count
|
|
|
|
if ( !this.markReadWhenSeen || !this.popup.isVisible() || unreadCount < this.currentUnreadCountInBadge ) {
|
2016-02-18 22:36:58 +00:00
|
|
|
wgEchoMaxNotificationCount = mw.config.get( 'wgEchoMaxNotificationCount' );
|
|
|
|
if ( unreadCount > wgEchoMaxNotificationCount ) {
|
|
|
|
badgeLabel = mw.message( 'echo-notification-count', wgEchoMaxNotificationCount ).text();
|
|
|
|
} else {
|
|
|
|
badgeLabel = mw.language.convertNumber( unreadCount );
|
|
|
|
}
|
|
|
|
this.badgeButton.setLabel( badgeLabel );
|
2015-09-23 00:04:14 +00:00
|
|
|
}
|
2015-09-08 20:46:57 +00:00
|
|
|
|
|
|
|
// Check if we need to display the 'mark all unread' button
|
2015-10-16 23:18:25 +00:00
|
|
|
this.markAllReadButton.toggle( !this.markReadWhenSeen && nonBundledUnreadCount > 0 );
|
2015-09-23 00:04:14 +00:00
|
|
|
this.currentUnreadCountInBadge = unreadCount;
|
2015-08-13 00:54:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Respond to 'mark all as read' button click
|
|
|
|
*/
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.prototype.onMarkAllReadButtonClick = function () {
|
|
|
|
this.notificationsModel.markAllRead();
|
|
|
|
};
|
|
|
|
|
2015-09-15 06:13:51 +00:00
|
|
|
/**
|
|
|
|
* Extend the response to button click so we can also update the notification list.
|
2016-01-15 22:11:33 +00:00
|
|
|
* @fires finishLoading
|
2015-09-15 06:13:51 +00:00
|
|
|
*/
|
|
|
|
mw.echo.ui.NotificationBadgeWidget.prototype.onPopupToggle = function ( isVisible ) {
|
2015-09-24 21:23:23 +00:00
|
|
|
var widget = this;
|
|
|
|
|
2016-01-15 22:11:33 +00:00
|
|
|
if ( this.promiseRunning ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-09-15 06:13:51 +00:00
|
|
|
if ( !isVisible ) {
|
2015-09-23 00:15:28 +00:00
|
|
|
// If the popup is closing, remove "initiallyUnseen" and leave
|
|
|
|
this.notificationsWidget.resetNotificationItems();
|
2015-09-15 06:13:51 +00:00
|
|
|
return;
|
2015-08-13 00:54:16 +00:00
|
|
|
}
|
2015-09-15 06:13:51 +00:00
|
|
|
|
|
|
|
// Log the click event
|
|
|
|
mw.echo.logger.logInteraction(
|
|
|
|
'ui-badge-link-click',
|
2015-10-08 22:20:31 +00:00
|
|
|
mw.echo.Logger.static.context.badge,
|
2015-09-15 06:13:51 +00:00
|
|
|
null,
|
|
|
|
this.type
|
|
|
|
);
|
|
|
|
|
|
|
|
if ( this.hasRunFirstTime ) {
|
|
|
|
// 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();
|
|
|
|
}
|
2016-01-15 22:11:33 +00:00
|
|
|
|
|
|
|
this.pushPending();
|
|
|
|
this.markAllReadButton.toggle( false );
|
|
|
|
this.promiseRunning = true;
|
2015-09-16 20:42:17 +00:00
|
|
|
// Always populate on popup open. The model and widget should handle
|
|
|
|
// the case where the promise is already underway.
|
2016-01-15 22:11:33 +00:00
|
|
|
this.notificationsModel.fetchNotifications()
|
2015-09-24 21:23:23 +00:00
|
|
|
.then( function () {
|
2015-10-06 21:34:51 +00:00
|
|
|
var i,
|
|
|
|
items = widget.notificationsWidget.getItems();
|
|
|
|
|
2015-09-24 21:23:23 +00:00
|
|
|
if ( widget.popup.isVisible() ) {
|
2016-01-15 22:11:33 +00:00
|
|
|
widget.popup.clip();
|
|
|
|
|
2015-09-24 21:23:23 +00:00
|
|
|
// Update seen time
|
|
|
|
widget.notificationsModel.updateSeenTime();
|
2015-09-30 18:42:53 +00:00
|
|
|
// Mark notifications as 'read' if markReadWhenSeen is set to true
|
|
|
|
if ( widget.markReadWhenSeen ) {
|
2015-12-28 23:12:37 +00:00
|
|
|
widget.notificationsModel.autoMarkAllRead();
|
2015-09-30 18:42:53 +00:00
|
|
|
}
|
2015-10-06 21:34:51 +00:00
|
|
|
|
|
|
|
// Log impressions
|
2016-01-15 22:11:33 +00:00
|
|
|
// TODO: Only log the impressions of notifications that
|
|
|
|
// are actually visible
|
2015-10-06 21:34:51 +00:00
|
|
|
for ( i = 0; i < items.length; i++ ) {
|
2015-10-07 15:20:35 +00:00
|
|
|
if ( items[ i ].getModel ) {
|
|
|
|
mw.echo.logger.logInteraction(
|
|
|
|
mw.echo.Logger.static.actions.notificationImpression,
|
2015-10-08 22:20:31 +00:00
|
|
|
mw.echo.Logger.static.context.popup,
|
2015-10-07 15:20:35 +00:00
|
|
|
items[ i ].getModel().getId(),
|
|
|
|
items[ i ].getModel().getCategory()
|
|
|
|
);
|
|
|
|
}
|
2015-10-06 21:34:51 +00:00
|
|
|
}
|
2015-09-24 21:23:23 +00:00
|
|
|
}
|
2016-01-15 22:11:33 +00:00
|
|
|
|
|
|
|
widget.emit( 'finishLoading' );
|
|
|
|
} )
|
|
|
|
.always( function () {
|
|
|
|
// Pop pending
|
|
|
|
widget.popPending();
|
|
|
|
widget.promiseRunning = false;
|
2015-09-24 21:23:23 +00:00
|
|
|
} );
|
2015-09-16 20:42:17 +00:00
|
|
|
this.hasRunFirstTime = true;
|
2015-08-13 00:54:16 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 );
|