Merge "Add a SeenTimeModel to handle seenTime in sources"

This commit is contained in:
jenkins-bot 2016-07-18 13:01:35 +00:00 committed by Gerrit Code Review
commit d27cab59d6
10 changed files with 240 additions and 76 deletions

View file

@ -177,6 +177,7 @@ $wgResourceModules += array(
'model/mw.echo.dm.SourcePagesModel.js',
'model/mw.echo.dm.PaginationModel.js',
'model/mw.echo.dm.FiltersModel.js',
'model/mw.echo.dm.SeenTimeModel.js',
'model/mw.echo.dm.ModelManager.js',
'model/mw.echo.dm.SortedList.js',
'model/mw.echo.dm.NotificationItem.js',

View file

@ -157,7 +157,7 @@
/**
* Update the seen timestamp
*
* @param {string} [type] Notification type 'message', 'alert' or 'all'.
* @param {string|string[]} [types] Notification type 'message', 'alert' or [ 'message', 'alert' ].
* @return {jQuery.Promise} A promise that resolves with the seen timestamp
*/
mw.echo.api.APIHandler.prototype.updateSeenTime = null;

View file

@ -274,11 +274,13 @@
/**
* Update the seenTime property for the given type and source.
*
* @param {string} source Notification source
* @param {string} type Notification type
* @param {string} [type='alert,message'] Notification type
* @param {string} [source='local'] Notification source
* @return {jQuery.Promise} A promise that is resolved when the operation is complete.
*/
mw.echo.api.EchoApi.prototype.updateSeenTime = function ( source, type ) {
mw.echo.api.EchoApi.prototype.updateSeenTime = function ( type, source ) {
source = source || 'local';
type = type || [ 'alert', 'message' ];
return this.network.getApiHandler( source ).updateSeenTime( type );
};

View file

@ -40,9 +40,11 @@
* @inheritdoc
*/
mw.echo.api.LocalAPIHandler.prototype.updateSeenTime = function ( type ) {
type = Array.isArray( type ) ? type : [ type ];
return this.api.postWithToken( 'csrf', {
action: 'echomarkseen',
type: this.normalizedType[ type ]
type: type.length === 1 ? type[ 0 ] : 'all'
} )
.then( function ( data ) {
return data.query.echomarkseen.timestamp;

View file

@ -575,33 +575,41 @@
};
/**
* Update local seenTime for the given types
* Update seenTime for the given source
*
* @return {jQuery.Promise} A promise that is resolved when the
* seenTime was updated for all given types.
*/
mw.echo.Controller.prototype.updateSeenTime = function ( source ) {
var controller = this;
return this.api.updateSeenTime( this.getTypes(), source )
.then( function ( time ) {
controller.manager.getSeenTimeModel().setSeenTimeForSource( source, time );
} );
};
/**
* Update local seen time
*
* @return {jQuery.Promise} A promise that is resolved when the
* seenTime was updated for all given types.
*/
mw.echo.Controller.prototype.updateLocalSeenTime = function () {
var controller = this,
promises = [],
types = this.manager.getTypes();
types.forEach( function ( type ) {
promises.push( controller.api.updateSeenTime( 'local', type ) );
} );
return mw.echo.api.NetworkHandler.static.waitForAllPromises( promises )
.then( function ( promises ) {
var i;
// Update the seen time object
// The promises are in the same order as the types
// so we can use the same iterator for both
for ( i = 0; i < promises.length; i++ ) {
promises[ i ].done( controller.manager.updateSeenTimeObject.bind( controller.manager, types[ i ] ) );
}
} )
.then( controller.manager.setLocalModelItemsSeen.bind( controller.manager ) );
return this.updateSeenTime( 'local' );
};
/**
* Update seenTime for the currently selected source
*
* @return {jQuery.Promise} A promise that is resolved when the
* seenTime was updated for all given types.
*/
mw.echo.Controller.prototype.updateSeenTimeForCurrentSource = function () {
var currSource = this.manager.getFiltersModel().getSourcePagesModel().getCurrentSource();
return this.updateSeenTime( currSource );
};
/**
* Get the types associated with the controller and model
*

View file

@ -18,6 +18,7 @@
mw.echo.dm.CrossWikiNotificationItem.parent.call( this, id, config );
this.foreign = true;
this.source = null;
this.count = config.count || 0;
this.list = new mw.echo.dm.NotificationGroupsList();

View file

@ -32,20 +32,21 @@
OO.EventEmitter.call( this );
this.counter = counter;
// Keep types in an array
this.types = config.type || 'message';
this.types = Array.isArray( this.types ) ?
config.type : [ this.types ];
this.seenTimeModel = new mw.echo.dm.SeenTimeModel( { types: this.types } );
this.notificationModels = {};
this.paginationModel = new mw.echo.dm.PaginationModel();
this.filtersModel = new mw.echo.dm.FiltersModel( {
selectedSource: 'local'
} );
// Properties
this.seenTime = mw.config.get( 'wgEchoSeenTime' ) || {};
// Events
this.seenTimeModel.connect( this, { update: 'onSeenTimeUpdate' } );
};
OO.initClass( mw.echo.dm.ModelManager );
@ -69,6 +70,8 @@
/**
* @event seen
* @param {string} source Source where seenTime was updated
* @param {number} timestamp The new seen timestamp
*
* All local notifications are seen
*/
@ -81,6 +84,20 @@
/* Methods */
/**
* Respond to seen time change for a given source
*
* @param {string} source Source where seen time has changed
*/
mw.echo.dm.ModelManager.prototype.onSeenTimeUpdate = function ( source, timestamp ) {
var notifs = this.getNotificationsBySource( source );
notifs.forEach( function ( notification ) {
notification.toggleSeen( notification.isRead() || notification.getTimestamp() < timestamp );
} );
this.emit( 'seen', source, timestamp );
};
/**
* Get the notifications
*
@ -249,6 +266,27 @@
return model && model.hasUnseen();
};
/**
* Check if a model has any unseen notifications.
*
* @param {string} [source='local'] Model source
* @return {boolean} The given models has unseen notifications.
*/
mw.echo.dm.ModelManager.prototype.hasUnseenInSource = function ( source ) {
var i, modelNames;
source = source || 'local';
modelNames = this.getModelsBySource( source );
for ( i = 0; i < modelNames.length; i++ ) {
if ( this.getNotificationModel( modelNames[ i ] ).hasUnseen() ) {
return true;
}
}
return false;
};
/**
* Check if there are unseen notifications in any of the models
*
@ -267,52 +305,14 @@
return false;
};
/**
* Update the local version of seenTime object.
*
* @private
* @param {string|string[]} type Type of notifications; could be a single type
* or an array of types.
* @param {string} time Seen time
*/
mw.echo.dm.ModelManager.prototype.updateSeenTimeObject = function ( type, time ) {
var i,
types = Array.isArray( type ) ? type : [ type ];
for ( i = 0; i < types.length; i++ ) {
if ( this.seenTime.hasOwnProperty( types[ i ] ) ) {
this.seenTime[ types[ i ] ] = time;
}
}
};
/**
* Set items in the local model as seen
*
* @private
*/
mw.echo.dm.ModelManager.prototype.setLocalModelItemsSeen = function () {
this.getLocalNotifications().forEach( function ( item ) {
item.toggleSeen( true );
} );
this.emit( 'seen' );
};
/**
* Get the system seen time
*
* @param {string|string[]} [type] Notification types
* @param {string} [source='local'] Seen time source
* @return {string} Mediawiki seen timestamp in Mediawiki timestamp format
*/
mw.echo.dm.ModelManager.prototype.getSeenTime = function ( type ) {
var types = type || this.getTypes();
types = Array.isArray( types ) ? types : [ types ];
// If the type that is set is an array [ 'alert', 'message' ]
// then the seen time will be the same to both, and we can
// return the value of the arbitrary first one.
return this.seenTime[ types[ 0 ] ];
mw.echo.dm.ModelManager.prototype.getSeenTime = function ( source ) {
return this.getSeenTimeModel().getSeenTime( source );
};
/**
@ -343,15 +343,57 @@
* @return {mw.echo.dm.NotificationItem[]} all local notifications
*/
mw.echo.dm.ModelManager.prototype.getLocalNotifications = function () {
return this.getNotificationsBySource( 'local' );
};
/**
* Get all notifications that come from a given source
*
* @return {mw.echo.dm.NotificationItem[]} all notifications from that source
*/
mw.echo.dm.ModelManager.prototype.getNotificationsBySource = function ( source ) {
var notifications = [],
manager = this;
source = source || 'local';
Object.keys( this.getAllNotificationModels() ).forEach( function ( modelName ) {
var model = manager.getNotificationModel( modelName );
if ( !model.isForeign() ) {
if ( model.getSource() === source ) {
notifications = notifications.concat( model.getItems() );
}
} );
return notifications;
};
/**
* Get all models that have a specific source
*
* @param {string} [source='local'] Given source
* @return {string[]} All model IDs that use this source
*/
mw.echo.dm.ModelManager.prototype.getModelsBySource = function ( source ) {
var modelIds = [],
manager = this;
source = source || 'local';
Object.keys( this.getAllNotificationModels() ).forEach( function ( modelName ) {
var model = manager.getNotificationModel( modelName );
if ( model.getSource() === source ) {
modelIds.push( modelName );
}
} );
return modelIds;
};
/**
* Get the SeenTime model
*
* @return {mw.echo.dm.SeenTimeModel} SeenTime model
*/
mw.echo.dm.ModelManager.prototype.getSeenTimeModel = function () {
return this.seenTimeModel;
};
} )( mediaWiki, jQuery );

View file

@ -0,0 +1,91 @@
( function ( mw ) {
/**
* SeenTime model for Echo notifications
*
* @param {Object} [config] Configuration
* @cfg {string|string[]} [types='alert','message'] The types of notifications
* that this model handles
*/
mw.echo.dm.SeenTimeModel = function MwEchoSeenTimeModel( config ) {
config = config || {};
// Mixin constructor
OO.EventEmitter.call( this );
this.types = [ 'alert', 'message' ];
if ( config.types ) {
this.types = Array.isArray( config.types ) ? config.types : [ config.types ];
}
this.seenTime = {
local: mw.config.get( 'wgEchoSeenTime' ) || {}
};
};
/* Initialization */
OO.initClass( mw.echo.dm.SeenTimeModel );
OO.mixinClass( mw.echo.dm.SeenTimeModel, OO.EventEmitter );
/* Events */
/**
* @event update
* @param {string} source The source that updated its seenTime
* @param {number} time Seen time
*
* Seen time has been updated for the given source
*/
/* Methods */
/**
* Get the seenTime value for the source
*
* @param {string} source Source name
* @return {number} Seen time
*/
mw.echo.dm.SeenTimeModel.prototype.getSeenTime = function ( source ) {
source = source || 'local';
return ( this.seenTime[ source ] && this.seenTime[ source ][ this.getTypes()[ 0 ] ] ) || 0;
};
/**
* Set the seen time value for the source
*
* @private
* @param {string} [source='local'] Given source
* @fires update
*/
mw.echo.dm.SeenTimeModel.prototype.setSeenTimeForSource = function ( source, time ) {
var model = this,
hasChanged = false;
source = source || 'local';
this.seenTime[ source ] = this.seenTime[ source ] || {};
this.getTypes().forEach( function ( type ) {
if ( model.seenTime[ source ][ type ] !== time ) {
model.seenTime[ source ][ type ] = time;
hasChanged = true;
}
} );
if ( hasChanged ) {
this.emit( 'update', source, time );
}
};
/**
* Get the types associated with this model
*
* @private
* @return {string[]} Types for this model; an array of 'alert', 'message' or both.
*/
mw.echo.dm.SeenTimeModel.prototype.getTypes = function () {
return this.types;
};
} )( mediaWiki );

View file

@ -178,9 +178,9 @@
// Events
this.markAllReadButton.connect( this, { click: 'onMarkAllReadButtonClick' } );
this.manager.connect( this, {
update: 'updateBadge',
seen: [ 'updateBadgeSeenState', false ]
update: 'updateBadge'
} );
this.manager.getSeenTimeModel().connect( this, { update: 'onSeenTimeModelUpdate' } );
this.manager.getUnreadCounter().connect( this, { countChange: 'updateBadge' } );
this.popup.connect( this, { toggle: 'onPopupToggle' } );
this.badgeButton.connect( this, {
@ -272,12 +272,21 @@
}
};
/**
* Respond to SeenTime model update event
*/
mw.echo.ui.NotificationBadgeWidget.prototype.onSeenTimeModelUpdate = function () {
this.updateBadgeSeenState( this.manager.hasUnseenInSource( 'local' ) );
};
/**
* Update the badge style to match whether it contains unseen notifications.
*
* @param {boolean} hasUnseen There are unseen notifications
*/
mw.echo.ui.NotificationBadgeWidget.prototype.updateBadgeSeenState = function ( hasUnseen ) {
hasUnseen = hasUnseen === undefined ? this.manager.hasUnseenInSource( 'local' ) : !!hasUnseen;
this.badgeButton.setFlags( { unseen: !!hasUnseen } );
this.updateIcon( !!hasUnseen );
};
@ -359,7 +368,6 @@
function () {
if ( widget.popup.isVisible() ) {
widget.popup.clip();
// Update seen time
return widget.controller.updateLocalSeenTime();
}

View file

@ -184,7 +184,8 @@
* have been fetched.
*/
mw.echo.ui.NotificationsInboxWidget.prototype.populateNotifications = function ( direction ) {
var fetchPromise;
var fetchPromise,
widget = this;
if ( direction === 'prev' ) {
fetchPromise = this.controller.fetchPrevPageByDate();
@ -196,8 +197,16 @@
this.pushPending();
return fetchPromise
// Pop pending
.always( this.popPending.bind( this ) );
.then(
// Success
function () {
widget.popPending();
// Update seen time
widget.controller.updateSeenTimeForCurrentSource();
},
// Failure
this.popPending.bind( this )
);
};
/**