mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-23 23:44:53 +00:00
Merge "Add a SeenTimeModel to handle seenTime in sources"
This commit is contained in:
commit
d27cab59d6
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 );
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 );
|
||||
|
|
91
modules/model/mw.echo.dm.SeenTimeModel.js
Normal file
91
modules/model/mw.echo.dm.SeenTimeModel.js
Normal 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 );
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue