mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-11-12 09:26:05 +00:00
Merge "Echo API layer"
This commit is contained in:
commit
3fd75c41a4
|
@ -117,19 +117,14 @@ $wgResourceModules += array(
|
||||||
'viewmodel/mw.echo.dm.js',
|
'viewmodel/mw.echo.dm.js',
|
||||||
'viewmodel/mw.echo.dm.List.js',
|
'viewmodel/mw.echo.dm.List.js',
|
||||||
'viewmodel/mw.echo.dm.SortedList.js',
|
'viewmodel/mw.echo.dm.SortedList.js',
|
||||||
'viewmodel/handlers/mw.echo.dm.APIHandler.js',
|
|
||||||
'viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js',
|
|
||||||
'viewmodel/handlers/mw.echo.dm.ForeignAPIHandler.js',
|
|
||||||
'viewmodel/handlers/mw.echo.dm.NetworkHandler.js',
|
|
||||||
'viewmodel/mw.echo.dm.NotificationItem.js',
|
'viewmodel/mw.echo.dm.NotificationItem.js',
|
||||||
'viewmodel/mw.echo.dm.NotificationGroupItem.js',
|
'viewmodel/mw.echo.dm.NotificationGroupItem.js',
|
||||||
'viewmodel/mw.echo.dm.NotificationList.js',
|
'viewmodel/mw.echo.dm.NotificationList.js',
|
||||||
'viewmodel/mw.echo.dm.NotificationsModel.js',
|
'viewmodel/mw.echo.dm.NotificationsModel.js',
|
||||||
),
|
),
|
||||||
'dependencies' => array(
|
'dependencies' => array(
|
||||||
'mediawiki.api',
|
'oojs',
|
||||||
'mediawiki.ForeignApi',
|
'ext.echo.api',
|
||||||
'oojs'
|
|
||||||
),
|
),
|
||||||
'messages' => array(
|
'messages' => array(
|
||||||
'echo-api-failure',
|
'echo-api-failure',
|
||||||
|
@ -137,6 +132,22 @@ $wgResourceModules += array(
|
||||||
),
|
),
|
||||||
'targets' => array( 'desktop', 'mobile' ),
|
'targets' => array( 'desktop', 'mobile' ),
|
||||||
),
|
),
|
||||||
|
'ext.echo.api' => $echoResourceTemplate + array(
|
||||||
|
'scripts' => array(
|
||||||
|
'api/mw.echo.api.js',
|
||||||
|
'api/mw.echo.api.EchoApi.js',
|
||||||
|
'api/mw.echo.api.APIHandler.js',
|
||||||
|
'api/mw.echo.api.LocalAPIHandler.js',
|
||||||
|
'api/mw.echo.api.ForeignAPIHandler.js',
|
||||||
|
'api/mw.echo.api.NetworkHandler.js',
|
||||||
|
),
|
||||||
|
'dependencies' => array(
|
||||||
|
'mediawiki.api',
|
||||||
|
'mediawiki.ForeignApi',
|
||||||
|
'oojs'
|
||||||
|
),
|
||||||
|
'targets' => array( 'desktop', 'mobile' ),
|
||||||
|
),
|
||||||
'ext.echo.base' => array(
|
'ext.echo.base' => array(
|
||||||
// This is a dummy module for backwards compatibility.
|
// This is a dummy module for backwards compatibility.
|
||||||
// Most extensions that require ext.echo.base actually need
|
// Most extensions that require ext.echo.base actually need
|
||||||
|
@ -154,6 +165,7 @@ $wgResourceModules += array(
|
||||||
'scripts' => array(
|
'scripts' => array(
|
||||||
'ext.echo.init.js',
|
'ext.echo.init.js',
|
||||||
),
|
),
|
||||||
|
'dependencies' => array( 'ext.echo.api' ),
|
||||||
'targets' => array( 'desktop' ),
|
'targets' => array( 'desktop' ),
|
||||||
),
|
),
|
||||||
// Base no-js styles
|
// Base no-js styles
|
||||||
|
|
167
modules/api/mw.echo.api.APIHandler.js
Normal file
167
modules/api/mw.echo.api.APIHandler.js
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
/**
|
||||||
|
* Abstract notification API handler
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} [config] Configuration object
|
||||||
|
* @cfg {number} [limit=25] The limit on how many notifications to fetch
|
||||||
|
* @cfg {string} [userLang=mw.config.get( 'wgUserLanguage' )] User language. Defaults
|
||||||
|
* to the default user language configuration settings.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler = function MwEchoApiAPIHandler( config ) {
|
||||||
|
config = config || {};
|
||||||
|
|
||||||
|
this.fetchNotificationsPromise = {};
|
||||||
|
this.apiErrorState = {};
|
||||||
|
|
||||||
|
this.limit = config.limit || 25;
|
||||||
|
this.userLang = config.userLang || mw.config.get( 'wgUserLanguage' );
|
||||||
|
|
||||||
|
this.api = null;
|
||||||
|
|
||||||
|
// Map the logical type to the type
|
||||||
|
// that the API recognizes
|
||||||
|
this.normalizedType = {
|
||||||
|
message: 'message',
|
||||||
|
alert: 'alert',
|
||||||
|
all: 'message|alert'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parameters that are sent through
|
||||||
|
// to the 'fetch notification' promise
|
||||||
|
// per type
|
||||||
|
this.typeParams = {
|
||||||
|
message: {},
|
||||||
|
alert: {},
|
||||||
|
all: {}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Setup */
|
||||||
|
|
||||||
|
OO.initClass( mw.echo.api.APIHandler );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch notifications from the API.
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that resolves with an object containing the
|
||||||
|
* notification items
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.fetchNotifications = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new fetchNotifications promise that queries the API and overrides
|
||||||
|
* the cached promise.
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that resolves with an object containing the
|
||||||
|
* notification items
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.createNewFetchNotificationPromise = function ( type ) {
|
||||||
|
var me = this,
|
||||||
|
params = $.extend( {
|
||||||
|
action: 'query',
|
||||||
|
meta: 'notifications',
|
||||||
|
notsections: this.normalizedType[ type ],
|
||||||
|
notformat: 'model',
|
||||||
|
notlimit: this.limit,
|
||||||
|
notunreadfirst: 1,
|
||||||
|
notprop: 'index|list|count',
|
||||||
|
uselang: this.userLang
|
||||||
|
}, this.getTypeParams( type ) );
|
||||||
|
|
||||||
|
this.apiErrorState[ type ] = false;
|
||||||
|
this.fetchNotificationsPromise[ type ] = this.api.get( params )
|
||||||
|
.fail( function () {
|
||||||
|
// Mark API error state
|
||||||
|
me.apiErrorState[ type ] = true;
|
||||||
|
} );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the seen timestamp
|
||||||
|
*
|
||||||
|
* @param {string} [type] Notification type 'message', 'alert' or 'all'.
|
||||||
|
* @return {jQuery.Promise} A promise that resolves with the seen timestamp
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.updateSeenTime = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all notifications as read
|
||||||
|
*
|
||||||
|
* @param {string|string[]} type Notification type 'message', 'alert' or 'all'.
|
||||||
|
* @return {jQuery.Promise} A promise that resolves when all notifications
|
||||||
|
* are marked as read.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.markAllRead = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark multiple notification items as read using specific IDs
|
||||||
|
*
|
||||||
|
* @abstract
|
||||||
|
* @param {string[]} itemIdArray An array of notification item IDs
|
||||||
|
* @return {jQuery.Promise} A promise that resolves when all given notifications
|
||||||
|
* are marked as read.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.markItemsRead = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the read status of a notification item in the API
|
||||||
|
*
|
||||||
|
* @param {string} itemId Item id
|
||||||
|
* @return {jQuery.Promise} A promise that resolves when the notifications
|
||||||
|
* are marked as read.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.markItemRead = function ( itemId ) {
|
||||||
|
this.markItemsRead( [ itemId ] );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query the API for unread count of the notifications in this model
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type 'message', 'alert' or 'all'.
|
||||||
|
* @return {jQuery.Promise} jQuery promise that's resolved when the unread count is fetched
|
||||||
|
* and the badge label is updated.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.fetchUnreadCount = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the model has an API error state flagged
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type, 'alert', 'message' or 'all'
|
||||||
|
* @return {boolean} The model is in API error state
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.isFetchingErrorState = function ( type ) {
|
||||||
|
return !!this.apiErrorState[ type ];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the fetch notifications promise
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type, 'alert', 'message' or 'all'
|
||||||
|
* @return {jQuery.Promise} Promise that is resolved when notifications are
|
||||||
|
* fetched from the API.
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.getFetchNotificationPromise = function ( type ) {
|
||||||
|
if ( !this.fetchNotificationsPromise[ type ] ) {
|
||||||
|
this.createNewFetchNotificationPromise( type );
|
||||||
|
}
|
||||||
|
return this.fetchNotificationsPromise[ type ];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the extra parameters for fetching notifications for a given
|
||||||
|
* notification type.
|
||||||
|
*
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {Object} Extra API parameters for fetch notifications
|
||||||
|
*/
|
||||||
|
mw.echo.api.APIHandler.prototype.getTypeParams = function ( type ) {
|
||||||
|
return this.typeParams[ type ];
|
||||||
|
};
|
||||||
|
} )( mediaWiki, jQuery );
|
157
modules/api/mw.echo.api.EchoApi.js
Normal file
157
modules/api/mw.echo.api.EchoApi.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
( function ( mw ) {
|
||||||
|
/**
|
||||||
|
* A class defining Echo API instructions and network operations
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi = function MwEchoApiEchoApi() {
|
||||||
|
this.network = new mw.echo.api.NetworkHandler();
|
||||||
|
|
||||||
|
this.fetchingPromise = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
OO.initClass( mw.echo.api.EchoApi );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch notifications from the server based on type
|
||||||
|
* @param {string} types An array of notification types to fetch: 'alert', 'message', 'all'
|
||||||
|
* @param {string} [source="local"] The source from which to fetch the notifications
|
||||||
|
* @param {boolean} [isForced] Force a refresh on the fetch notifications promise
|
||||||
|
* @return {[type]} Promise that is resolved with all notifications for the
|
||||||
|
* requested types.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.fetchNotifications = function ( type, source, isForced ) {
|
||||||
|
var api = this;
|
||||||
|
|
||||||
|
source = source || 'local';
|
||||||
|
|
||||||
|
return this.network.getApiHandler( source ).fetchNotifications( type, isForced )
|
||||||
|
.then( function ( result ) {
|
||||||
|
var id, s,
|
||||||
|
sources = {},
|
||||||
|
rawData = OO.getProp( result.query, 'notifications' ),
|
||||||
|
sourceDefinitions = rawData.sources;
|
||||||
|
|
||||||
|
for ( source in sourceDefinitions ) {
|
||||||
|
api.network.addApiHandler( source, sourceDefinitions[ source ], true );
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( id in rawData.list ) {
|
||||||
|
if ( rawData.list[ id ].type === 'external' ) {
|
||||||
|
// Define sources
|
||||||
|
sources = {};
|
||||||
|
for ( s = 0; s < rawData.list[ id ].sources.length; s++ ) {
|
||||||
|
sources[ rawData.list[ id ].sources[ s ] ] = sourceDefinitions[ rawData.list[ id ].sources[ s ] ].title;
|
||||||
|
}
|
||||||
|
rawData.list[ id ].sources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawData;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch notifications from several sources
|
||||||
|
*
|
||||||
|
* @param {string[]} sourceArray An array of sources to fetch from the group
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that resolves with an array of promises for
|
||||||
|
* fetchNotifications per each given source in the source array.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.fetchNotificationGroups = function ( sourceArray, type ) {
|
||||||
|
var i, promise,
|
||||||
|
promises = [];
|
||||||
|
|
||||||
|
for ( i = 0; i < sourceArray.length; i++ ) {
|
||||||
|
promise = this.network.getApiHandler( sourceArray[ i ] ).fetchNotifications( type );
|
||||||
|
promises.push( promise );
|
||||||
|
}
|
||||||
|
|
||||||
|
return mw.echo.api.NetworkHandler.static.waitForAllPromises( promises );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark items as read in the API.
|
||||||
|
*
|
||||||
|
* @param {string[]} itemIds An array of item IDs to mark as read
|
||||||
|
* @param {string} source The source that these items belong to
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that is resolved when the operation
|
||||||
|
* is complete, with the number of unread notifications still remaining
|
||||||
|
* for that type in the given source
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.markItemsRead = function ( itemIds, source, type ) {
|
||||||
|
return this.network.getApiHandler( source ).markItemsRead( itemIds, type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark all notifications for a given type as read in the given source.
|
||||||
|
*
|
||||||
|
* @param {[type]} source Notifications source
|
||||||
|
* @param {[type]} type Notifications type
|
||||||
|
* @return {jQuery.Promise} A promise that is resolved when the operation
|
||||||
|
* is complete, with the number of unread notifications still remaining
|
||||||
|
* for that type in the given source
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.markAllRead = function ( source, type ) {
|
||||||
|
// FIXME: This specific method sends an operation
|
||||||
|
// to the API that marks all notifications of the given type as read regardless
|
||||||
|
// of whether they were actually seen by the user.
|
||||||
|
// We should consider removing the use of this method and, instead,
|
||||||
|
// using strictly the 'markItemsRead' by giving the API only the
|
||||||
|
// notifications that are available to the user.
|
||||||
|
return this.network.getApiHandler( source ).markAllRead( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the number of unread notifications for the given type in the given
|
||||||
|
* source.
|
||||||
|
*
|
||||||
|
* @param {string} source Notifications source
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that is resolved with the number of
|
||||||
|
* unread notifications for the given type and source.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.fetchUnreadCount = function ( source, type ) {
|
||||||
|
return this.network.getApiHandler( source ).fetchUnreadCount( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the seenTime property for the given type and source.
|
||||||
|
*
|
||||||
|
* @param {string} source Notification source
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} A promise that is resolved when the operation is complete.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.updateSeenTime = function ( source, type ) {
|
||||||
|
return this.network.getApiHandler( source ).updateSeenTime( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the API promise for fetch notification is in an error
|
||||||
|
* state for the given source and notification type.
|
||||||
|
*
|
||||||
|
* @param {string} source Notification source.
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {Boolean} The API response for fetching notification has
|
||||||
|
* resolved in an error state, or is rejected.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.isFetchingErrorState = function ( source, type ) {
|
||||||
|
return this.network.getApiHandler( source ).isFetchingErrorState( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fetch notifications promise active for the current source and type.
|
||||||
|
*
|
||||||
|
* @param {string} source Notification source.
|
||||||
|
* @param {string} type Notification type
|
||||||
|
* @return {jQuery.Promise} Promise that is resolved when notifications are
|
||||||
|
* fetched from the API.
|
||||||
|
*/
|
||||||
|
mw.echo.api.EchoApi.prototype.getFetchNotificationPromise = function ( source, type ) {
|
||||||
|
return this.network.getApiHandler( source ).getFetchNotificationPromise( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
} )( mediaWiki );
|
41
modules/api/mw.echo.api.ForeignAPIHandler.js
Normal file
41
modules/api/mw.echo.api.ForeignAPIHandler.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
/**
|
||||||
|
* Foreign notification API handler
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @extends mw.echo.api.LocalAPIHandler
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {string} apiUrl A url for the access point of the
|
||||||
|
* foreign API.
|
||||||
|
* @param {Object} [config] Configuration object
|
||||||
|
*/
|
||||||
|
mw.echo.api.ForeignAPIHandler = function MwEchoApiForeignAPIHandler( apiUrl, config ) {
|
||||||
|
config = config || {};
|
||||||
|
|
||||||
|
// Parent constructor
|
||||||
|
mw.echo.api.ForeignAPIHandler.parent.call( this, config );
|
||||||
|
|
||||||
|
// Add 'noforn' setting to foreign APIs
|
||||||
|
$.extend( true, this.typeParams, {
|
||||||
|
message: {
|
||||||
|
notnoforn: 1,
|
||||||
|
notfilter: '!read'
|
||||||
|
},
|
||||||
|
alert: {
|
||||||
|
notnoforn: 1,
|
||||||
|
notfilter: '!read'
|
||||||
|
},
|
||||||
|
all: {
|
||||||
|
notnoforn: 1,
|
||||||
|
notfilter: '!read'
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.api = new mw.ForeignApi( apiUrl );
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Setup */
|
||||||
|
|
||||||
|
OO.inheritClass( mw.echo.api.ForeignAPIHandler, mw.echo.api.LocalAPIHandler );
|
||||||
|
} )( mediaWiki, jQuery );
|
97
modules/api/mw.echo.api.LocalAPIHandler.js
Normal file
97
modules/api/mw.echo.api.LocalAPIHandler.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
( function ( mw ) {
|
||||||
|
/**
|
||||||
|
* Notification API handler
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @extends mw.echo.dm.APIHandler
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {Object} [config] Configuration object
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler = function MwEchoApiLocalAPIHandler( config ) {
|
||||||
|
config = config || {};
|
||||||
|
|
||||||
|
// Parent constructor
|
||||||
|
mw.echo.api.LocalAPIHandler.parent.call( this, config );
|
||||||
|
|
||||||
|
this.api = new mw.Api( { ajax: { cache: false } } );
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Setup */
|
||||||
|
|
||||||
|
OO.inheritClass( mw.echo.api.LocalAPIHandler, mw.echo.api.APIHandler );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler.prototype.fetchNotifications = function ( type, isForced ) {
|
||||||
|
if ( isForced || this.isFetchingErrorState( type ) ) {
|
||||||
|
// Force new promise
|
||||||
|
this.createNewFetchNotificationPromise( type );
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getFetchNotificationPromise( type );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler.prototype.updateSeenTime = function ( type ) {
|
||||||
|
return this.api.postWithToken( 'edit', {
|
||||||
|
action: 'echomarkseen',
|
||||||
|
type: this.normalizedType[ type ]
|
||||||
|
} )
|
||||||
|
.then( function ( data ) {
|
||||||
|
return data.query.echomarkseen.timestamp;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler.prototype.markAllRead = function ( type ) {
|
||||||
|
var data = {
|
||||||
|
action: 'echomarkread',
|
||||||
|
sections: this.normalizedType[ type ]
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.api.postWithToken( 'edit', data )
|
||||||
|
.then( function ( result ) {
|
||||||
|
return OO.getProp( result.query, 'echomarkread', type, 'rawcount' ) || 0;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler.prototype.markItemsRead = function ( itemIdArray ) {
|
||||||
|
var data = {
|
||||||
|
action: 'echomarkread',
|
||||||
|
list: itemIdArray.join( '|' )
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.api.postWithToken( 'edit', data );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
mw.echo.api.LocalAPIHandler.prototype.fetchUnreadCount = function ( type ) {
|
||||||
|
var normalizedType = this.normalizedType[ type ],
|
||||||
|
apiData = {
|
||||||
|
action: 'query',
|
||||||
|
meta: 'notifications',
|
||||||
|
notsections: normalizedType,
|
||||||
|
notgroupbysection: 1,
|
||||||
|
notmessageunreadfirst: 1,
|
||||||
|
notlimit: this.limit,
|
||||||
|
notprop: 'count',
|
||||||
|
uselang: this.userLang
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.api.get( apiData )
|
||||||
|
.then( function ( result ) {
|
||||||
|
return OO.getProp( result.query, 'notifications', normalizedType, 'rawcount' ) || 0;
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
} )( mediaWiki );
|
101
modules/api/mw.echo.api.NetworkHandler.js
Normal file
101
modules/api/mw.echo.api.NetworkHandler.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
/**
|
||||||
|
* Network handler for echo notifications. Manages multiple APIHandlers
|
||||||
|
* according to their sources.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
mw.echo.api.NetworkHandler = function MwEchoApiNetworkHandler() {
|
||||||
|
this.handlers = {};
|
||||||
|
|
||||||
|
// Add initial local handler
|
||||||
|
this.addApiHandler( 'local', {} );
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Setup */
|
||||||
|
|
||||||
|
OO.initClass( mw.echo.api.NetworkHandler );
|
||||||
|
|
||||||
|
/* Static methods */
|
||||||
|
/**
|
||||||
|
* Wait for all promises to finish either with a resolve or reject and
|
||||||
|
* return them to the caller once they do.
|
||||||
|
*
|
||||||
|
* @param {jQuery.Promise[]} promiseArray An array of promises
|
||||||
|
* @return {jQuery.Promise} A promise that resolves when all the promises
|
||||||
|
* finished with some resolution or rejection.
|
||||||
|
*/
|
||||||
|
mw.echo.api.NetworkHandler.static.waitForAllPromises = function ( promiseArray ) {
|
||||||
|
var i,
|
||||||
|
promises = promiseArray.slice( 0 ),
|
||||||
|
counter = 0,
|
||||||
|
deferred = $.Deferred(),
|
||||||
|
countPromises = function () {
|
||||||
|
counter++;
|
||||||
|
if ( counter === promises.length ) {
|
||||||
|
deferred.resolve( promises );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( !promiseArray.length ) {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( i = 0; i < promises.length; i++ ) {
|
||||||
|
promises[ i ].always( countPromises );
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise();
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the API handler that matches the symbolic name
|
||||||
|
*
|
||||||
|
* @param {string} name Symbolic name of the API handler
|
||||||
|
* @return {mw.echo.dm.APIHandler|undefined} API handler, if exists
|
||||||
|
*/
|
||||||
|
mw.echo.api.NetworkHandler.prototype.getApiHandler = function ( name ) {
|
||||||
|
return this.handlers[ name ];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an API handler
|
||||||
|
*
|
||||||
|
* @param {string} name Symbolic name
|
||||||
|
* @param {Object} config Configuration details
|
||||||
|
* @param {boolean} isForeign Is a foreign API
|
||||||
|
* @throws {Error} If no URL was given for a foreign API
|
||||||
|
*/
|
||||||
|
mw.echo.api.NetworkHandler.prototype.addApiHandler = function ( name, config, isForeign ) {
|
||||||
|
// This must be here so that it short-circuits the object construction below
|
||||||
|
if ( this.handlers[ name ] ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isForeign ) {
|
||||||
|
if ( !config.url ) {
|
||||||
|
throw new Error( 'Foreign APIs must have a valid url.' );
|
||||||
|
}
|
||||||
|
this.addCustomApiHandler( name, new mw.echo.api.ForeignAPIHandler( config.url, config ) );
|
||||||
|
} else {
|
||||||
|
this.addCustomApiHandler( name, new mw.echo.api.LocalAPIHandler( config ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a custom API handler by passing in an instance of an mw.echo.api.APIHandler subclass directly.
|
||||||
|
*
|
||||||
|
* @param {string} name Symbolic name
|
||||||
|
* @param {mw.echo.api.APIHandler} handler Handler object
|
||||||
|
*/
|
||||||
|
mw.echo.api.NetworkHandler.prototype.addCustomApiHandler = function ( name, handler ) {
|
||||||
|
if ( !this.handlers[ name ] ) {
|
||||||
|
this.handlers[ name ] = handler;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} )( mediaWiki, jQuery );
|
4
modules/api/mw.echo.api.js
Normal file
4
modules/api/mw.echo.api.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
( function ( mw ) {
|
||||||
|
mw.echo = mw.echo || {};
|
||||||
|
mw.echo.api = {};
|
||||||
|
} )( mediaWiki );
|
|
@ -3,23 +3,9 @@
|
||||||
|
|
||||||
mw.echo = mw.echo || {};
|
mw.echo = mw.echo || {};
|
||||||
|
|
||||||
mw.echo.apiCallParams = {
|
|
||||||
action: 'query',
|
|
||||||
meta: 'notifications',
|
|
||||||
// We have to send the API 'groupbysection' otherwise
|
|
||||||
// the 'messageunreadfirst' doesn't do anything.
|
|
||||||
// TODO: Fix the API.
|
|
||||||
notgroupbysection: 1,
|
|
||||||
notmessageunreadfirst: 1,
|
|
||||||
notformat: 'model',
|
|
||||||
notlimit: 25,
|
|
||||||
notprop: 'index|list|count',
|
|
||||||
uselang: mw.config.get( 'wgUserLanguage' )
|
|
||||||
};
|
|
||||||
|
|
||||||
// Activate ooui
|
// Activate ooui
|
||||||
$( document ).ready( function () {
|
$( document ).ready( function () {
|
||||||
var apiRequest, myWidget,
|
var myWidget, echoApi,
|
||||||
$existingAlertLink = $( '#pt-notifications-alert a' ),
|
$existingAlertLink = $( '#pt-notifications-alert a' ),
|
||||||
$existingMessageLink = $( '#pt-notifications-message a' ),
|
$existingMessageLink = $( '#pt-notifications-message a' ),
|
||||||
numAlerts = $existingAlertLink.text(),
|
numAlerts = $existingAlertLink.text(),
|
||||||
|
@ -45,7 +31,8 @@
|
||||||
$( this ).addClass( 'mw-echo-notifications-badge-dimmed' );
|
$( this ).addClass( 'mw-echo-notifications-badge-dimmed' );
|
||||||
|
|
||||||
// Fire the notification API requests
|
// Fire the notification API requests
|
||||||
apiRequest = new mw.Api( { ajax: { cache: false } } ).get( $.extend( { notsections: myType }, mw.echo.apiCallParams ) )
|
echoApi = new mw.echo.api.EchoApi();
|
||||||
|
echoApi.fetchNotifications( myType )
|
||||||
.then( function ( data ) {
|
.then( function ( data ) {
|
||||||
mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time );
|
mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time );
|
||||||
return data;
|
return data;
|
||||||
|
@ -61,11 +48,7 @@
|
||||||
// Load message button and popup if messages exist
|
// Load message button and popup if messages exist
|
||||||
if ( $existingMessageLink.length ) {
|
if ( $existingMessageLink.length ) {
|
||||||
messageNotificationsModel = new mw.echo.dm.NotificationsModel(
|
messageNotificationsModel = new mw.echo.dm.NotificationsModel(
|
||||||
// Create a network handler
|
echoApi,
|
||||||
new mw.echo.dm.NetworkHandler( {
|
|
||||||
type: 'message',
|
|
||||||
baseParams: mw.echo.apiCallParams
|
|
||||||
} ),
|
|
||||||
{
|
{
|
||||||
type: 'message'
|
type: 'message'
|
||||||
}
|
}
|
||||||
|
@ -93,11 +76,7 @@
|
||||||
}
|
}
|
||||||
// Load alerts popup and button
|
// Load alerts popup and button
|
||||||
alertNotificationsModel = new mw.echo.dm.NotificationsModel(
|
alertNotificationsModel = new mw.echo.dm.NotificationsModel(
|
||||||
// Create a network handler
|
echoApi,
|
||||||
new mw.echo.dm.NetworkHandler( {
|
|
||||||
type: 'alert',
|
|
||||||
baseParams: mw.echo.apiCallParams
|
|
||||||
} ),
|
|
||||||
{
|
{
|
||||||
type: 'alert'
|
type: 'alert'
|
||||||
}
|
}
|
||||||
|
@ -121,11 +100,8 @@
|
||||||
|
|
||||||
// HACK: Now that the module loaded, show the popup
|
// HACK: Now that the module loaded, show the popup
|
||||||
myWidget = myType === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
|
myWidget = myType === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
|
||||||
myWidget.populateNotifications( apiRequest ).then( function () {
|
myWidget.once( 'finishLoading', function () {
|
||||||
// Log timing after notifications are shown
|
// Log timing after notifications are shown
|
||||||
// FIXME: The notifications might not be shown yet if the API
|
|
||||||
// request finished before the UI loads, in which case it will
|
|
||||||
// only be shown after the toggle( true ) call below.
|
|
||||||
mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - time );
|
mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - time );
|
||||||
} );
|
} );
|
||||||
myWidget.popup.toggle( true );
|
myWidget.popup.toggle( true );
|
||||||
|
|
|
@ -179,12 +179,22 @@
|
||||||
* All notifications were marked as read
|
* All notifications were marked as read
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @event finishLoading
|
||||||
|
* Notifications have successfully finished being processed and are fully loaded
|
||||||
|
*/
|
||||||
|
|
||||||
/* Methods */
|
/* Methods */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Respond to badge button click
|
* Respond to badge button click
|
||||||
*/
|
*/
|
||||||
mw.echo.ui.NotificationBadgeWidget.prototype.onBadgeButtonClick = function () {
|
mw.echo.ui.NotificationBadgeWidget.prototype.onBadgeButtonClick = function () {
|
||||||
|
if ( !this.popup.isVisible() ) {
|
||||||
|
// Force a new API request for notifications
|
||||||
|
this.notificationsModel.fetchNotifications( true );
|
||||||
|
}
|
||||||
|
|
||||||
this.popup.toggle();
|
this.popup.toggle();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,51 +258,17 @@
|
||||||
this.notificationsModel.markAllRead();
|
this.notificationsModel.markAllRead();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate notifications from the API.
|
|
||||||
*
|
|
||||||
* @param {jQuery.Promise} [fetchingApiRequest] An existing promise for fetching
|
|
||||||
* notifications from the API. This allows us to start fetching notifications
|
|
||||||
* externally.
|
|
||||||
* @return {jQuery.Promise} Promise that is resolved when the notifications populate
|
|
||||||
*/
|
|
||||||
mw.echo.ui.NotificationBadgeWidget.prototype.populateNotifications = function ( fetchingApiRequest ) {
|
|
||||||
var widget = this;
|
|
||||||
|
|
||||||
// The model retrieves the ongoing promise or returns the existing one that it
|
|
||||||
// has. When the promise is completed successfuly, it nullifies itself so we can
|
|
||||||
// request for it to be rebuilt and the request to the API resent.
|
|
||||||
// However, in the case of an API failure, the promise does not nullify itself.
|
|
||||||
// In that case we also want the model to rebuild the request, so in this condition
|
|
||||||
// we must check both cases.
|
|
||||||
if ( !this.notificationsModel.isFetchingNotifications() || this.notificationsModel.isFetchingErrorState() ) {
|
|
||||||
this.pushPending();
|
|
||||||
this.markAllReadButton.toggle( false );
|
|
||||||
return this.notificationsModel.fetchNotifications( fetchingApiRequest )
|
|
||||||
.then( function ( idArray ) {
|
|
||||||
// Clip again
|
|
||||||
widget.popup.clip();
|
|
||||||
|
|
||||||
// Log impressions
|
|
||||||
mw.echo.logger.logNotificationImpressions( this.type, idArray, mw.echo.Logger.static.context.popup );
|
|
||||||
} )
|
|
||||||
.always( function () {
|
|
||||||
// Pop pending
|
|
||||||
widget.popPending();
|
|
||||||
// Nullify the promise; let the user fetch again
|
|
||||||
widget.fetchNotificationsPromise = null;
|
|
||||||
} );
|
|
||||||
} else {
|
|
||||||
return this.notificationsModel.getFetchNotificationPromise();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend the response to button click so we can also update the notification list.
|
* Extend the response to button click so we can also update the notification list.
|
||||||
|
* @fires finishLoading
|
||||||
*/
|
*/
|
||||||
mw.echo.ui.NotificationBadgeWidget.prototype.onPopupToggle = function ( isVisible ) {
|
mw.echo.ui.NotificationBadgeWidget.prototype.onPopupToggle = function ( isVisible ) {
|
||||||
var widget = this;
|
var widget = this;
|
||||||
|
|
||||||
|
if ( this.promiseRunning ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ( !isVisible ) {
|
if ( !isVisible ) {
|
||||||
// If the popup is closing, remove "initiallyUnseen" and leave
|
// If the popup is closing, remove "initiallyUnseen" and leave
|
||||||
this.notificationsWidget.resetNotificationItems();
|
this.notificationsWidget.resetNotificationItems();
|
||||||
|
@ -316,14 +292,20 @@
|
||||||
this.popup.$clippable.css( 'height', '1px' );
|
this.popup.$clippable.css( 'height', '1px' );
|
||||||
this.popup.clip();
|
this.popup.clip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.pushPending();
|
||||||
|
this.markAllReadButton.toggle( false );
|
||||||
|
this.promiseRunning = true;
|
||||||
// Always populate on popup open. The model and widget should handle
|
// Always populate on popup open. The model and widget should handle
|
||||||
// the case where the promise is already underway.
|
// the case where the promise is already underway.
|
||||||
this.populateNotifications()
|
this.notificationsModel.fetchNotifications()
|
||||||
.then( function () {
|
.then( function () {
|
||||||
var i,
|
var i,
|
||||||
items = widget.notificationsWidget.getItems();
|
items = widget.notificationsWidget.getItems();
|
||||||
|
|
||||||
if ( widget.popup.isVisible() ) {
|
if ( widget.popup.isVisible() ) {
|
||||||
|
widget.popup.clip();
|
||||||
|
|
||||||
// Update seen time
|
// Update seen time
|
||||||
widget.notificationsModel.updateSeenTime();
|
widget.notificationsModel.updateSeenTime();
|
||||||
// Mark notifications as 'read' if markReadWhenSeen is set to true
|
// Mark notifications as 'read' if markReadWhenSeen is set to true
|
||||||
|
@ -332,6 +314,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log impressions
|
// Log impressions
|
||||||
|
// TODO: Only log the impressions of notifications that
|
||||||
|
// are actually visible
|
||||||
for ( i = 0; i < items.length; i++ ) {
|
for ( i = 0; i < items.length; i++ ) {
|
||||||
if ( items[ i ].getModel ) {
|
if ( items[ i ].getModel ) {
|
||||||
mw.echo.logger.logInteraction(
|
mw.echo.logger.logInteraction(
|
||||||
|
@ -343,6 +327,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
widget.emit( 'finishLoading' );
|
||||||
|
} )
|
||||||
|
.always( function () {
|
||||||
|
// Pop pending
|
||||||
|
widget.popPending();
|
||||||
|
widget.promiseRunning = false;
|
||||||
} );
|
} );
|
||||||
this.hasRunFirstTime = true;
|
this.hasRunFirstTime = true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
( function ( mw ) {
|
|
||||||
/**
|
|
||||||
* Abstract notification API handler
|
|
||||||
*
|
|
||||||
* @abstract
|
|
||||||
* @class
|
|
||||||
* @mixins OO.EventEmitter
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} [config] Configuration object
|
|
||||||
* @cfg {Object} [baseParams] The base parameters for all API calls
|
|
||||||
* done by the API handler
|
|
||||||
* @cfg {number} [limit=25] The limit on how many notifications to fetch
|
|
||||||
* @cfg {string} [type='alert'] Notification type
|
|
||||||
* @cfg {string} [userLang='en'] User language
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler = function MwEchoDmAPIHandler( config ) {
|
|
||||||
config = config || {};
|
|
||||||
|
|
||||||
// Mixin constructor
|
|
||||||
OO.EventEmitter.call( this );
|
|
||||||
|
|
||||||
this.fetchNotificationsPromise = null;
|
|
||||||
this.apiErrorState = false;
|
|
||||||
|
|
||||||
this.type = config.type || 'alert';
|
|
||||||
this.limit = config.limit || 25;
|
|
||||||
this.userLang = config.userLang || 'en';
|
|
||||||
this.baseParams = config.baseParams || {};
|
|
||||||
|
|
||||||
this.api = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Setup */
|
|
||||||
|
|
||||||
OO.initClass( mw.echo.dm.APIHandler );
|
|
||||||
OO.mixinClass( mw.echo.dm.APIHandler, OO.EventEmitter );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch notifications from the API.
|
|
||||||
*
|
|
||||||
* @param {jQuery.Promise} [apiPromise] An existing promise querying the API for notifications.
|
|
||||||
* This allows us to send an API request foreign to the DM and have the model
|
|
||||||
* handle the operation as if it asked for the request itself, updating all that
|
|
||||||
* needs to be updated and emitting all proper events.
|
|
||||||
* @return {jQuery.Promise} A promise that resolves with an object containing the
|
|
||||||
* notification items
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.fetchNotifications = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the seen timestamp
|
|
||||||
*
|
|
||||||
* @param {string|string[]} [type] Notification type 'message', 'alert' or
|
|
||||||
* an array of both.
|
|
||||||
* @return {jQuery.Promise} A promise that resolves with the seen timestamp
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.updateSeenTime = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark all notifications as read
|
|
||||||
*
|
|
||||||
* @return {jQuery.Promise} A promise that resolves when all notifications
|
|
||||||
* are marked as read.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.markAllRead = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark multiple notification items as read using specific IDs
|
|
||||||
*
|
|
||||||
* @abstract
|
|
||||||
* @param {string[]} itemIdArray An array of notification item IDs
|
|
||||||
* @return {jQuery.Promise} A promise that resolves when all given notifications
|
|
||||||
* are marked as read.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.markMultipleItemsRead = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the read status of a notification item in the API
|
|
||||||
*
|
|
||||||
* @param {string} itemId Item id
|
|
||||||
* @return {jQuery.Promise} A promise that resolves when the notifications
|
|
||||||
* are marked as read.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.markItemRead = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Query the API for unread count of the notifications in this model
|
|
||||||
*
|
|
||||||
* @return {jQuery.Promise} jQuery promise that's resolved when the unread count is fetched
|
|
||||||
* and the badge label is updated.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.fetchUnreadCount = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the model is fetching notifications from the API
|
|
||||||
*
|
|
||||||
* @return {boolean} The model is in the process of fetching from the API
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.isFetchingNotifications = function () {
|
|
||||||
return !!this.fetchNotificationsPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the model has an API error state flagged
|
|
||||||
*
|
|
||||||
* @return {boolean} The model is in API error state
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.isFetchingErrorState = function () {
|
|
||||||
return !!this.apiErrorState;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the fetch notifications promise
|
|
||||||
* @return {jQuery.Promise} Promise that is resolved when notifications are
|
|
||||||
* fetched from the API.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.getFetchNotificationPromise = function () {
|
|
||||||
return this.fetchNotificationsPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the base params associated with this API handler
|
|
||||||
*
|
|
||||||
* @return {Object} Base API params
|
|
||||||
*/
|
|
||||||
mw.echo.dm.APIHandler.prototype.getBaseParams = function () {
|
|
||||||
return this.baseParams;
|
|
||||||
};
|
|
||||||
} )( mediaWiki );
|
|
|
@ -1,25 +0,0 @@
|
||||||
( function ( mw ) {
|
|
||||||
/**
|
|
||||||
* Foreign notification API handler
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @extends mw.echo.dm.LocalAPIHandler
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {string} apiUrl A url for the access point of the
|
|
||||||
* foreign API.
|
|
||||||
* @param {Object} [config] Configuration object
|
|
||||||
*/
|
|
||||||
mw.echo.dm.ForeignAPIHandler = function MwEchoDmForeignAPIHandler( apiUrl, config ) {
|
|
||||||
config = config || {};
|
|
||||||
|
|
||||||
// Parent constructor
|
|
||||||
mw.echo.dm.ForeignAPIHandler.parent.call( this, config );
|
|
||||||
|
|
||||||
this.api = new mw.ForeignApi( apiUrl );
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Setup */
|
|
||||||
|
|
||||||
OO.inheritClass( mw.echo.dm.ForeignAPIHandler, mw.echo.dm.LocalAPIHandler );
|
|
||||||
} )( mediaWiki );
|
|
|
@ -1,133 +0,0 @@
|
||||||
( function ( mw, $ ) {
|
|
||||||
/**
|
|
||||||
* Notification API handler
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @extends mw.echo.dm.APIHandler
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} [config] Configuration object
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler = function MwEchoDmLocalAPIHandler( config ) {
|
|
||||||
config = config || {};
|
|
||||||
|
|
||||||
// Parent constructor
|
|
||||||
mw.echo.dm.LocalAPIHandler.parent.call( this, config );
|
|
||||||
|
|
||||||
this.api = new mw.Api( { ajax: { cache: false } } );
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Setup */
|
|
||||||
|
|
||||||
OO.inheritClass( mw.echo.dm.LocalAPIHandler, mw.echo.dm.APIHandler );
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.fetchNotifications = function ( apiPromise ) {
|
|
||||||
var helper = this,
|
|
||||||
params = $.extend( { notsections: this.type }, this.getBaseParams() );
|
|
||||||
|
|
||||||
if ( !this.fetchNotificationsPromise || this.isFetchingErrorState() ) {
|
|
||||||
this.apiErrorState = false;
|
|
||||||
this.fetchNotificationsPromise = ( apiPromise || this.api.get( params ) )
|
|
||||||
.fail( function () {
|
|
||||||
// Mark API error state
|
|
||||||
helper.apiErrorState = true;
|
|
||||||
} )
|
|
||||||
.always( function () {
|
|
||||||
helper.fetchNotificationsPromise = null;
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.fetchNotificationsPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.updateSeenTime = function ( type ) {
|
|
||||||
type = type || this.type;
|
|
||||||
|
|
||||||
return this.api.postWithToken( 'edit', {
|
|
||||||
action: 'echomarkseen',
|
|
||||||
// TODO: The API should also work with piped types like
|
|
||||||
// getting notification lists
|
|
||||||
type: $.isArray( type ) ? 'all' : type
|
|
||||||
} )
|
|
||||||
.then( function ( data ) {
|
|
||||||
return data.query.echomarkseen.timestamp;
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.markAllRead = function () {
|
|
||||||
var model = this,
|
|
||||||
data = {
|
|
||||||
action: 'echomarkread',
|
|
||||||
uselang: this.userLang,
|
|
||||||
sections: $.isArray( this.type ) ? this.type.join( '|' ) : this.type
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.api.postWithToken( 'edit', data )
|
|
||||||
.then( function ( result ) {
|
|
||||||
return OO.getProp( result.query, 'echomarkread', model.type, 'rawcount' ) || 0;
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.markMultipleItemsRead = function ( itemIdArray ) {
|
|
||||||
var model = this,
|
|
||||||
data = {
|
|
||||||
action: 'echomarkread',
|
|
||||||
uselang: this.userLang,
|
|
||||||
list: itemIdArray.join( '|' )
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.api.postWithToken( 'edit', data )
|
|
||||||
.then( function ( result ) {
|
|
||||||
return OO.getProp( result.query, 'echomarkread', model.type, 'rawcount' ) || 0;
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.markItemRead = function ( itemId ) {
|
|
||||||
var model = this,
|
|
||||||
data = {
|
|
||||||
action: 'echomarkread',
|
|
||||||
uselang: this.userLang,
|
|
||||||
list: itemId
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.api.postWithToken( 'edit', data )
|
|
||||||
.then( function ( result ) {
|
|
||||||
return OO.getProp( result.query, 'echomarkread', model.type, 'rawcount' ) || 0;
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
mw.echo.dm.LocalAPIHandler.prototype.fetchUnreadCount = function () {
|
|
||||||
var apiData = {
|
|
||||||
action: 'query',
|
|
||||||
meta: 'notifications',
|
|
||||||
notsections: $.isArray( this.type ) ? this.type.join( '|' ) : this.type,
|
|
||||||
notmessageunreadfirst: 1,
|
|
||||||
notlimit: this.limit,
|
|
||||||
notprop: 'index|count',
|
|
||||||
uselang: this.userLang
|
|
||||||
};
|
|
||||||
|
|
||||||
return this.api.get( apiData )
|
|
||||||
.then( function ( result ) {
|
|
||||||
return OO.getProp( result.query, 'notifications', 'rawcount' ) || 0;
|
|
||||||
} );
|
|
||||||
};
|
|
||||||
} )( mediaWiki, jQuery );
|
|
|
@ -1,143 +0,0 @@
|
||||||
( function ( mw, $ ) {
|
|
||||||
/**
|
|
||||||
* Network handler for echo notifications. Manages multiple APIHandlers
|
|
||||||
* according to their sources.
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
* @mixins OO.EventEmitter
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} [config] Configuration object
|
|
||||||
* @cfg {string} [type="alert"] Notification type
|
|
||||||
* @cfg {Object} [baseParams] The base params to send to the
|
|
||||||
* APIs with every fetch notifications process.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler = function MwEchoDmNetworkHandler( config ) {
|
|
||||||
config = config || {};
|
|
||||||
|
|
||||||
// Mixin constructor
|
|
||||||
OO.EventEmitter.call( this );
|
|
||||||
|
|
||||||
this.type = config.type || 'alert';
|
|
||||||
this.baseParams = config.baseParams || {};
|
|
||||||
this.handlers = {};
|
|
||||||
|
|
||||||
// Add initial local handler
|
|
||||||
this.addApiHandler( 'local', {} );
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Setup */
|
|
||||||
|
|
||||||
OO.initClass( mw.echo.dm.NetworkHandler );
|
|
||||||
OO.mixinClass( mw.echo.dm.NetworkHandler, OO.EventEmitter );
|
|
||||||
|
|
||||||
/* Static methods */
|
|
||||||
/**
|
|
||||||
* Wait for all promises to finish either with a resolve or reject and
|
|
||||||
* return them to the caller once they do.
|
|
||||||
*
|
|
||||||
* @param {jQuery.Promise[]} promiseArray An array of promises
|
|
||||||
* @return {jQuery.Promise} A promise that resolves when all the promises
|
|
||||||
* finished with some resolution or rejection.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.static.waitForAllPromises = function ( promiseArray ) {
|
|
||||||
var i,
|
|
||||||
promises = promiseArray.slice( 0 ),
|
|
||||||
counter = 0,
|
|
||||||
deferred = $.Deferred(),
|
|
||||||
countPromises = function () {
|
|
||||||
counter++;
|
|
||||||
if ( counter === promises.length ) {
|
|
||||||
deferred.resolve( promises );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if ( !promiseArray.length ) {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( i = 0; i < promises.length; i++ ) {
|
|
||||||
promises[ i ].always( countPromises );
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Methods */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch notifications from several sources
|
|
||||||
*
|
|
||||||
* @param {string[]} sourceArray Sources
|
|
||||||
* @return {jQuery.Promise} A promise that resolves with an array of promises for
|
|
||||||
* fetchNotifications per each given source in the source array.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.prototype.fetchNotificationGroups = function ( sourceArray ) {
|
|
||||||
var i, promise,
|
|
||||||
promises = [];
|
|
||||||
|
|
||||||
for ( i = 0; i < sourceArray.length; i++ ) {
|
|
||||||
promise = this.getApiHandler( sourceArray[ i ] ).fetchNotifications();
|
|
||||||
promises.push( promise );
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.constructor.static.waitForAllPromises( promises );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the API handler that matches the symbolic name
|
|
||||||
*
|
|
||||||
* @param {string} name Symbolic name of the API handler
|
|
||||||
* @return {mw.echo.dm.APIHandler|undefined} API handler, if exists
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.prototype.getApiHandler = function ( name ) {
|
|
||||||
return this.handlers[ name ];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an API handler
|
|
||||||
*
|
|
||||||
* @param {string} name Symbolic name
|
|
||||||
* @param {Object} config Configuration details
|
|
||||||
* @param {boolean} isForeign Is a foreign API
|
|
||||||
* @throws {Error} If no URL was given for a foreign API
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.prototype.addApiHandler = function ( name, config, isForeign ) {
|
|
||||||
var apiConfig;
|
|
||||||
|
|
||||||
if ( !this.handlers[ name ] ) {
|
|
||||||
apiConfig = $.extend( true, {}, { baseParams: this.baseParams, type: this.getType() }, config );
|
|
||||||
|
|
||||||
if ( isForeign ) {
|
|
||||||
if ( !config.url ) {
|
|
||||||
throw new Error( 'Foreign APIs must have a valid url.' );
|
|
||||||
}
|
|
||||||
this.addCustomApiHandler( name, new mw.echo.dm.ForeignAPIHandler( config.url, apiConfig ) );
|
|
||||||
} else {
|
|
||||||
this.addCustomApiHandler( name, new mw.echo.dm.LocalAPIHandler( apiConfig ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a custom API handler by passing in an instance of an mw.echo.dm.APIHandler subclass directly.
|
|
||||||
*
|
|
||||||
* @param {string} name Symbolic name
|
|
||||||
* @param {mw.echo.dm.APIHandler} handler Handler object
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.prototype.addCustomApiHandler = function ( name, handler ) {
|
|
||||||
if ( !this.handlers[ name ] ) {
|
|
||||||
this.handlers[ name ] = handler;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type of notifications this network handler is associated with
|
|
||||||
*
|
|
||||||
* @return {string} Notification type
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NetworkHandler.prototype.getType = function () {
|
|
||||||
return this.type;
|
|
||||||
};
|
|
||||||
|
|
||||||
} )( mediaWiki, jQuery );
|
|
|
@ -7,7 +7,7 @@
|
||||||
* @mixins mw.echo.dm.SortedList
|
* @mixins mw.echo.dm.SortedList
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {mw.echo.dm.NetworkHandler} networkHandler The network handler
|
* @param {mw.echo.api.EchoApi} api Echo API
|
||||||
* @param {Object[]} sources An array of objects defining the sources
|
* @param {Object[]} sources An array of objects defining the sources
|
||||||
* of its item's sub-items.
|
* of its item's sub-items.
|
||||||
* @param {number} id Notification id,
|
* @param {number} id Notification id,
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
* @cfg {number} [count=0] The number of items this group contains. This is used for both the
|
* @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.
|
* 'expand' label and also to potentially update the badge counters for local bundles.
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationGroupItem = function mwEchoDmNotificationGroupItem( networkHandler, sources, id, config ) {
|
mw.echo.dm.NotificationGroupItem = function mwEchoDmNotificationGroupItem( api, sources, id, config ) {
|
||||||
var source, item,
|
var source, item,
|
||||||
items = [];
|
items = [];
|
||||||
|
|
||||||
|
@ -49,18 +49,15 @@
|
||||||
this.removeReadNotifications = !!config.removeReadNotifications;
|
this.removeReadNotifications = !!config.removeReadNotifications;
|
||||||
|
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
this.networkHandler = networkHandler;
|
this.api = api;
|
||||||
this.notifModels = {};
|
this.notifModels = {};
|
||||||
this.anticipatedCount = config.count || 0;
|
this.anticipatedCount = config.count || 0;
|
||||||
|
|
||||||
// Create notification models for each source
|
// Create notification models for each source
|
||||||
for ( source in this.sources ) {
|
for ( source in this.sources ) {
|
||||||
// Add foreign API handler
|
|
||||||
this.networkHandler.addApiHandler( source, { url: this.sources[ source ].url, baseParams: { notnoforn: 1, notfilter: '!read' } }, true );
|
|
||||||
|
|
||||||
// Create a notifications model
|
// Create a notifications model
|
||||||
item = new mw.echo.dm.NotificationsModel(
|
item = new mw.echo.dm.NotificationsModel(
|
||||||
this.networkHandler,
|
this.api,
|
||||||
{
|
{
|
||||||
type: this.getType(),
|
type: this.getType(),
|
||||||
source: source,
|
source: source,
|
||||||
|
@ -124,19 +121,19 @@
|
||||||
fetchPromises = [],
|
fetchPromises = [],
|
||||||
sourceKeys = Object.keys( this.sources );
|
sourceKeys = Object.keys( this.sources );
|
||||||
|
|
||||||
return this.networkHandler.fetchNotificationGroups( sourceKeys )
|
return this.api.fetchNotificationGroups( sourceKeys, this.getType() )
|
||||||
.then( function ( promises ) {
|
.then( function () {
|
||||||
var i;
|
var i;
|
||||||
|
|
||||||
for ( i = 0; i < sourceKeys.length; i++ ) {
|
for ( i = 0; i < sourceKeys.length; i++ ) {
|
||||||
notifModel = model.getItemById( sourceKeys[ i ] );
|
notifModel = model.getItemById( sourceKeys[ i ] );
|
||||||
if ( notifModel ) {
|
if ( notifModel ) {
|
||||||
fetchPromises.push( notifModel.fetchNotifications( promises[ i ] ) );
|
fetchPromises.push( notifModel.fetchNotifications() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for all fetch processes to finish before we resolve this promise
|
// Wait for all fetch processes to finish before we resolve this promise
|
||||||
return mw.echo.dm.NetworkHandler.static.waitForAllPromises( fetchPromises );
|
return mw.echo.api.NetworkHandler.static.waitForAllPromises( fetchPromises );
|
||||||
} )
|
} )
|
||||||
.then( function () {
|
.then( function () {
|
||||||
model.anticipatedCount = null;
|
model.anticipatedCount = null;
|
||||||
|
@ -180,9 +177,9 @@
|
||||||
.then( ( function ( model ) {
|
.then( ( function ( model ) {
|
||||||
return function ( idArray ) {
|
return function ( idArray ) {
|
||||||
// Mark sub items as read in the UI
|
// Mark sub items as read in the UI
|
||||||
model.markAllRead();
|
model.markAllRead( model.getSource(), model.getType() );
|
||||||
// Mark all existing items as read in the API
|
// Mark all existing items as read in the API
|
||||||
model.markExistingItemsReadInApi( idArray );
|
model.markItemsReadInApi( idArray );
|
||||||
};
|
};
|
||||||
} )( notifModels[ i ] ) );
|
} )( notifModels[ i ] ) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* @mixins OO.EventEmitter
|
* @mixins OO.EventEmitter
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
* @param {mw.echo.dm.NetworkHandler} networkHandler Network handler
|
* @param {mw.echo.api.EchoApi} api Network handler
|
||||||
* @param {Object} [config] Configuration object
|
* @param {Object} [config] Configuration object
|
||||||
* @cfg {string} [id] Model id, used to refer to the model specifically.
|
* @cfg {string} [id] Model id, used to refer to the model specifically.
|
||||||
* Falls back to the model's unique source
|
* Falls back to the model's unique source
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
* means the model will only contain unread notifications. This is useful for
|
* means the model will only contain unread notifications. This is useful for
|
||||||
* cross-wiki bundled notifications.
|
* cross-wiki bundled notifications.
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( networkHandler, config ) {
|
mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( api, config ) {
|
||||||
config = config || {};
|
config = config || {};
|
||||||
|
|
||||||
// Mixin constructor
|
// Mixin constructor
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
this.removeReadNotifications = !!config.removeReadNotifications;
|
this.removeReadNotifications = !!config.removeReadNotifications;
|
||||||
this.foreign = !!config.foreign;
|
this.foreign = !!config.foreign;
|
||||||
|
|
||||||
this.networkHandler = networkHandler;
|
this.api = api;
|
||||||
|
|
||||||
this.seenTime = mw.config.get( 'wgEchoSeenTime' ) || {};
|
this.seenTime = mw.config.get( 'wgEchoSeenTime' ) || {};
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
// because the API takes a single request to mark all notifications
|
// because the API takes a single request to mark all notifications
|
||||||
// as read, and we don't need to send multiple individual requests.
|
// as read, and we don't need to send multiple individual requests.
|
||||||
if ( !this.markingAllAsRead ) {
|
if ( !this.markingAllAsRead ) {
|
||||||
this.markItemReadInApi( id );
|
this.markItemsReadInApi( id );
|
||||||
}
|
}
|
||||||
if ( this.removeReadNotifications ) {
|
if ( this.removeReadNotifications ) {
|
||||||
// Remove this notification from the model
|
// Remove this notification from the model
|
||||||
|
@ -341,7 +341,7 @@
|
||||||
|
|
||||||
// Only update seenTime in the API locally
|
// Only update seenTime in the API locally
|
||||||
if ( !this.isForeign() ) {
|
if ( !this.isForeign() ) {
|
||||||
promise = this.getApi().updateSeenTime( type );
|
promise = this.api.updateSeenTime( this.getSource(), type );
|
||||||
} else {
|
} else {
|
||||||
promise = $.Deferred().resolve();
|
promise = $.Deferred().resolve();
|
||||||
}
|
}
|
||||||
|
@ -363,8 +363,8 @@
|
||||||
length = items.length;
|
length = items.length;
|
||||||
|
|
||||||
// Skip if this is an automatic "mark as read" and this model is
|
// Skip if this is an automatic "mark as read" and this model is
|
||||||
// external
|
// foreign
|
||||||
if ( this.external && this.autoMarkReadInProcess ) {
|
if ( this.foreign && this.autoMarkReadInProcess ) {
|
||||||
return $.Deferred().resolve( 0 ).promise();
|
return $.Deferred().resolve( 0 ).promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +382,7 @@
|
||||||
|
|
||||||
this.markingAllAsRead = true;
|
this.markingAllAsRead = true;
|
||||||
for ( i = 0, len = items.length; i < len; i++ ) {
|
for ( i = 0, len = items.length; i < len; i++ ) {
|
||||||
// Skip items that are external if we are in automatic 'mark all as read'
|
// Skip items that are foreign if we are in automatic 'mark all as read'
|
||||||
if ( !items[ i ].isForeign() || !this.autoMarkReadInProcess ) {
|
if ( !items[ i ].isForeign() || !this.autoMarkReadInProcess ) {
|
||||||
items[ i ].toggleRead( true );
|
items[ i ].toggleRead( true );
|
||||||
items[ i ].toggleSeen( true );
|
items[ i ].toggleSeen( true );
|
||||||
|
@ -391,7 +391,7 @@
|
||||||
}
|
}
|
||||||
this.markingAllAsRead = false;
|
this.markingAllAsRead = false;
|
||||||
|
|
||||||
return this.getApi().markAllRead();
|
return this.api.markAllRead( this.getSource(), this.getType() );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -408,40 +408,27 @@
|
||||||
var model = this;
|
var model = this;
|
||||||
|
|
||||||
this.autoMarkReadInProcess = true;
|
this.autoMarkReadInProcess = true;
|
||||||
this.markAllRead()
|
this.markAllRead( this.getSource(), this.getType() )
|
||||||
.then( function () {
|
.then( function () {
|
||||||
model.autoMarkReadInProcess = false;
|
model.autoMarkReadInProcess = false;
|
||||||
} );
|
} );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the read status of a notification item in the API
|
* Update the read status of a notification item, or a list of items, in the API
|
||||||
*
|
*
|
||||||
* @param {string} itemId Item id
|
* @param {string|string[]} itemIds Item id or an array of item Ids
|
||||||
* @return {jQuery.Promise} A promise that resolves when the notifications
|
* @return {jQuery.Promise} A promise that resolves when the notifications
|
||||||
* were marked as read.
|
* were marked as read.
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel.prototype.markItemReadInApi = function ( itemId ) {
|
mw.echo.dm.NotificationsModel.prototype.markItemsReadInApi = function ( itemIds ) {
|
||||||
if ( !this.unreadNotifications.getItemCount() ) {
|
if ( !this.unreadNotifications.getItemCount() ) {
|
||||||
return $.Deferred().resolve( 0 ).promise();
|
return $.Deferred().resolve( 0 ).promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getApi().markItemRead( itemId );
|
itemIds = $.isArray( itemIds ) ? itemIds : [ itemIds ];
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
return this.api.markItemsRead( itemIds, this.getSource(), this.getType() );
|
||||||
* Update the read status in the API only for the existing items in this model.
|
|
||||||
* If an item id array is given, those items will be updated. Otherwise, all items
|
|
||||||
* in the model are updated.
|
|
||||||
*
|
|
||||||
* @param {string[]} [itemIds] Array of item ids
|
|
||||||
* @return {jQuery.Promise} A promise that resolves when the notifications
|
|
||||||
* were marked as read.
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NotificationsModel.prototype.markExistingItemsReadInApi = function ( itemIds ) {
|
|
||||||
itemIds = itemIds || this.getAllItemIds();
|
|
||||||
|
|
||||||
return this.getApi().markMultipleItemsRead( itemIds );
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -464,34 +451,31 @@
|
||||||
/**
|
/**
|
||||||
* Fetch notifications from the API and update the notifications list.
|
* Fetch notifications from the API and update the notifications list.
|
||||||
*
|
*
|
||||||
* @param {jQuery.Promise} An existing promise querying the API for notifications.
|
* @param {boolean} [isForced] Force a renewed fetching promise. If set to false, the
|
||||||
* This allows us to send an API request external to the DM and have the model
|
* model will request the stored/cached fetching promise from the API. A 'true' value
|
||||||
* handle the operation as if it asked for the request itself, updating all that
|
* will force the API to re-request that information from the server and update the
|
||||||
* needs to be updated and emitting all proper events.
|
* notifications.
|
||||||
* @return {jQuery.Promise} A promise that resolves with an array of notification IDs
|
* @return {jQuery.Promise} A promise that resolves with an array of notification IDs
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel.prototype.fetchNotifications = function ( apiPromise ) {
|
mw.echo.dm.NotificationsModel.prototype.fetchNotifications = function ( isForced ) {
|
||||||
var model = this;
|
var model = this;
|
||||||
|
|
||||||
// Rebuild the notifications promise either when it is null or when
|
// Rebuild the notifications promise either when it is null or when
|
||||||
// it exists in a failed state
|
// it exists in a failed state
|
||||||
return ( apiPromise || this.getApi().fetchNotifications() )
|
return this.api.fetchNotifications( this.getType(), this.getSource(), !!isForced )
|
||||||
.then(
|
.then(
|
||||||
// Success
|
// Success
|
||||||
function ( result ) {
|
function ( data ) {
|
||||||
var notifData, id, t, tlen, s,
|
var notifData, id, s,
|
||||||
notificationModel, types, content,
|
notificationModel, content,
|
||||||
newNotifData = {},
|
newNotifData = {},
|
||||||
sources = {},
|
sources = {},
|
||||||
optionItems = [],
|
optionItems = [],
|
||||||
idArray = [],
|
idArray = [],
|
||||||
data = OO.getProp( result.query, 'notifications', model.type ) || { index: [] },
|
sourceDefinitions = data.sources || {};
|
||||||
sourceDefinitions = OO.getProp( result.query, 'notifications', 'sources' ) || {};
|
|
||||||
|
|
||||||
types = $.isArray( model.type ) ? model.type : [ model.type ];
|
data = data || {};
|
||||||
|
|
||||||
for ( t = 0, tlen = types.length; t < tlen; t++ ) {
|
|
||||||
data = OO.getProp( result.query, 'notifications', types[ t ] ) || { list: [] };
|
|
||||||
for ( id in data.list ) {
|
for ( id in data.list ) {
|
||||||
notifData = data.list[ id ];
|
notifData = data.list[ id ];
|
||||||
content = notifData[ '*' ] || {};
|
content = notifData[ '*' ] || {};
|
||||||
|
@ -525,14 +509,13 @@
|
||||||
|
|
||||||
if ( notifData.type === 'foreign' ) {
|
if ( notifData.type === 'foreign' ) {
|
||||||
// Define sources
|
// Define sources
|
||||||
sources = {};
|
|
||||||
for ( s = 0; s < notifData.sources.length; s++ ) {
|
for ( s = 0; s < notifData.sources.length; s++ ) {
|
||||||
sources[ notifData.sources[ s ] ] = sourceDefinitions[ notifData.sources[ s ] ];
|
sources[ notifData.sources[ s ] ] = sourceDefinitions[ notifData.sources[ s ] ];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create model
|
// Create model
|
||||||
notificationModel = new mw.echo.dm.NotificationGroupItem(
|
notificationModel = new mw.echo.dm.NotificationGroupItem(
|
||||||
model.networkHandler,
|
model.api,
|
||||||
sources,
|
sources,
|
||||||
id,
|
id,
|
||||||
$.extend( true, {}, newNotifData, {
|
$.extend( true, {}, newNotifData, {
|
||||||
|
@ -558,7 +541,6 @@
|
||||||
idArray.push( notifData.id );
|
idArray.push( notifData.id );
|
||||||
optionItems.push( notificationModel );
|
optionItems.push( notificationModel );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add to the model
|
// Add to the model
|
||||||
model.addItems( optionItems, 0 );
|
model.addItems( optionItems, 0 );
|
||||||
|
@ -641,19 +623,9 @@
|
||||||
* Query the API for unread count of the notifications in this model
|
* Query the API for unread count of the notifications in this model
|
||||||
*
|
*
|
||||||
* @return {jQuery.Promise} jQuery promise that's resolved when the unread count is fetched
|
* @return {jQuery.Promise} jQuery promise that's resolved when the unread count is fetched
|
||||||
* and the badge label is updated.
|
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel.prototype.fetchUnreadCountFromApi = function () {
|
mw.echo.dm.NotificationsModel.prototype.fetchUnreadCountFromApi = function () {
|
||||||
return this.getApi().fetchUnreadCount();
|
return this.api.fetchUnreadCount( this.getSource(), this.getType() );
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the model is fetching notifications from the API
|
|
||||||
*
|
|
||||||
* @return {boolean} The model is in the process of fetching from the API
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NotificationsModel.prototype.isFetchingNotifications = function () {
|
|
||||||
return this.getApi().isFetchingNotifications();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -662,7 +634,7 @@
|
||||||
* @return {boolean} The model is in api error state
|
* @return {boolean} The model is in api error state
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel.prototype.isFetchingErrorState = function () {
|
mw.echo.dm.NotificationsModel.prototype.isFetchingErrorState = function () {
|
||||||
return this.getApi().isFetchingErrorState();
|
return this.api.isFetchingErrorState( this.getSource(), this.getType() );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -671,7 +643,7 @@
|
||||||
* fetched from the API.
|
* fetched from the API.
|
||||||
*/
|
*/
|
||||||
mw.echo.dm.NotificationsModel.prototype.getFetchNotificationPromise = function () {
|
mw.echo.dm.NotificationsModel.prototype.getFetchNotificationPromise = function () {
|
||||||
return this.getApi().getFetchNotificationPromise();
|
return this.api.getFetchNotificationPromise( this.getSource(), this.getType() );
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -687,24 +659,6 @@
|
||||||
return items[ 0 ] && items[ 0 ].getTimestamp();
|
return items[ 0 ] && items[ 0 ].getTimestamp();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the API handler associated with this model's source
|
|
||||||
*
|
|
||||||
* @return {mw.echo.dm.APIHandler} API handler
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NotificationsModel.prototype.getApi = function () {
|
|
||||||
return this.networkHandler.getApiHandler( this.source );
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the network handler
|
|
||||||
*
|
|
||||||
* @return {mw.echo.dm.NetworkHandler} Network handler
|
|
||||||
*/
|
|
||||||
mw.echo.dm.NotificationsModel.prototype.getNetworkHandler = function () {
|
|
||||||
return this.networkHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the source this model is associated with
|
* Get the source this model is associated with
|
||||||
*
|
*
|
||||||
|
|
|
@ -22,12 +22,17 @@
|
||||||
TestApiHandler.parent.call( this );
|
TestApiHandler.parent.call( this );
|
||||||
}
|
}
|
||||||
/* Setup */
|
/* Setup */
|
||||||
OO.inheritClass( TestApiHandler, mw.echo.dm.APIHandler );
|
OO.inheritClass( TestApiHandler, mw.echo.api.APIHandler );
|
||||||
// Override api call
|
// Override api call
|
||||||
TestApiHandler.prototype.markItemRead = function () {
|
TestApiHandler.prototype.markItemsRead = function () {
|
||||||
return $.Deferred().resolve( 0 );
|
return $.Deferred().resolve( 0 );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create an Echo API instance
|
||||||
|
var echoApi = new mw.echo.api.EchoApi();
|
||||||
|
// HACK: Reach into the EchoAPI to create a test handler
|
||||||
|
echoApi.network.addCustomApiHandler( 'test', new TestApiHandler() );
|
||||||
|
|
||||||
QUnit.test( 'Adding notifications', function ( assert ) {
|
QUnit.test( 'Adding notifications', function ( assert ) {
|
||||||
var initialItems = [
|
var initialItems = [
|
||||||
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
||||||
|
@ -88,15 +93,13 @@
|
||||||
|
|
||||||
cases.forEach( function ( test ) {
|
cases.forEach( function ( test ) {
|
||||||
var r, runCase, runItem,
|
var r, runCase, runItem,
|
||||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||||
model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
|
||||||
type: 'alert',
|
type: 'alert',
|
||||||
source: 'test',
|
source: 'test',
|
||||||
limit: 25,
|
limit: 25,
|
||||||
userLang: 'en'
|
userLang: 'en'
|
||||||
} );
|
} );
|
||||||
|
|
||||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
|
||||||
model.addItems( test.items );
|
model.addItems( test.items );
|
||||||
|
|
||||||
if ( test.add ) {
|
if ( test.add ) {
|
||||||
|
@ -115,8 +118,7 @@
|
||||||
} );
|
} );
|
||||||
|
|
||||||
QUnit.test( 'Deleting notifications', 2, function ( assert ) {
|
QUnit.test( 'Deleting notifications', 2, function ( assert ) {
|
||||||
var networkHandler = new mw.echo.dm.NetworkHandler(),
|
var model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||||
model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
|
||||||
type: 'alert',
|
type: 'alert',
|
||||||
source: 'test',
|
source: 'test',
|
||||||
limit: 25,
|
limit: 25,
|
||||||
|
@ -135,7 +137,6 @@
|
||||||
new mw.echo.dm.NotificationItem( 10, { content: '10', timestamp: '20150828172900' } )
|
new mw.echo.dm.NotificationItem( 10, { content: '10', timestamp: '20150828172900' } )
|
||||||
];
|
];
|
||||||
|
|
||||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
|
||||||
// Add initial notifications
|
// Add initial notifications
|
||||||
model.addItems( items );
|
model.addItems( items );
|
||||||
|
|
||||||
|
@ -151,7 +152,6 @@
|
||||||
|
|
||||||
QUnit.test( 'Clearing notifications', function ( assert ) {
|
QUnit.test( 'Clearing notifications', function ( assert ) {
|
||||||
var i, ilen, model, actual, test,
|
var i, ilen, model, actual, test,
|
||||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
|
||||||
cases = [
|
cases = [
|
||||||
{
|
{
|
||||||
prepare: [
|
prepare: [
|
||||||
|
@ -182,15 +182,13 @@
|
||||||
assert.expect( cases.length );
|
assert.expect( cases.length );
|
||||||
|
|
||||||
for ( i = 0, ilen = cases.length; i < ilen; i++ ) {
|
for ( i = 0, ilen = cases.length; i < ilen; i++ ) {
|
||||||
model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||||
type: 'alert',
|
type: 'alert',
|
||||||
source: 'test',
|
source: 'test',
|
||||||
limit: 25,
|
limit: 25,
|
||||||
userLang: 'en'
|
userLang: 'en'
|
||||||
} );
|
} );
|
||||||
|
|
||||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
|
||||||
|
|
||||||
test = cases[ i ];
|
test = cases[ i ];
|
||||||
|
|
||||||
// Run preparation
|
// Run preparation
|
||||||
|
@ -204,7 +202,6 @@
|
||||||
|
|
||||||
QUnit.test( 'Changing read/unread status', function ( assert ) {
|
QUnit.test( 'Changing read/unread status', function ( assert ) {
|
||||||
var i,
|
var i,
|
||||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
|
||||||
initialItems = [
|
initialItems = [
|
||||||
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
||||||
new mw.echo.dm.NotificationItem( 1, { timestamp: '20150828173100', read: false } ),
|
new mw.echo.dm.NotificationItem( 1, { timestamp: '20150828173100', read: false } ),
|
||||||
|
@ -231,14 +228,13 @@
|
||||||
QUnit.expect( cases.length );
|
QUnit.expect( cases.length );
|
||||||
|
|
||||||
cases.forEach( function ( test ) {
|
cases.forEach( function ( test ) {
|
||||||
var model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
var model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||||
type: 'alert',
|
type: 'alert',
|
||||||
source: 'test',
|
source: 'test',
|
||||||
limit: 25,
|
limit: 25,
|
||||||
userLang: 'en'
|
userLang: 'en'
|
||||||
} );
|
} );
|
||||||
|
|
||||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
|
||||||
model.addItems( test.items );
|
model.addItems( test.items );
|
||||||
|
|
||||||
if ( test.markRead ) {
|
if ( test.markRead ) {
|
||||||
|
|
Loading…
Reference in a new issue