mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Echo
synced 2024-09-24 18:59:40 +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.List.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.NotificationGroupItem.js',
|
||||
'viewmodel/mw.echo.dm.NotificationList.js',
|
||||
'viewmodel/mw.echo.dm.NotificationsModel.js',
|
||||
),
|
||||
'dependencies' => array(
|
||||
'mediawiki.api',
|
||||
'mediawiki.ForeignApi',
|
||||
'oojs'
|
||||
'oojs',
|
||||
'ext.echo.api',
|
||||
),
|
||||
'messages' => array(
|
||||
'echo-api-failure',
|
||||
|
@ -137,6 +132,22 @@ $wgResourceModules += array(
|
|||
),
|
||||
'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(
|
||||
// This is a dummy module for backwards compatibility.
|
||||
// Most extensions that require ext.echo.base actually need
|
||||
|
@ -154,6 +165,7 @@ $wgResourceModules += array(
|
|||
'scripts' => array(
|
||||
'ext.echo.init.js',
|
||||
),
|
||||
'dependencies' => array( 'ext.echo.api' ),
|
||||
'targets' => array( 'desktop' ),
|
||||
),
|
||||
// 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.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
|
||||
$( document ).ready( function () {
|
||||
var apiRequest, myWidget,
|
||||
var myWidget, echoApi,
|
||||
$existingAlertLink = $( '#pt-notifications-alert a' ),
|
||||
$existingMessageLink = $( '#pt-notifications-message a' ),
|
||||
numAlerts = $existingAlertLink.text(),
|
||||
|
@ -45,7 +31,8 @@
|
|||
$( this ).addClass( 'mw-echo-notifications-badge-dimmed' );
|
||||
|
||||
// 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 ) {
|
||||
mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time );
|
||||
return data;
|
||||
|
@ -61,11 +48,7 @@
|
|||
// Load message button and popup if messages exist
|
||||
if ( $existingMessageLink.length ) {
|
||||
messageNotificationsModel = new mw.echo.dm.NotificationsModel(
|
||||
// Create a network handler
|
||||
new mw.echo.dm.NetworkHandler( {
|
||||
type: 'message',
|
||||
baseParams: mw.echo.apiCallParams
|
||||
} ),
|
||||
echoApi,
|
||||
{
|
||||
type: 'message'
|
||||
}
|
||||
|
@ -93,11 +76,7 @@
|
|||
}
|
||||
// Load alerts popup and button
|
||||
alertNotificationsModel = new mw.echo.dm.NotificationsModel(
|
||||
// Create a network handler
|
||||
new mw.echo.dm.NetworkHandler( {
|
||||
type: 'alert',
|
||||
baseParams: mw.echo.apiCallParams
|
||||
} ),
|
||||
echoApi,
|
||||
{
|
||||
type: 'alert'
|
||||
}
|
||||
|
@ -121,11 +100,8 @@
|
|||
|
||||
// HACK: Now that the module loaded, show the popup
|
||||
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
|
||||
// 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 );
|
||||
} );
|
||||
myWidget.popup.toggle( true );
|
||||
|
|
|
@ -179,12 +179,22 @@
|
|||
* All notifications were marked as read
|
||||
*/
|
||||
|
||||
/**
|
||||
* @event finishLoading
|
||||
* Notifications have successfully finished being processed and are fully loaded
|
||||
*/
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Respond to badge button click
|
||||
*/
|
||||
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();
|
||||
};
|
||||
|
||||
|
@ -248,51 +258,17 @@
|
|||
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.
|
||||
* @fires finishLoading
|
||||
*/
|
||||
mw.echo.ui.NotificationBadgeWidget.prototype.onPopupToggle = function ( isVisible ) {
|
||||
var widget = this;
|
||||
|
||||
if ( this.promiseRunning ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !isVisible ) {
|
||||
// If the popup is closing, remove "initiallyUnseen" and leave
|
||||
this.notificationsWidget.resetNotificationItems();
|
||||
|
@ -316,14 +292,20 @@
|
|||
this.popup.$clippable.css( 'height', '1px' );
|
||||
this.popup.clip();
|
||||
}
|
||||
|
||||
this.pushPending();
|
||||
this.markAllReadButton.toggle( false );
|
||||
this.promiseRunning = true;
|
||||
// Always populate on popup open. The model and widget should handle
|
||||
// the case where the promise is already underway.
|
||||
this.populateNotifications()
|
||||
this.notificationsModel.fetchNotifications()
|
||||
.then( function () {
|
||||
var i,
|
||||
items = widget.notificationsWidget.getItems();
|
||||
|
||||
if ( widget.popup.isVisible() ) {
|
||||
widget.popup.clip();
|
||||
|
||||
// Update seen time
|
||||
widget.notificationsModel.updateSeenTime();
|
||||
// Mark notifications as 'read' if markReadWhenSeen is set to true
|
||||
|
@ -332,6 +314,8 @@
|
|||
}
|
||||
|
||||
// Log impressions
|
||||
// TODO: Only log the impressions of notifications that
|
||||
// are actually visible
|
||||
for ( i = 0; i < items.length; i++ ) {
|
||||
if ( items[ i ].getModel ) {
|
||||
mw.echo.logger.logInteraction(
|
||||
|
@ -343,6 +327,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
widget.emit( 'finishLoading' );
|
||||
} )
|
||||
.always( function () {
|
||||
// Pop pending
|
||||
widget.popPending();
|
||||
widget.promiseRunning = false;
|
||||
} );
|
||||
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
|
||||
*
|
||||
* @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
|
||||
* of its item's sub-items.
|
||||
* @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
|
||||
* '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,
|
||||
items = [];
|
||||
|
||||
|
@ -49,18 +49,15 @@
|
|||
this.removeReadNotifications = !!config.removeReadNotifications;
|
||||
|
||||
this.sources = sources;
|
||||
this.networkHandler = networkHandler;
|
||||
this.api = api;
|
||||
this.notifModels = {};
|
||||
this.anticipatedCount = config.count || 0;
|
||||
|
||||
// Create notification models for each source
|
||||
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
|
||||
item = new mw.echo.dm.NotificationsModel(
|
||||
this.networkHandler,
|
||||
this.api,
|
||||
{
|
||||
type: this.getType(),
|
||||
source: source,
|
||||
|
@ -124,19 +121,19 @@
|
|||
fetchPromises = [],
|
||||
sourceKeys = Object.keys( this.sources );
|
||||
|
||||
return this.networkHandler.fetchNotificationGroups( sourceKeys )
|
||||
.then( function ( promises ) {
|
||||
return this.api.fetchNotificationGroups( sourceKeys, this.getType() )
|
||||
.then( function () {
|
||||
var i;
|
||||
|
||||
for ( i = 0; i < sourceKeys.length; i++ ) {
|
||||
notifModel = model.getItemById( sourceKeys[ i ] );
|
||||
if ( notifModel ) {
|
||||
fetchPromises.push( notifModel.fetchNotifications( promises[ i ] ) );
|
||||
fetchPromises.push( notifModel.fetchNotifications() );
|
||||
}
|
||||
}
|
||||
|
||||
// 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 () {
|
||||
model.anticipatedCount = null;
|
||||
|
@ -180,9 +177,9 @@
|
|||
.then( ( function ( model ) {
|
||||
return function ( idArray ) {
|
||||
// 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
|
||||
model.markExistingItemsReadInApi( idArray );
|
||||
model.markItemsReadInApi( idArray );
|
||||
};
|
||||
} )( notifModels[ i ] ) );
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* @mixins OO.EventEmitter
|
||||
*
|
||||
* @constructor
|
||||
* @param {mw.echo.dm.NetworkHandler} networkHandler Network handler
|
||||
* @param {mw.echo.api.EchoApi} api Network handler
|
||||
* @param {Object} [config] Configuration object
|
||||
* @cfg {string} [id] Model id, used to refer to the model specifically.
|
||||
* Falls back to the model's unique source
|
||||
|
@ -23,7 +23,7 @@
|
|||
* means the model will only contain unread notifications. This is useful for
|
||||
* cross-wiki bundled notifications.
|
||||
*/
|
||||
mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( networkHandler, config ) {
|
||||
mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( api, config ) {
|
||||
config = config || {};
|
||||
|
||||
// Mixin constructor
|
||||
|
@ -43,7 +43,7 @@
|
|||
this.removeReadNotifications = !!config.removeReadNotifications;
|
||||
this.foreign = !!config.foreign;
|
||||
|
||||
this.networkHandler = networkHandler;
|
||||
this.api = api;
|
||||
|
||||
this.seenTime = mw.config.get( 'wgEchoSeenTime' ) || {};
|
||||
|
||||
|
@ -186,7 +186,7 @@
|
|||
// because the API takes a single request to mark all notifications
|
||||
// as read, and we don't need to send multiple individual requests.
|
||||
if ( !this.markingAllAsRead ) {
|
||||
this.markItemReadInApi( id );
|
||||
this.markItemsReadInApi( id );
|
||||
}
|
||||
if ( this.removeReadNotifications ) {
|
||||
// Remove this notification from the model
|
||||
|
@ -341,7 +341,7 @@
|
|||
|
||||
// Only update seenTime in the API locally
|
||||
if ( !this.isForeign() ) {
|
||||
promise = this.getApi().updateSeenTime( type );
|
||||
promise = this.api.updateSeenTime( this.getSource(), type );
|
||||
} else {
|
||||
promise = $.Deferred().resolve();
|
||||
}
|
||||
|
@ -363,8 +363,8 @@
|
|||
length = items.length;
|
||||
|
||||
// Skip if this is an automatic "mark as read" and this model is
|
||||
// external
|
||||
if ( this.external && this.autoMarkReadInProcess ) {
|
||||
// foreign
|
||||
if ( this.foreign && this.autoMarkReadInProcess ) {
|
||||
return $.Deferred().resolve( 0 ).promise();
|
||||
}
|
||||
|
||||
|
@ -382,7 +382,7 @@
|
|||
|
||||
this.markingAllAsRead = true;
|
||||
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 ) {
|
||||
items[ i ].toggleRead( true );
|
||||
items[ i ].toggleSeen( true );
|
||||
|
@ -391,7 +391,7 @@
|
|||
}
|
||||
this.markingAllAsRead = false;
|
||||
|
||||
return this.getApi().markAllRead();
|
||||
return this.api.markAllRead( this.getSource(), this.getType() );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -408,40 +408,27 @@
|
|||
var model = this;
|
||||
|
||||
this.autoMarkReadInProcess = true;
|
||||
this.markAllRead()
|
||||
this.markAllRead( this.getSource(), this.getType() )
|
||||
.then( function () {
|
||||
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
|
||||
* were marked as read.
|
||||
*/
|
||||
mw.echo.dm.NotificationsModel.prototype.markItemReadInApi = function ( itemId ) {
|
||||
mw.echo.dm.NotificationsModel.prototype.markItemsReadInApi = function ( itemIds ) {
|
||||
if ( !this.unreadNotifications.getItemCount() ) {
|
||||
return $.Deferred().resolve( 0 ).promise();
|
||||
}
|
||||
|
||||
return this.getApi().markItemRead( itemId );
|
||||
};
|
||||
itemIds = $.isArray( itemIds ) ? itemIds : [ itemIds ];
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
return this.api.markItemsRead( itemIds, this.getSource(), this.getType() );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -464,100 +451,95 @@
|
|||
/**
|
||||
* Fetch notifications from the API and update the notifications list.
|
||||
*
|
||||
* @param {jQuery.Promise} An existing promise querying the API for notifications.
|
||||
* This allows us to send an API request external 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.
|
||||
* @param {boolean} [isForced] Force a renewed fetching promise. If set to false, the
|
||||
* model will request the stored/cached fetching promise from the API. A 'true' value
|
||||
* will force the API to re-request that information from the server and update the
|
||||
* notifications.
|
||||
* @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;
|
||||
|
||||
// Rebuild the notifications promise either when it is null or when
|
||||
// it exists in a failed state
|
||||
return ( apiPromise || this.getApi().fetchNotifications() )
|
||||
return this.api.fetchNotifications( this.getType(), this.getSource(), !!isForced )
|
||||
.then(
|
||||
// Success
|
||||
function ( result ) {
|
||||
var notifData, id, t, tlen, s,
|
||||
notificationModel, types, content,
|
||||
function ( data ) {
|
||||
var notifData, id, s,
|
||||
notificationModel, content,
|
||||
newNotifData = {},
|
||||
sources = {},
|
||||
optionItems = [],
|
||||
idArray = [],
|
||||
data = OO.getProp( result.query, 'notifications', model.type ) || { index: [] },
|
||||
sourceDefinitions = OO.getProp( result.query, 'notifications', 'sources' ) || {};
|
||||
sourceDefinitions = data.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 ) {
|
||||
notifData = data.list[ id ];
|
||||
content = notifData[ '*' ] || {};
|
||||
for ( id in data.list ) {
|
||||
notifData = data.list[ id ];
|
||||
content = notifData[ '*' ] || {};
|
||||
|
||||
if ( model.getItemById( id ) ) {
|
||||
// Skip if we already have the item
|
||||
// TODO: Instead of skipping, we should consider repopulating
|
||||
// the item, in case there are any changes that would result
|
||||
// in repositioning/resorting in the future.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect common data
|
||||
newNotifData = {
|
||||
read: !!notifData.read,
|
||||
seen: !!notifData.read || notifData.timestamp.mw <= model.getSeenTime(),
|
||||
timestamp: notifData.timestamp.utcmw,
|
||||
category: notifData.category,
|
||||
content: {
|
||||
header: content.header,
|
||||
body: content.body
|
||||
},
|
||||
iconURL: content.iconUrl,
|
||||
iconType: content.icon,
|
||||
type: model.getType(),
|
||||
foreign: model.isForeign(),
|
||||
source: model.getSource(),
|
||||
primaryUrl: OO.getProp( content.links, 'primary', 'url' ),
|
||||
secondaryUrls: OO.getProp( content.links, 'secondary' ) || []
|
||||
};
|
||||
|
||||
if ( notifData.type === 'foreign' ) {
|
||||
// Define sources
|
||||
sources = {};
|
||||
for ( s = 0; s < notifData.sources.length; s++ ) {
|
||||
sources[ notifData.sources[ s ] ] = sourceDefinitions[ notifData.sources[ s ] ];
|
||||
}
|
||||
|
||||
// Create model
|
||||
notificationModel = new mw.echo.dm.NotificationGroupItem(
|
||||
model.networkHandler,
|
||||
sources,
|
||||
id,
|
||||
$.extend( true, {}, newNotifData, {
|
||||
// This should probably be separated by bundled
|
||||
// type. Some types don't have read messages, but
|
||||
// some do
|
||||
removeReadNotifications: true,
|
||||
// Override the foreign flag to 'true' for cross-wiki
|
||||
// notifications.
|
||||
// For bundles that are not foreign (like regular
|
||||
// bundles of notifications) this flag should be false
|
||||
foreign: true,
|
||||
count: notifData.count
|
||||
} )
|
||||
);
|
||||
} else {
|
||||
notificationModel = new mw.echo.dm.NotificationItem(
|
||||
id,
|
||||
newNotifData
|
||||
);
|
||||
}
|
||||
|
||||
idArray.push( notifData.id );
|
||||
optionItems.push( notificationModel );
|
||||
if ( model.getItemById( id ) ) {
|
||||
// Skip if we already have the item
|
||||
// TODO: Instead of skipping, we should consider repopulating
|
||||
// the item, in case there are any changes that would result
|
||||
// in repositioning/resorting in the future.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect common data
|
||||
newNotifData = {
|
||||
read: !!notifData.read,
|
||||
seen: !!notifData.read || notifData.timestamp.mw <= model.getSeenTime(),
|
||||
timestamp: notifData.timestamp.utcmw,
|
||||
category: notifData.category,
|
||||
content: {
|
||||
header: content.header,
|
||||
body: content.body
|
||||
},
|
||||
iconURL: content.iconUrl,
|
||||
iconType: content.icon,
|
||||
type: model.getType(),
|
||||
foreign: model.isForeign(),
|
||||
source: model.getSource(),
|
||||
primaryUrl: OO.getProp( content.links, 'primary', 'url' ),
|
||||
secondaryUrls: OO.getProp( content.links, 'secondary' ) || []
|
||||
};
|
||||
|
||||
if ( notifData.type === 'foreign' ) {
|
||||
// Define sources
|
||||
for ( s = 0; s < notifData.sources.length; s++ ) {
|
||||
sources[ notifData.sources[ s ] ] = sourceDefinitions[ notifData.sources[ s ] ];
|
||||
}
|
||||
|
||||
// Create model
|
||||
notificationModel = new mw.echo.dm.NotificationGroupItem(
|
||||
model.api,
|
||||
sources,
|
||||
id,
|
||||
$.extend( true, {}, newNotifData, {
|
||||
// This should probably be separated by bundled
|
||||
// type. Some types don't have read messages, but
|
||||
// some do
|
||||
removeReadNotifications: true,
|
||||
// Override the foreign flag to 'true' for cross-wiki
|
||||
// notifications.
|
||||
// For bundles that are not foreign (like regular
|
||||
// bundles of notifications) this flag should be false
|
||||
foreign: true,
|
||||
count: notifData.count
|
||||
} )
|
||||
);
|
||||
} else {
|
||||
notificationModel = new mw.echo.dm.NotificationItem(
|
||||
id,
|
||||
newNotifData
|
||||
);
|
||||
}
|
||||
|
||||
idArray.push( notifData.id );
|
||||
optionItems.push( notificationModel );
|
||||
}
|
||||
|
||||
// Add to the model
|
||||
|
@ -641,19 +623,9 @@
|
|||
* 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.NotificationsModel.prototype.fetchUnreadCountFromApi = function () {
|
||||
return this.getApi().fetchUnreadCount();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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();
|
||||
return this.api.fetchUnreadCount( this.getSource(), this.getType() );
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -662,7 +634,7 @@
|
|||
* @return {boolean} The model is in api error state
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
|
|
@ -22,12 +22,17 @@
|
|||
TestApiHandler.parent.call( this );
|
||||
}
|
||||
/* Setup */
|
||||
OO.inheritClass( TestApiHandler, mw.echo.dm.APIHandler );
|
||||
OO.inheritClass( TestApiHandler, mw.echo.api.APIHandler );
|
||||
// Override api call
|
||||
TestApiHandler.prototype.markItemRead = function () {
|
||||
TestApiHandler.prototype.markItemsRead = function () {
|
||||
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 ) {
|
||||
var initialItems = [
|
||||
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
||||
|
@ -88,15 +93,13 @@
|
|||
|
||||
cases.forEach( function ( test ) {
|
||||
var r, runCase, runItem,
|
||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
||||
model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
||||
model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||
type: 'alert',
|
||||
source: 'test',
|
||||
limit: 25,
|
||||
userLang: 'en'
|
||||
} );
|
||||
|
||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
||||
model.addItems( test.items );
|
||||
|
||||
if ( test.add ) {
|
||||
|
@ -115,8 +118,7 @@
|
|||
} );
|
||||
|
||||
QUnit.test( 'Deleting notifications', 2, function ( assert ) {
|
||||
var networkHandler = new mw.echo.dm.NetworkHandler(),
|
||||
model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
||||
var model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||
type: 'alert',
|
||||
source: 'test',
|
||||
limit: 25,
|
||||
|
@ -135,7 +137,6 @@
|
|||
new mw.echo.dm.NotificationItem( 10, { content: '10', timestamp: '20150828172900' } )
|
||||
];
|
||||
|
||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
||||
// Add initial notifications
|
||||
model.addItems( items );
|
||||
|
||||
|
@ -151,7 +152,6 @@
|
|||
|
||||
QUnit.test( 'Clearing notifications', function ( assert ) {
|
||||
var i, ilen, model, actual, test,
|
||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
||||
cases = [
|
||||
{
|
||||
prepare: [
|
||||
|
@ -182,15 +182,13 @@
|
|||
assert.expect( cases.length );
|
||||
|
||||
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',
|
||||
source: 'test',
|
||||
limit: 25,
|
||||
userLang: 'en'
|
||||
} );
|
||||
|
||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
||||
|
||||
test = cases[ i ];
|
||||
|
||||
// Run preparation
|
||||
|
@ -204,7 +202,6 @@
|
|||
|
||||
QUnit.test( 'Changing read/unread status', function ( assert ) {
|
||||
var i,
|
||||
networkHandler = new mw.echo.dm.NetworkHandler(),
|
||||
initialItems = [
|
||||
new mw.echo.dm.NotificationItem( 0, { timestamp: '20150828173000', read: false } ),
|
||||
new mw.echo.dm.NotificationItem( 1, { timestamp: '20150828173100', read: false } ),
|
||||
|
@ -231,14 +228,13 @@
|
|||
QUnit.expect( cases.length );
|
||||
|
||||
cases.forEach( function ( test ) {
|
||||
var model = new mw.echo.dm.NotificationsModel( networkHandler, {
|
||||
var model = new mw.echo.dm.NotificationsModel( echoApi, {
|
||||
type: 'alert',
|
||||
source: 'test',
|
||||
limit: 25,
|
||||
userLang: 'en'
|
||||
} );
|
||||
|
||||
networkHandler.addCustomApiHandler( 'test', new TestApiHandler() );
|
||||
model.addItems( test.items );
|
||||
|
||||
if ( test.markRead ) {
|
||||
|
|
Loading…
Reference in a new issue