mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-12-01 10:56:44 +00:00
3093c7a69f
The "hover" pseudo-event was deprecated in jQuery 1.8 and removed in 1.9. This is logged as "JQMIGRATE: 'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'". This fix switches to using the .hover() method which appears to be the original intent as two functions have been used which .hover() supports, whereas .on() only accepts one handler. Now the class change works as expected. Change-Id: Ib28801293b72f8f344455b5f308876d185abc8bd
429 lines
12 KiB
JavaScript
429 lines
12 KiB
JavaScript
/*global window:false */
|
|
( function ( $, mw ) {
|
|
'use strict';
|
|
|
|
// backwards compatibility <= MW 1.21
|
|
var getUrl = mw.util.getUrl || mw.util.wikiGetlink,
|
|
useLang = mw.config.get( 'wgUserLanguage' );
|
|
|
|
function EchoOverlay( apiResultNotifications ) {
|
|
this.api = mw.echo.overlay.api;
|
|
// set internal properties
|
|
this.tabs = [];
|
|
this._buildOverlay( apiResultNotifications );
|
|
}
|
|
|
|
function EchoOverlayTab( options, notifications ) {
|
|
this.api = mw.echo.overlay.api;
|
|
this.markOnView = options.markOnView;
|
|
this.markAsReadCallback = options.markAsReadCallback;
|
|
this.name = options.name;
|
|
this.unread = [];
|
|
this._totalUnread = notifications[this.name].rawcount;
|
|
this._buildList( notifications[this.name] );
|
|
}
|
|
|
|
EchoOverlayTab.prototype = {
|
|
/* @var integer totalUnread the number of unread notifications in this tab.
|
|
including those that are not visible. */
|
|
/**
|
|
* Return a list of unread and shown ids
|
|
* @method
|
|
* @param integer id of a notification to mark as read
|
|
* @return jQuery.Deferred
|
|
*/
|
|
getUnreadIds: function() {
|
|
return this.unread;
|
|
},
|
|
/**
|
|
* Get a count the number of all unread notifications of this type
|
|
* @method
|
|
* @param integer id of a notification to mark as read
|
|
* @return integer
|
|
*/
|
|
getNumberUnread: function() {
|
|
return this._totalUnread;
|
|
},
|
|
/**
|
|
* Mark all existing notifications as read
|
|
* @method
|
|
* @param integer id of a notification to mark as read
|
|
* @return jQuery.Deferred
|
|
*/
|
|
markAsRead: function( id ) {
|
|
var self = this, data;
|
|
// only need to mark as read if there is unread item
|
|
if ( this.unread.length ) {
|
|
data = {
|
|
action: 'echomarkread',
|
|
token: mw.user.tokens.get( 'editToken' ),
|
|
uselang: useLang
|
|
};
|
|
if ( id ) {
|
|
// If id is given mark that as read otherwise use all unread messages
|
|
data.list = id;
|
|
} else {
|
|
data.sections = this.name;
|
|
}
|
|
|
|
return this.api.post( data ).then( function ( result ) {
|
|
return result.query.echomarkread;
|
|
} ).done( function( result ) {
|
|
// reset internal state of unread messages
|
|
if ( id ) {
|
|
if ( self.unread.indexOf( id ) > -1 ) {
|
|
self.unread.splice( self.unread.indexOf( id ), 1 );
|
|
}
|
|
} else {
|
|
self.unread = [];
|
|
}
|
|
// update the count
|
|
self._totalUnread = result[self.name].rawcount;
|
|
self.markAsReadCallback( result, id );
|
|
} );
|
|
} else {
|
|
return new $.Deferred();
|
|
}
|
|
},
|
|
/**
|
|
* Builds an Echo notifications list
|
|
* @method
|
|
* @param string tabName the tab
|
|
* @param object notifications as returned by the api of notification items
|
|
* @return jQuery element
|
|
*/
|
|
_buildList: function( notifications ) {
|
|
var self = this,
|
|
$container = $( '<div class="mw-echo-notifications">' )
|
|
.data( 'tab', this )
|
|
.css( 'max-height', $( window ).height() - 134 ),
|
|
$ul = $( '<ul>' ).appendTo( $container );
|
|
|
|
$.each( notifications.index, function ( index, id ) {
|
|
var $wrapper,
|
|
data = notifications.list[id],
|
|
$li = $( '<li>' )
|
|
.data( 'details', data )
|
|
.data( 'id', id )
|
|
.attr( {
|
|
'data-notification-category': data.category,
|
|
'data-notification-event': data.id,
|
|
'data-notification-type': data.type
|
|
} )
|
|
.addClass( 'mw-echo-notification' );
|
|
|
|
if ( !data['*'] ) {
|
|
return;
|
|
}
|
|
|
|
$li.append( data['*'] )
|
|
.appendTo( $ul );
|
|
|
|
if ( !data.read ) {
|
|
$li.addClass( 'mw-echo-unread' );
|
|
self.unread.push( id );
|
|
if ( !self.markOnView ) {
|
|
$( '<button class="mw-ui-button mw-ui-quiet">×</button>' )
|
|
.on( 'click', function( ev ) {
|
|
ev.preventDefault();
|
|
self.markAsRead( $( this ).closest( 'li' ).data( 'notification-event' ) );
|
|
} ).appendTo( $li );
|
|
}
|
|
}
|
|
|
|
// Grey links in the notification title and footer (except on hover)
|
|
$li.find( '.mw-echo-title a, .mw-echo-notification-footer a' )
|
|
.addClass( 'mw-echo-grey-link' );
|
|
$li.hover(
|
|
function() {
|
|
$( this ).find( '.mw-echo-title a' ).removeClass( 'mw-echo-grey-link' );
|
|
},
|
|
function() {
|
|
$( this ).find( '.mw-echo-title a' ).addClass( 'mw-echo-grey-link' );
|
|
}
|
|
);
|
|
// If there is a primary link, make the entire notification clickable.
|
|
// Yes, it is possible to nest <a> tags via DOM manipulation,
|
|
// and it works like one would expect.
|
|
if ( $li.find( '.mw-echo-notification-primary-link' ).length ) {
|
|
$wrapper = $( '<a>' )
|
|
.addClass( 'mw-echo-notification-wrapper' )
|
|
.attr( 'href', $li.find( '.mw-echo-notification-primary-link' ).attr( 'href' ) )
|
|
.click( function() {
|
|
if ( mw.echo.clickThroughEnabled ) {
|
|
// Log the clickthrough
|
|
mw.echo.logInteraction( 'notification-link-click', 'flyout', +data.id, data.type );
|
|
}
|
|
} );
|
|
} else {
|
|
$wrapper = $('<div>').addClass( 'mw-echo-notification-wrapper' );
|
|
}
|
|
|
|
$li.wrapInner( $wrapper );
|
|
|
|
mw.echo.setupNotificationLogging( $li, 'flyout' );
|
|
|
|
// Set up each individual notification with a close box and dismiss
|
|
// interface if it is dismissable.
|
|
if ( $li.find( '.mw-echo-dismiss' ).length ) {
|
|
mw.echo.setUpDismissability( $li );
|
|
}
|
|
} );
|
|
|
|
if ( !this.markOnView && this.unread.length ) {
|
|
$( '<button class="mw-ui-button mw-ui-quiet">' )
|
|
.text( mw.msg( 'echo-mark-all-as-read' ) )
|
|
.on( 'click', function() {
|
|
var $btn = $( this );
|
|
self.markAsRead().done( function() {
|
|
self.$el.find( '.mw-echo-unread' ).removeClass( 'mw-echo-unread' );
|
|
$btn.remove();
|
|
} );
|
|
} )
|
|
.prependTo( $container );
|
|
}
|
|
this.$el = $container;
|
|
}
|
|
};
|
|
|
|
EchoOverlay.prototype = {
|
|
/**
|
|
* @var array a list of EchoOverlayTabs
|
|
*/
|
|
tabs: [],
|
|
/**
|
|
* @var object current count status of notification types
|
|
*/
|
|
notificationCount: {
|
|
/* @var integer length of all notifications (both unread and read) that will be visible in the overlay */
|
|
all: 0,
|
|
/* @var string a string representation the current number of unread notifications (1, 99, 99+) */
|
|
unread: '0',
|
|
/* @var integer the total number of all unread notifications including those not in the overlay */
|
|
unreadRaw: 0
|
|
},
|
|
|
|
/**
|
|
* FIXME: This should be pulled out of EchoOverlay and use an EventEmitter.
|
|
* @param newCount formatted count
|
|
* @param rawCount unformatted count
|
|
*/
|
|
updateBadgeCount: function ( newCount, rawCount ) {
|
|
var $badge = mw.echo.getBadge();
|
|
$badge.text( newCount );
|
|
|
|
if ( rawCount !== '0' && rawCount !== 0 ) {
|
|
$badge.addClass( 'mw-echo-unread-notifications' );
|
|
} else {
|
|
$badge.removeClass( 'mw-echo-unread-notifications' );
|
|
}
|
|
this.notificationCount.unread = newCount;
|
|
this.notificationCount.unreadRaw = rawCount;
|
|
},
|
|
|
|
configuration: mw.config.get( 'wgEchoOverlayConfiguration' ),
|
|
|
|
_getFooterElement: function() {
|
|
var $prefLink = $( '#pt-preferences a' ),
|
|
links = [
|
|
{ url: getUrl( 'Special:Notifications' ), text: mw.msg( 'echo-overlay-link' ),
|
|
className: 'mw-echo-icon-all' },
|
|
{ url: $prefLink.attr( 'href' ) + '#mw-prefsection-echo', text: $prefLink.text(),
|
|
className: 'mw-echo-icon-cog' }
|
|
],
|
|
$overlayFooter = $( '<div class="mw-echo-overlay-footer">' );
|
|
|
|
$.each( links, function( i, link ) {
|
|
$( '<a class="mw-echo-grey-link">' )
|
|
.attr( 'href', link.url )
|
|
.addClass( link.className )
|
|
.text( link.text )
|
|
.appendTo( $overlayFooter );
|
|
} );
|
|
// add link to notifications archive
|
|
$overlayFooter.find( 'a' ).hover(
|
|
function() {
|
|
$( this ).removeClass( 'mw-echo-grey-link' );
|
|
},
|
|
function() {
|
|
$( this ).addClass( 'mw-echo-grey-link' );
|
|
}
|
|
);
|
|
return $overlayFooter;
|
|
},
|
|
|
|
_showTabList: function( tab ) {
|
|
var $lists = this.$el.find( '.mw-echo-notifications' ).hide();
|
|
|
|
this._activeTab = tab;
|
|
$lists.each( function() {
|
|
if ( $( this ).data( 'tab' ).name === tab.name ) {
|
|
$( this ).show();
|
|
if ( tab.markOnView ) {
|
|
tab.markAsRead();
|
|
}
|
|
}
|
|
} );
|
|
},
|
|
|
|
|
|
_updateTitleElement: function() {
|
|
var $header;
|
|
$header = this.$el.find( '.mw-echo-overlay-title' );
|
|
this._getTitleElement().insertBefore( $header );
|
|
$header.remove();
|
|
},
|
|
|
|
_getTabsElement: function() {
|
|
var $li,
|
|
$ul = $( '<ul>' ), self = this;
|
|
|
|
$.each( this.tabs, function( i, echoTab ) {
|
|
var
|
|
tabName = self.tabs.length > 1 ? echoTab.name : ( echoTab.name + '-text-only' ),
|
|
// Messages that can be used here:
|
|
// * echo-notification-alert
|
|
// * echo-notification-message
|
|
// * echo-notification-alert-text-only
|
|
// * echo-notification-message-text-only
|
|
// @todo: Unread value is inaccurate. If a user has more than mw.echo.overlay.notificationLimit
|
|
// API change needed
|
|
label = mw.msg(
|
|
'echo-notification-' + tabName,
|
|
mw.language.convertNumber( echoTab.getNumberUnread() )
|
|
);
|
|
|
|
$li = $( '<li>' )
|
|
.appendTo( $ul );
|
|
|
|
$( '<a class="mw-ui-progressive">' )
|
|
.on( 'click', function() {
|
|
var $this = $( this );
|
|
$ul.find( 'a' ).removeClass( 'mw-ui-quiet' ).addClass( 'mw-ui-active' );
|
|
$this.addClass( 'mw-ui-quiet' ).removeClass( 'mw-ui-active');
|
|
self._showTabList( $this.data( 'tab' ) );
|
|
} )
|
|
.data( 'tab', echoTab )
|
|
.addClass( echoTab.name === self._activeTab.name ? 'mw-ui-quiet' : 'mw-ui-active' )
|
|
.text( label ).appendTo( $li );
|
|
} );
|
|
return $ul;
|
|
},
|
|
|
|
getUnreadCount: function() {
|
|
var count = 0;
|
|
$.each( this.tabs, function( i, tab ) {
|
|
count += tab.getNumberUnread();
|
|
} );
|
|
return count;
|
|
},
|
|
|
|
_getTitleElement: function() {
|
|
var $title = $( '<div>' ).addClass( 'mw-echo-overlay-title' )
|
|
.append( this._getTabsElement() );
|
|
return $title;
|
|
},
|
|
|
|
_buildOverlay: function ( notifications ) {
|
|
var tabs,
|
|
self = this,
|
|
options = {
|
|
markAsReadCallback: function( data, id ) {
|
|
self.updateBadgeCount( data.count, data.rawcount );
|
|
self._updateTitleElement();
|
|
if ( id ) {
|
|
self.$el.find( '[data-notification-event="' + id + '"]').removeClass( 'mw-echo-unread' )
|
|
.find( 'button' ).remove();
|
|
}
|
|
}
|
|
},
|
|
$overlay = $( '<div>' ).addClass( 'mw-echo-overlay' );
|
|
|
|
this.$el = $overlay;
|
|
|
|
if ( notifications.message.index.length ) {
|
|
tabs = [ { name: 'alert', markOnView: true }, { name: 'message' } ];
|
|
} else {
|
|
tabs = [ { name: 'alert', markOnView: true } ];
|
|
}
|
|
|
|
$.each( tabs, function( i, tabOptions ) {
|
|
var tab = new EchoOverlayTab( $.extend( tabOptions, options ), notifications );
|
|
self.$el.append( tab.$el );
|
|
self.tabs.push( tab );
|
|
self.notificationCount.all += notifications[tabOptions.name].index.length;
|
|
} );
|
|
|
|
if ( tabs.length === 1 ) {
|
|
// only one tab exists
|
|
this._activeTab = this.tabs[0];
|
|
} else if (
|
|
notifications.message.rawcount > 0 &&
|
|
notifications.alert.rawcount === 0
|
|
) {
|
|
// if there are new messages and no new alerts show the messages tab
|
|
this._activeTab = this.tabs[1];
|
|
} else {
|
|
// otherwise show the alerts tab
|
|
this._activeTab = this.tabs[0];
|
|
}
|
|
|
|
$overlay.prepend( this._getTitleElement() );
|
|
$overlay.append( this._getFooterElement() );
|
|
// Show the active tab.
|
|
this._showTabList( this._activeTab );
|
|
}
|
|
};
|
|
|
|
mw.echo.overlay = {
|
|
/**
|
|
* @var integer the maximum number of notifications to show in the overlay
|
|
*/
|
|
notificationLimit: 25,
|
|
/**
|
|
* @var mw.Api
|
|
*/
|
|
api: new mw.Api( { ajax: { cache: false } } ),
|
|
/**
|
|
* Create an Echo overlay
|
|
* @return jQuery.Deferred with new EchoOverlay passed in callback
|
|
*/
|
|
getNewOverlay: function() {
|
|
var apiData = {
|
|
action: 'query',
|
|
meta: 'notifications',
|
|
notsections: 'alert|message',
|
|
notgroupbysection: 1,
|
|
notmessageunreadfirst: 1,
|
|
notformat: 'flyout',
|
|
notlimit: this.notificationLimit,
|
|
notprop: 'index|list|count',
|
|
uselang: useLang
|
|
};
|
|
|
|
return this.api.get( apiData ).then( function ( result ) {
|
|
return new EchoOverlay( result.query.notifications );
|
|
} );
|
|
},
|
|
/**
|
|
* Builds an overlay element
|
|
* @method
|
|
* @param callback a callback which passes the newly created overlay as a parameter
|
|
*/
|
|
buildOverlay: function( callback ) {
|
|
this.getNewOverlay().done( function( overlay ) {
|
|
callback( overlay.$el );
|
|
} ).fail( function () {
|
|
window.location.href = $( '#pt-notifications a' ).attr( 'href' );
|
|
} );
|
|
},
|
|
removeOverlay: function () {
|
|
$( '.mw-echo-overlay' ).fadeOut( 'fast',
|
|
function () {
|
|
$( this ).remove();
|
|
}
|
|
);
|
|
}
|
|
};
|
|
} )( jQuery, mediaWiki );
|