mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-28 17:50:39 +00:00
9c83e14c59
Change-Id: Id1581858e59ea5f49804b6cf753b0c2860fb9855
255 lines
7.2 KiB
JavaScript
255 lines
7.2 KiB
JavaScript
( function ( mw, $ ) {
|
|
/**
|
|
* Echo notification group item model
|
|
*
|
|
* @class
|
|
* @extends mw.echo.dm.NotificationItem
|
|
* @mixins mw.echo.dm.SortedList
|
|
*
|
|
* @constructor
|
|
* @param {mw.echo.api.EchoApi} api Echo API
|
|
* @param {Object[]} sources An array of objects defining the sources
|
|
* of its item's sub-items.
|
|
* @param {number} id Notification id,
|
|
* @param {Object} [config] Configuration object
|
|
* @cfg {boolean} [removeReadNotifications=false] Completely remove notifications that are
|
|
* marked as read.
|
|
* @cfg {number} [count=0] The number of items this group contains. This is used for both the
|
|
* 'expand' label and also to potentially update the badge counters for local bundles.
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem = function mwEchoDmNotificationGroupItem( api, sources, id, config ) {
|
|
var source, item,
|
|
items = [];
|
|
|
|
config = config || {};
|
|
|
|
// Parent
|
|
mw.echo.dm.NotificationGroupItem.parent.call( this, id, config );
|
|
|
|
// Mixin constructor
|
|
mw.echo.dm.SortedList.call( this );
|
|
this.setSortingCallback( function ( a, b ) {
|
|
var diff;
|
|
// Reverse sorting
|
|
diff = Number( b.getTimestamp() ) - Number( a.getTimestamp() );
|
|
if ( diff !== 0 ) {
|
|
return diff;
|
|
}
|
|
|
|
// Fallback on IDs
|
|
return b.getId() - a.getId();
|
|
} );
|
|
this.aggregate( {
|
|
empty: 'groupEmpty',
|
|
itemRead: 'groupItemRead'
|
|
} );
|
|
this.connect( this, {
|
|
groupEmpty: 'onGroupEmpty'
|
|
} );
|
|
this.removeReadNotifications = !!config.removeReadNotifications;
|
|
|
|
this.sources = sources;
|
|
this.api = api;
|
|
this.notifModels = {};
|
|
this.anticipatedCount = config.count || 0;
|
|
|
|
// Create notification models for each source
|
|
for ( source in this.sources ) {
|
|
// Create a notifications model
|
|
item = new mw.echo.dm.NotificationsModel(
|
|
this.api,
|
|
{
|
|
type: this.getType(),
|
|
source: source,
|
|
foreign: this.isForeign(),
|
|
title: this.sources[ source ].title,
|
|
removeReadNotifications: this.removeReadNotifications
|
|
}
|
|
);
|
|
items.push( item );
|
|
this.notifModels[ source ] = item;
|
|
}
|
|
|
|
this.addItems( items );
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
OO.inheritClass( mw.echo.dm.NotificationGroupItem, mw.echo.dm.NotificationItem );
|
|
OO.mixinClass( mw.echo.dm.NotificationGroupItem, mw.echo.dm.SortedList );
|
|
|
|
/* Events */
|
|
|
|
/**
|
|
* The group is empty
|
|
*
|
|
* @event empty
|
|
*/
|
|
|
|
/**
|
|
* The number of item read in a group changed
|
|
*
|
|
* @event groupItemRead
|
|
*/
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Respond to notification model being empty
|
|
*
|
|
* @param {mw.echo.dm.NotificationsModel} notifModel Notifications model
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.onGroupEmpty = function ( notifModel ) {
|
|
if ( this.removeReadNotifications ) {
|
|
// This means the model is now empty. We should remove it as a group completely
|
|
this.removeItems( [ notifModel ] );
|
|
}
|
|
|
|
if ( this.isEmpty() ) {
|
|
this.emit( 'empty' );
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fetch items from each of the sources
|
|
*
|
|
* @return {jQuery.Promise} Promise that is resolved when all items are fetched
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.fetchAllNotificationsInGroups = function () {
|
|
var notifModel,
|
|
model = this,
|
|
fetchPromises = [],
|
|
sourceKeys = Object.keys( this.sources );
|
|
|
|
return this.api.fetchNotificationGroups( sourceKeys, this.getType() )
|
|
.then( function () {
|
|
var i;
|
|
|
|
for ( i = 0; i < sourceKeys.length; i++ ) {
|
|
notifModel = model.getItemById( sourceKeys[ i ] );
|
|
if ( notifModel ) {
|
|
fetchPromises.push( notifModel.fetchNotifications() );
|
|
}
|
|
}
|
|
|
|
// Wait for all fetch processes to finish before we resolve this promise
|
|
return mw.echo.api.NetworkHandler.static.waitForAllPromises( fetchPromises );
|
|
} )
|
|
.then( function () {
|
|
model.anticipatedCount = null;
|
|
} );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.toggleRead = function ( read ) {
|
|
var i, promise,
|
|
notifModels = this.getItems();
|
|
|
|
read = read !== undefined ? read : !this.read;
|
|
|
|
if ( this.read !== read ) {
|
|
for ( i = 0; i < notifModels.length; i++ ) {
|
|
// Verify that we have items in the models. The models may still
|
|
// be empty in the case where the group was marked as read before
|
|
// it was expanded and the notifications were fetched.
|
|
// NOTE: Local groups should never be empty, as we are
|
|
// getting all items without the need to query the API. Even so,
|
|
// this should happen to any notification that is empty to make
|
|
// sure that we are only marking the correct items as read and not
|
|
// all items indiscriminently.
|
|
if ( notifModels[ i ].isEmpty() ) {
|
|
// Fetch the notifications so we know what to mark as read
|
|
promise = notifModels[ i ].fetchNotifications();
|
|
} else {
|
|
// Create a fake resolved promise for models that already
|
|
// have items in them
|
|
promise = $.Deferred().resolve( notifModels[ i ].getAllItemIds() );
|
|
}
|
|
|
|
// For each of those, mark items as read in the UI and API
|
|
// Note that the promise for the notification models that
|
|
// were already full will resolve immediately, and hence be
|
|
// synchronous.
|
|
/*jshint loopfunc:true */
|
|
promise
|
|
.then( ( function ( model ) {
|
|
return function ( idArray ) {
|
|
// Mark sub items as read in the UI
|
|
model.markAllRead( model.getSource(), model.getType() );
|
|
// Mark all existing items as read in the API
|
|
model.markItemsReadInApi( idArray );
|
|
};
|
|
} )( notifModels[ i ] ) );
|
|
}
|
|
}
|
|
|
|
// Parent method
|
|
// Note: The parent method will mark this item as read, synchronously.
|
|
// In cases where the notification is external and empty, we are fetching
|
|
// the items (above) asynchronously, but the process of visually tagging
|
|
// the entire group as read in the UI should not wait for that API request
|
|
// to finish. Despite the async methods above, this is synchronous by design.
|
|
mw.echo.dm.NotificationGroupItem.parent.prototype.toggleRead.call( this, read );
|
|
};
|
|
|
|
/**
|
|
* @inheritdoc
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.toggleSeen = function ( seen ) {
|
|
var i,
|
|
notifModels = this.getItems();
|
|
|
|
seen = seen !== undefined ? seen : !this.seen;
|
|
|
|
if ( this.seen !== seen ) {
|
|
// Mark sub items as seen
|
|
for ( i = 0; i < notifModels.length; i++ ) {
|
|
notifModels[ i ].updateSeenTime();
|
|
}
|
|
}
|
|
|
|
// Parent method
|
|
mw.echo.dm.NotificationGroupItem.parent.prototype.toggleSeen.call( this, seen );
|
|
};
|
|
|
|
/**
|
|
* Get the anticipated count of items in this group item,
|
|
* or actual count if is has been loaded.
|
|
*
|
|
* @return {number} count
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.getCount = function () {
|
|
var sum;
|
|
if ( this.anticipatedCount !== null ) {
|
|
return this.anticipatedCount;
|
|
}
|
|
sum = 0;
|
|
this.getItems().forEach( function ( notificationsModel ) {
|
|
sum += notificationsModel.getUnreadCount();
|
|
} );
|
|
return sum;
|
|
};
|
|
|
|
/**
|
|
* Get the array of sources for this group
|
|
*
|
|
* @return {string[]} Sources
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.getSources = function () {
|
|
return this.sources;
|
|
};
|
|
|
|
/**
|
|
* Get all the sub-notification models for this group
|
|
*
|
|
* @return {Object} A keyed object containing mw.echo.dm.NotificationModel
|
|
* objects keyed by their source name.
|
|
*/
|
|
mw.echo.dm.NotificationGroupItem.prototype.getSubModels = function () {
|
|
return this.notifModels;
|
|
};
|
|
|
|
} )( mediaWiki, jQuery );
|