jenkins-bot has submitted this change and it was merged.
Change subject: Add a SeenTimeModel to handle seenTime in sources
......................................................................
Add a SeenTimeModel to handle seenTime in sources
This organizes the operation of seenTime so we can store and
follow up on it based on different sources, as well as update
it correctly remotely when needed.
Change-Id: I629ecfc84999be998b45c9c7adb00ea7e3e51742
---
M Resources.php
M modules/api/mw.echo.api.APIHandler.js
M modules/api/mw.echo.api.EchoApi.js
M modules/api/mw.echo.api.LocalAPIHandler.js
M modules/controller/mw.echo.Controller.js
M modules/model/mw.echo.dm.CrossWikiNotificationItem.js
M modules/model/mw.echo.dm.ModelManager.js
A modules/model/mw.echo.dm.SeenTimeModel.js
M modules/ui/mw.echo.ui.NotificationBadgeWidget.js
M modules/ui/mw.echo.ui.NotificationsInboxWidget.js
10 files changed, 240 insertions(+), 76 deletions(-)
Approvals:
Sbisson: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Resources.php b/Resources.php
index 42a5c72..c4c693b 100644
--- a/Resources.php
+++ b/Resources.php
@@ -177,6 +177,7 @@
'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',
diff --git a/modules/api/mw.echo.api.APIHandler.js
b/modules/api/mw.echo.api.APIHandler.js
index d802287..7ff1b8d 100644
--- a/modules/api/mw.echo.api.APIHandler.js
+++ b/modules/api/mw.echo.api.APIHandler.js
@@ -156,7 +156,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;
diff --git a/modules/api/mw.echo.api.EchoApi.js
b/modules/api/mw.echo.api.EchoApi.js
index 64baabd..d4c31f0 100644
--- a/modules/api/mw.echo.api.EchoApi.js
+++ b/modules/api/mw.echo.api.EchoApi.js
@@ -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 );
};
diff --git a/modules/api/mw.echo.api.LocalAPIHandler.js
b/modules/api/mw.echo.api.LocalAPIHandler.js
index eaa4260..dc183b0 100644
--- a/modules/api/mw.echo.api.LocalAPIHandler.js
+++ b/modules/api/mw.echo.api.LocalAPIHandler.js
@@ -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;
diff --git a/modules/controller/mw.echo.Controller.js
b/modules/controller/mw.echo.Controller.js
index f51091b..7b1e26c 100644
--- a/modules/controller/mw.echo.Controller.js
+++ b/modules/controller/mw.echo.Controller.js
@@ -575,34 +575,42 @@
};
/**
- * 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
*
* @return {string[]} Notification types
diff --git a/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
b/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
index efe131f..fc908ba 100644
--- a/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
+++ b/modules/model/mw.echo.dm.CrossWikiNotificationItem.js
@@ -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();
diff --git a/modules/model/mw.echo.dm.ModelManager.js
b/modules/model/mw.echo.dm.ModelManager.js
index f119847..49c0f76 100644
--- a/modules/model/mw.echo.dm.ModelManager.js
+++ b/modules/model/mw.echo.dm.ModelManager.js
@@ -32,11 +32,12 @@
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();
@@ -44,8 +45,8 @@
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
*/
@@ -80,6 +83,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
@@ -250,6 +267,27 @@
};
/**
+ * 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
*
* @return {boolean} Local model has unseen notifications.
@@ -268,51 +306,13 @@
};
/**
- * 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 );
diff --git a/modules/model/mw.echo.dm.SeenTimeModel.js
b/modules/model/mw.echo.dm.SeenTimeModel.js
new file mode 100644
index 0000000..3497d0c
--- /dev/null
+++ b/modules/model/mw.echo.dm.SeenTimeModel.js
@@ -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 );
diff --git a/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
b/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
index ee921d9..9cb55ee 100644
--- a/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
+++ b/modules/ui/mw.echo.ui.NotificationBadgeWidget.js
@@ -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, {
@@ -273,11 +273,20 @@
};
/**
+ * 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();
}
diff --git a/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
b/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
index 44e32bc..7107512 100644
--- a/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
+++ b/modules/ui/mw.echo.ui.NotificationsInboxWidget.js
@@ -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 )
+ );
};
/**
--
To view, visit https://gerrit.wikimedia.org/r/298105
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I629ecfc84999be998b45c9c7adb00ea7e3e51742
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
Gerrit-Reviewer: Mooeypoo <[email protected]>
Gerrit-Reviewer: Sbisson <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits